diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 00000000..eafe4a4a Binary files /dev/null and b/public/favicon.png differ diff --git a/src/api/useGetAddress.ts b/src/api/useGetAddress.ts index e0fae927..9b1e7791 100644 --- a/src/api/useGetAddress.ts +++ b/src/api/useGetAddress.ts @@ -1,5 +1,4 @@ import { api } from '@/lib/api' -import addressStore from '@/store/addressStore' import memberStore from '@/store/user' import { useQuery, useQueryClient } from '@tanstack/react-query' @@ -24,13 +23,11 @@ export interface AddressResponseData { const useGetAddress = () => { const qc = useQueryClient() const { member } = memberStore() - const { setAddress } = addressStore() const { data: address, isSuccess } = useQuery({ queryKey: ['address'], queryFn: async () => { const response = await api.get(`members/address`) - setAddress(response) return response }, diff --git a/src/api/usePostLogout.ts b/src/api/usePostLogout.ts index 83731642..4e61236c 100644 --- a/src/api/usePostLogout.ts +++ b/src/api/usePostLogout.ts @@ -1,6 +1,5 @@ import { useLocalStorage } from '@/hooks/useLocalStorage' import { api } from '@/lib/api' -import addressStore from '@/store/addressStore' import memberStore from '@/store/user' import { useMutation, useQueryClient } from '@tanstack/react-query' @@ -9,7 +8,6 @@ const usePostLogout = () => { const accessToken = useLocalStorage('accessToken') const refreshToken = useLocalStorage('refreshToken') const { resetMember } = memberStore() - const { resetAddress } = addressStore() return useMutation({ mutationFn: async () => @@ -21,7 +19,6 @@ const usePostLogout = () => { accessToken.resetValue() refreshToken.resetValue() resetMember() - resetAddress() queryClient.removeQueries({ queryKey: ['member'] }) queryClient.removeQueries({ queryKey: ['favorites'] }) queryClient.removeQueries({ queryKey: ['carts'] }) diff --git a/src/app/favicon.ico b/src/app/favicon.ico deleted file mode 100644 index 718d6fea..00000000 Binary files a/src/app/favicon.ico and /dev/null differ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 758967da..cc5afa57 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,6 +13,9 @@ import './globals.css' export const metadata: Metadata = { title: '개발의민족', description: '개발의민족', + icons: { + icon: '/favicon.png', + }, } export const viewport = { diff --git a/src/app/mypage/address/_components/AddressOption.tsx b/src/app/mypage/address/_components/AddressOption.tsx index d17b2eab..1d5f3590 100644 --- a/src/app/mypage/address/_components/AddressOption.tsx +++ b/src/app/mypage/address/_components/AddressOption.tsx @@ -3,6 +3,7 @@ import useDeleteAddress from '@/api/useDeleteAddress' import useGetAddress, { AddressResponseData } from '@/api/useGetAddress' import useGetAddressToGeolocation from '@/api/useGetAddressToGeolocation' +import useGetMember from '@/api/useGetMember' import { AddressType } from '@/api/usePostAddress' import usePostDefaultAddress from '@/api/usePostDefaultAddress' import AddressSearchModal from '@/app/mypage/address/_components/AddressSearchModal' @@ -15,9 +16,7 @@ import { useToast } from '@/hooks/useToast' import { cn } from '@/lib/utils' import { modalStore } from '@/store/modal' import memberStore from '@/store/user' -import { ROUTE_PATHS } from '@/utils/routes' import { useQueryClient } from '@tanstack/react-query' -import { useRouter } from 'next/navigation' import { useState } from 'react' import DaumPostcode from 'react-daum-postcode' import AddressDetail, { AddressData } from '../detail/_components/AddressDetail' @@ -26,7 +25,7 @@ const AddressOption = () => { const [word, setWord] = useState('') const [popup, setPopup] = useState(false) - const { member } = memberStore() + const { member, setMember } = memberStore() const { showModal, hideModal } = modalStore() const { mutate: deleteAddress, isPending: isPendingDeleting } = useDeleteAddress() @@ -34,9 +33,9 @@ const AddressOption = () => { usePostDefaultAddress() const { address } = useGetAddress() const { mutate: addressToGeolocation } = useGetAddressToGeolocation() + const { refetch: refetchMember } = useGetMember() const { toast } = useToast() - const router = useRouter() const queryClient = useQueryClient() @@ -88,14 +87,23 @@ const AddressOption = () => { const handleClickSetDefaultAddress = (id: number | undefined) => { if (!id || isPendingSettingDefaultAddress) return if (id === address?.defaultAddress?.id) { - router.push(ROUTE_PATHS.HOME) return } setDefaultAddress(id, { onSuccess: () => { - router.push(ROUTE_PATHS.HOME) + refetchMember().then((res) => { + if (res.data) { + setMember(res.data) + } + }) + queryClient.invalidateQueries({ queryKey: ['address'] }) + + toast({ + title: '기본 주소가 변경되었습니다.', + position: 'center', + }) }, }) } diff --git a/src/app/pay/_components/OrderInfo.tsx b/src/app/pay/_components/OrderInfo.tsx index 68f99439..693da1bc 100644 --- a/src/app/pay/_components/OrderInfo.tsx +++ b/src/app/pay/_components/OrderInfo.tsx @@ -8,6 +8,7 @@ import usePostOrderPay, { OrderPay, OrderPayResponse, OrderPayType } from '@/api import MenuItem from '@/app/pay/_components/MenuItem' import Alert from '@/components/Alert' import { Button } from '@/components/button' +import Confirm from '@/components/Confirm' import Icon from '@/components/Icon' import Separator from '@/components/Separator' import { Checkbox } from '@/components/shadcn/checkbox' @@ -15,7 +16,6 @@ import { Label } from '@/components/shadcn/label' import useBottomSheet from '@/hooks/useBottomSheet' import { useToast } from '@/hooks/useToast' import { cn } from '@/lib/utils' -import addressStore from '@/store/addressStore' import { modalStore } from '@/store/modal' import memberStore from '@/store/user' import { ROUTE_PATHS } from '@/utils/routes' @@ -41,7 +41,6 @@ const OrderInfo = () => { const { member } = memberStore() const { showModal } = modalStore() - const { address } = addressStore() // const { payments, setPayments } = successPaymentStore() const { BottomSheet, hide } = useBottomSheet() @@ -49,13 +48,28 @@ const OrderInfo = () => { const handleEmptyCart = () => { if (!cartsState) return - const cartIds = cartsState.orderMenus.map((menu) => menu.cartId) - deleteCarts( - { cartIds }, - { - onSuccess: () => setCartsState(undefined), - } - ) + + showModal({ + content: ( + { + const cartIds = cartsState.orderMenus.map((menu) => menu.cartId) + deleteCarts( + { cartIds }, + { + onSuccess: () => { + resetCarts() + setCartsState(undefined) + }, + } + ) + }} + /> + ), + }) } const handleIncreaseQuantity = (cartId: number) => { const updateCartsState = (newQuantity: number) => { @@ -138,7 +152,7 @@ const OrderInfo = () => { } const handleOrderPay = async () => { - if (!cartsState || !member || !address) { + if (!cartsState || !member) { showModal({ content: ( { const orderData: OrderPay = { storeId: cartsState.storeId, - roadAddress: address?.defaultAddress?.roadAddress || '', - jibunAddress: address?.defaultAddress?.jibunAddress || '', - detailAddress: address?.defaultAddress?.detailAddress || '', + roadAddress: member.address.roadAddress || '', + jibunAddress: member.address.jibunAddress || '', + detailAddress: member.address.detailAddress || '', excludingSpoonAndFork: isExcludingSpoon, orderType: 'DELIVERY', // paymentType, @@ -228,6 +242,11 @@ const OrderInfo = () => { return } + // 결제 시작 전 현재 URL을 히스토리에 추가 + if (window.history && window.history.pushState) { + window.history.pushState(null, '', window.location.href) + } + if (paymentType === OrderPayType.PAY200) { // SDK 초기화 const requestPayment = pay200SDK({ @@ -264,9 +283,9 @@ const OrderInfo = () => { // customerMobilePhone: "01012341234", card: { useEscrow: false, - flowMode: 'DIRECT', - // flowMode: 'DEFAULT', - cardCompany: 'TOSSBANK', + flowMode: 'DEFAULT', + // flowMode: 'DIRECT', + // cardCompany: 'TOSSBANK', useCardPoint: false, useAppCardOnly: false, }, @@ -316,7 +335,7 @@ const OrderInfo = () => { } return ( -
+
@@ -333,16 +352,14 @@ const OrderInfo = () => {
- {`${address?.defaultAddress?.roadAddress} ${address?.defaultAddress?.detailAddress}`} + {`${member?.address.roadAddress} ${member?.address.detailAddress}`}
(으)로 배달
-
- [지번] {address?.defaultAddress?.jibunAddress} -
+
[지번] {member?.address.jibunAddress}
@@ -443,22 +460,28 @@ const OrderInfo = () => { {(totalMenuPrice + deliveryPrice).toLocaleString()}원
- {isUnderMinOrder && ( -

- {(storeDetail.minimumOrderAmount - totalMenuPrice).toLocaleString()}원 더 담으면 배달 - 가능해요 + + {/* 배달비 무료 조건 추가 */} +

+

+ {isUnderMinOrder + ? `${(storeDetail.minimumOrderAmount - totalMenuPrice).toLocaleString()}원 더 담으면 배달 가능해요` + : '배달비 무료!'}

- )} - + +
) } diff --git a/src/app/pay/fail/_components/PayFail.tsx b/src/app/pay/fail/_components/PayFail.tsx index 4118f054..5d138c94 100644 --- a/src/app/pay/fail/_components/PayFail.tsx +++ b/src/app/pay/fail/_components/PayFail.tsx @@ -19,7 +19,7 @@ const PayFail = () => { useEffect(() => { if (timer <= 0) { - router.push(ROUTE_PATHS.PAY) + router.replace(ROUTE_PATHS.PAY) } }, [timer]) diff --git a/src/app/pay/success/_components/PaySuccess.tsx b/src/app/pay/success/_components/PaySuccess.tsx index ae3ad583..12ecacce 100644 --- a/src/app/pay/success/_components/PaySuccess.tsx +++ b/src/app/pay/success/_components/PaySuccess.tsx @@ -21,6 +21,14 @@ const PaySuccess = () => { const { mutate: payment } = usePostPayment() useEffect(() => { + // 브라우저 히스토리 조작 + if (window.history && window.history.pushState) { + window.history.pushState(null, '', window.location.href) + window.onpopstate = () => { + router.replace(ROUTE_PATHS.HOME) + } + } + if (orderId && paymentKey && amount) { payment( { @@ -71,7 +79,7 @@ const PaySuccess = () => { title="결제 실패" message="결제 중 오류가 발생했습니다." onClick={() => { - router.push(ROUTE_PATHS.PAY) + router.replace(ROUTE_PATHS.PAY) }} /> ), diff --git a/src/app/store/detail/[id]/_components/OrderButton.tsx b/src/app/store/detail/[id]/_components/OrderButton.tsx index 9291201f..46a1f914 100644 --- a/src/app/store/detail/[id]/_components/OrderButton.tsx +++ b/src/app/store/detail/[id]/_components/OrderButton.tsx @@ -30,20 +30,18 @@ export const OrderButton = ({ minimumOrderAmount }: { minimumOrderAmount: number } return ( -
- {isUnderMinOrder && ( -

- {(minimumOrderAmount - totalPrice).toLocaleString()}원 더 담으면 배달 가능해요 -

- )} -
diff --git a/src/app/store/detail/[id]/_components/StoreOrderDetail.tsx b/src/app/store/detail/[id]/_components/StoreOrderDetail.tsx index f3bffd31..d80c3e9b 100644 --- a/src/app/store/detail/[id]/_components/StoreOrderDetail.tsx +++ b/src/app/store/detail/[id]/_components/StoreOrderDetail.tsx @@ -10,6 +10,7 @@ import Confirm from '@/components/Confirm' import Icon from '@/components/Icon' import { Skeleton } from '@/components/shadcn/skeleton' import LoginModal from '@/components/shared/LoginModal' +import UpDownBtn from '@/components/UpDownBtn' import { useLocalStorage } from '@/hooks/useLocalStorage' import { useThrottle } from '@/hooks/useThrottle' import { toast } from '@/hooks/useToast' @@ -20,7 +21,6 @@ import { orderDetailStore } from '@/store/orderDetail' import { COLORS } from '@/styles/color' import { motion } from 'motion/react' import Image from 'next/image' -import { useRouter } from 'next/navigation' import { useCallback, useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import MenuOption from './MenuOption' @@ -43,6 +43,7 @@ const StoreOrderDetail = ({ minimumOrderAmount }: { minimumOrderAmount: number } const [isTextOverflow, setIsTextOverflow] = useState(false) const [isExpanded, setIsExpanded] = useState(false) const [price, setPrice] = useState(0) + const [quantity, setQuantity] = useState(1) const [isHeaderOpaque, setIsHeaderOpaque] = useState(false) const [selectedOptions, setSelectedOptions] = useState>({}) const [isValid, setIsValid] = useState(false) @@ -52,8 +53,6 @@ const StoreOrderDetail = ({ minimumOrderAmount }: { minimumOrderAmount: number } orderDetail?.menuId ?? '' ) - const router = useRouter() - const onChangeOption = ( id: string, action: 'add' | 'remove' | 'change', @@ -120,7 +119,7 @@ const StoreOrderDetail = ({ minimumOrderAmount }: { minimumOrderAmount: number } storeId: orderDetail.storeId.toString(), orderMenu: { menuId: storeMenuOptions.menuId, - quantity: 1, + quantity, orderMenuOptionGroups: Object.entries(selectedOptions).map(([groupId, group]) => { return { id: groupId, @@ -388,15 +387,19 @@ const StoreOrderDetail = ({ minimumOrderAmount }: { minimumOrderAmount: number } delay: 0.2, }} > -

+

{minimumOrderAmount.toLocaleString()}원부터 주문 가능해요

- +
+ + +
, document.body diff --git a/src/assets/images/Pay200_Logo.png b/src/assets/images/Pay200_Logo.png deleted file mode 100644 index 12389e94..00000000 Binary files a/src/assets/images/Pay200_Logo.png and /dev/null differ diff --git a/src/assets/images/Toss_Logo.png b/src/assets/images/Toss_Logo.png deleted file mode 100644 index 697043c6..00000000 Binary files a/src/assets/images/Toss_Logo.png and /dev/null differ diff --git a/src/components/CommonLayout.tsx b/src/components/CommonLayout.tsx index b66b132b..18b4e3d1 100644 --- a/src/components/CommonLayout.tsx +++ b/src/components/CommonLayout.tsx @@ -1,7 +1,5 @@ 'use client' -import useGetAddress from '@/api/useGetAddress' -import useGetGeolocationToAddress from '@/api/useGetGeolocationToAddress' import useGetMember from '@/api/useGetMember' import usePostLogout from '@/api/usePostLogout' import { getNavigationProps } from '@/constants/navigationProps' @@ -39,17 +37,9 @@ const CommonLayout = ({ children }: CommonLayoutProps) => { const { refetch } = useGetMember() const { mutate: logout } = usePostLogout() - const { address } = useGetAddress() - const { mutate: getGeolocationToAddress } = useGetGeolocationToAddress() - - const { member, setMember } = memberStore() - const { - setCoordinates, - setAddress, - address: addressStoreAddress, - setError, - setIsLoading, - } = useGeoLocationStore() + + const { setMember } = memberStore() + const { setCoordinates, setError, setIsLoading } = useGeoLocationStore() const { isGlobalLoading, setIsGlobalLoading } = globalLoaderStore() useEffect(() => { @@ -82,12 +72,13 @@ const CommonLayout = ({ children }: CommonLayoutProps) => { useEffect(() => { if (!isMounted) return - + setIsGlobalLoading(false) const requestGeolocation = async () => { try { const permissionStatus = await navigator.permissions.query({ name: 'geolocation' }) if (permissionStatus.state === 'denied') { + setIsGlobalLoading(false) setError( '위치 정보가 차단되어 있습니다.\n 브라우저 설정에서 위치 정보 접근을 허용해주세요.' ) @@ -102,41 +93,9 @@ const CommonLayout = ({ children }: CommonLayoutProps) => { longitude: position.coords.longitude, } - console.log(process.env.NEXT_PUBLIC_KAKAO_API_KEY) - - if (!addressStoreAddress || !member) { - getGeolocationToAddress( - { - latitude: coords.latitude.toString(), - longitude: coords.longitude.toString(), - }, - { - onSuccess: (data) => { - const address = data.documents[0] - - setAddress({ - addressName: address.address.address_name, - sido: address.address.region_1depth_name, - sigungu: address.address.region_2depth_name, - roadAddress: address.road_address?.address_name || '', - jibunAddress: address.address.address_name, - }) - }, - onError: (error) => { - console.log(error) - }, - onSettled: () => { - setCoordinates(coords) - setIsLoading(false) - setIsGlobalLoading(false) - }, - } - ) - } else { - setCoordinates(coords) - setIsLoading(false) - setIsGlobalLoading(false) - } + setCoordinates(coords) + setIsLoading(false) + setIsGlobalLoading(false) }, (error) => { console.log('위치 정보 에러:', error) diff --git a/src/components/Confirm.tsx b/src/components/Confirm.tsx index 616c2dab..748508f0 100644 --- a/src/components/Confirm.tsx +++ b/src/components/Confirm.tsx @@ -31,7 +31,7 @@ const Confirm = ({ } return ( -
+
{title}
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 123b7c80..8e2ea179 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -1,14 +1,12 @@ 'use client' import { cn } from '@/lib/utils' -import addressStore from '@/store/addressStore' import { useGeoLocationStore } from '@/store/geoLocation' import { modalStore } from '@/store/modal' import memberStore from '@/store/user' import { ROUTE_PATHS } from '@/utils/routes' import { useRouter } from 'next/navigation' import Icon from './Icon' -import LoginModal from './shared/LoginModal' export interface NavigationProps { hasBackButton?: boolean @@ -32,7 +30,6 @@ const Navigation = ({ const router = useRouter() const { address } = useGeoLocationStore() const { member } = memberStore() - const { address: addressStoreAddress } = addressStore() const { showModal } = modalStore() return ( @@ -68,21 +65,13 @@ const Navigation = ({ onClick={() => { if (useAddress && member) { router.push(ROUTE_PATHS.ADDRESS) - } else { - showModal({ - content: , - useAnimation: true, - }) } }} > - {useAddress - ? addressStoreAddress - ? addressStoreAddress.defaultAddress?.roadAddress || - addressStoreAddress.defaultAddress?.jibunAddress + - ' ' + - addressStoreAddress.defaultAddress?.detailAddress - : address?.roadAddress || address?.jibunAddress + {useAddress && member + ? `${member.address.roadAddress || member.address.jibunAddress} ${ + member.address.detailAddress + }` : title} {useAddress && member && } diff --git a/src/components/UpDownBtn.tsx b/src/components/UpDownBtn.tsx index b9f1b591..8ec22388 100644 --- a/src/components/UpDownBtn.tsx +++ b/src/components/UpDownBtn.tsx @@ -1,19 +1,52 @@ 'use client' import Icon from '@/components/Icon' -interface UpDownProps { +import { cn } from '@/lib/utils' +import { cva, VariantProps } from 'class-variance-authority' + +const upDownBtnVariants = cva( + 'flex flex-row items-center rounded-lg border border-solid border-gray-300 px-2', + { + variants: { + size: { + sm: 'h-8', + md: 'h-12', + }, + }, + defaultVariants: { + size: 'sm', + }, + } +) + +interface UpDownProps extends VariantProps { value: number onChange: (value: number) => void } -const UpDownBtn = ({ value = 0, onChange }: UpDownProps) => { +const UpDownBtn = ({ value = 0, onChange, size = 'sm' }: UpDownProps) => { return ( -
+
-
{value}
+
+ {value} +
) diff --git a/src/components/button.tsx b/src/components/button.tsx index 0c0a43f5..1d0ebede 100644 --- a/src/components/button.tsx +++ b/src/components/button.tsx @@ -15,7 +15,7 @@ const buttonVariants = cva( size: { default: 'rounded-md text-base h-12', - s: 'rounded text-sm h-10', + s: 'rounded-lg text-sm h-12', m: 'rounded-md text-lg font-semibold h-14', }, diff --git a/src/models/auth.ts b/src/models/auth.ts index 968602c5..eb494ea1 100644 --- a/src/models/auth.ts +++ b/src/models/auth.ts @@ -38,4 +38,11 @@ export interface Member { id: number signname: string nickname: string + address: { + roadAddress: string + jibunAddress: string + detailAddress: string + latitude: number + longitude: number + } } diff --git a/src/store/addressStore.ts b/src/store/addressStore.ts deleted file mode 100644 index 1216d0fa..00000000 --- a/src/store/addressStore.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AddressResponseData } from '@/api/useGetAddress' -import { create } from 'zustand' - -interface AddressStore { - address: AddressResponseData | null - setAddress: (address: AddressResponseData) => void - resetAddress: () => void -} - -const addressStore = create((set) => ({ - address: null, - setAddress: (address) => set({ address }), - resetAddress: () => set({ address: null }), -})) - -export default addressStore diff --git a/src/store/geoLocation.ts b/src/store/geoLocation.ts index f3e92c09..2e083eae 100644 --- a/src/store/geoLocation.ts +++ b/src/store/geoLocation.ts @@ -11,6 +11,7 @@ interface Address { sido: string // 시/도 sigungu: string // 시/군/구 addressName: string // 주소 + detailAddress: string // 상세 주소 } interface GeoLocationStore { @@ -27,8 +28,15 @@ interface GeoLocationStore { } export const useGeoLocationStore = create((set) => ({ - coordinates: { latitude: 37.5177, longitude: 127.0473 }, - address: null, + coordinates: { latitude: 37.4955498697675, longitude: 127.029293901519 }, + address: { + roadAddress: '서울특별시 강남구 강남대로 364', + jibunAddress: '서울 강남구 역삼동 826-21', + sido: '서울특별시', + sigungu: '강남구', + addressName: '역삼동', + detailAddress: '미왕빌딩 10층 10C', + }, isLoading: true, error: null, diff --git a/src/store/user.ts b/src/store/user.ts index c9dd85fb..16a9c7c6 100644 --- a/src/store/user.ts +++ b/src/store/user.ts @@ -13,6 +13,13 @@ const memberStore = create((set) => ({ set({ member: { ...member, + address: member.address || { + roadAddress: '', + jibunAddress: '', + detailAddress: '', + latitude: 0, + longitude: 0, + }, }, }), resetMember: () => set({ member: null }),