From 1e4ade54f5dc3bc0a26661aecbfcace7600d29ae Mon Sep 17 00:00:00 2001 From: fakerdeft Date: Mon, 27 Jul 2026 08:06:36 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=20=EB=B0=8F=20=EC=A0=9C=EB=B3=B4=20=ED=9D=90=EB=A6=84?= =?UTF-8?q?=20=EC=95=88=EC=A0=95=ED=99=94=20-=20GPS=20=EC=9E=90=EB=8F=99?= =?UTF-8?q?=20=EA=B0=90=EC=A7=80=EB=A5=BC=20=EC=B5=9C=EC=B4=88=20=EC=A0=91?= =?UTF-8?q?=EC=86=8D=201=ED=9A=8C=EB=A1=9C=20=EC=A0=9C=ED=95=9C=ED=95=98?= =?UTF-8?q?=EA=B3=A0=20=EC=84=A0=ED=83=9D=ED=95=9C=20=EB=8F=99=EB=84=A4=20?= =?UTF-8?q?=EC=9C=A0=EC=A7=80=20-=20=ED=99=94=EB=A9=B4=20=EB=B3=B5?= =?UTF-8?q?=EA=B7=80=20=EC=8B=9C=20=ED=98=84=EC=9E=AC=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=EB=A5=BC=20=EB=8B=A4=EC=8B=9C=20=EA=B0=90=EC=A7=80=ED=95=98?= =?UTF-8?q?=EB=8D=98=20=EB=8F=99=EC=9E=91=20=EC=A0=9C=EA=B1=B0=20-=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EA=B0=90=EC=A7=80=20=EC=97=AC=EB=B6=80?= =?UTF-8?q?=EB=A5=BC=20=EB=A1=9C=EC=BB=AC=20=EC=A0=80=EC=9E=A5=EC=86=8C?= =?UTF-8?q?=EC=97=90=20=EC=98=81=EC=86=8D=ED=99=94=ED=95=98=EA=B3=A0=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20-=20=ED=99=88,=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC,=20=EC=A0=9C=EB=B3=B4=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9D=98=20=EC=9C=84=EC=B9=98=20=ED=83=90=EC=83=89=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EB=A5=BC=20=ED=9A=8C=EC=A0=84=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=EC=9C=BC=EB=A1=9C=20=ED=86=B5=EC=9D=BC=20-=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EB=AA=A8=EB=8B=AC=EA=B3=BC=20=EC=A0=9C?= =?UTF-8?q?=EB=B3=B4=20=EC=9C=84=EC=B9=98=20=EC=98=81=EC=97=AD=EC=9D=98=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0,=20=EB=AC=B8=EA=B5=AC=20=EB=B0=8F=20GPS=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20-=20=EC=9C=84=EC=B9=98=20=EA=B2=80=EC=83=89=20?= =?UTF-8?q?=EB=B9=88=20=EC=83=81=ED=83=9C=20=EB=AC=B8=EA=B5=AC=EB=A5=BC=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B2=B0=EA=B3=BC=20=EC=97=86=EC=9D=8C?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=86=B5=EC=9D=BC=20-=20=ED=99=88=20?= =?UTF-8?q?=EB=82=A0=EC=94=A8=20=ED=86=B5=EA=B3=84=EC=9D=98=20=EA=B0=95?= =?UTF-8?q?=EC=88=98=20=EB=AA=85=EC=B9=AD=EC=9D=84=20=EB=B9=84=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20-=20=EC=A0=9C=EB=B3=B4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=9D=B4=ED=83=88=20=EC=8B=9C=20=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=20=EB=8B=A8=EA=B3=84=EB=A5=BC=20=EC=B4=88=EA=B8=B0=ED=99=94?= =?UTF-8?q?=ED=95=B4=202=EB=8B=A8=EA=B3=84=20=EC=9E=AC=EC=A7=84=EC=9E=85?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A4=91=EB=B3=B5=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=20=EB=B0=A9=EC=A7=80=20-=20=EC=A0=9C=EB=B3=B4=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=ED=99=94=EB=A9=B4=EC=9D=98=20=EC=A0=9C=EB=AA=A9,?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=EC=B9=B4=EB=93=9C=20=EB=86=92=EC=9D=B4?= =?UTF-8?q?=EC=99=80=20=EC=84=B9=EC=85=98=20=EA=B0=84=EA=B2=A9=20=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=20-=20=EC=86=8C=EC=85=9C=20=EA=B3=84=EC=A0=95=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EC=99=84=EB=A3=8C=20=ED=9B=84=20=EA=B3=84?= =?UTF-8?q?=EC=A0=95=20=EC=A0=95=EB=B3=B4=20=ED=99=94=EB=A9=B4=EC=9D=84=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=20=EC=97=B4=EC=96=B4=20=EC=97=B0=EB=8F=99=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=ED=99=95=EC=9D=B8=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?-=20=EA=B3=84=EC=A0=95=20=EC=97=B0=EB=8F=99=20=EC=95=88?= =?UTF-8?q?=EB=82=B4=20=EB=AA=A8=EB=8B=AC=EC=9D=98=20=ED=85=8C=EB=91=90?= =?UTF-8?q?=EB=A6=AC=EC=99=80=20=EA=B7=B8=EB=A6=BC=EC=9E=90=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=A1=B0=EC=A0=95=20-=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95=20=EB=AA=A8=EB=8B=AC=EC=9D=98=20?= =?UTF-8?q?=EC=9E=90=EB=8F=99=20=ED=82=A4=EB=B3=B4=EB=93=9C=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EC=A0=9C=EA=B1=B0=20-=20=EB=AA=A8=EB=B0=94?= =?UTF-8?q?=EC=9D=BC=20=EB=B7=B0=ED=8F=AC=ED=8A=B8=20=EB=86=92=EC=9D=B4?= =?UTF-8?q?=EB=A5=BC=20dvh=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=B4=EC=99=84=20-=20=EB=B9=84=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=95=98=EB=8B=A8=20=ED=94=84=EB=A1=9C=ED=95=84=20=EC=95=84?= =?UTF-8?q?=EC=9D=B4=EC=BD=98=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20-=20README=EC=9D=98=20=EC=83=81=EB=8B=A8=20?= =?UTF-8?q?=EB=B0=8F=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=AF=B8=EB=A6=AC?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +--- app/globals.css | 4 +- components/auth-callback-screen.tsx | 4 +- components/home-screen.tsx | 4 +- components/home-weather-controls.tsx | 5 ++- components/location-detecting-indicator.tsx | 30 ++++++++++++++ components/location-picker.tsx | 15 +++---- components/main-tab-navigation.tsx | 4 +- components/my-page.tsx | 18 +++++++-- components/name-edit-modal.tsx | 1 - components/report-form.tsx | 43 +++++++++++++++------ components/user-panel.tsx | 14 ++++++- components/weather-summary.tsx | 2 +- hooks/use-current-location.ts | 38 +++--------------- lib/account-panel-return.ts | 19 +++++++++ store/location-store.ts | 30 ++++++++------ 16 files changed, 157 insertions(+), 81 deletions(-) create mode 100644 components/location-detecting-indicator.tsx create mode 100644 lib/account-panel-return.ts diff --git a/README.md b/README.md index ec09dd6..1a646bd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@
- 날씨로그

날씨로그

예보보다 가까운, 이웃이 직접 전하는 지금 날씨 @@ -14,11 +13,7 @@ 날씨로그는 같은 동네의 이웃들이 직접 느낀 날씨를 사진과 함께 공유하는 모바일 웹 서비스입니다. 숫자로 표시된 예보만으로 알기 어려운 체감온도, 비, 햇빛 상태를 최근 제보로 빠르게 확인할 수 있습니다. -## 서비스 미리보기 - -

- 맑은 하늘 제보 -

+## 핵심 기능 ### 동네 날씨 피드 diff --git a/app/globals.css b/app/globals.css index 4c1eb7e..fb6ce04 100644 --- a/app/globals.css +++ b/app/globals.css @@ -16,6 +16,7 @@ html { background: #deeff8; } body { margin: 0; min-height: 100vh; + min-height: 100dvh; color: var(--ink); background: linear-gradient(145deg, #deeff8 0%, #f4fbff 50%, #ddecf5 100%); font-family: "Pretendard Variable", Pretendard, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif; @@ -27,12 +28,13 @@ button { cursor: pointer; } position: relative; width: min(100%, 480px); min-height: 100vh; + min-height: 100dvh; margin: 0 auto; overflow-x: clip; background: #eef9ff; box-shadow: 0 0 48px rgba(48, 98, 126, .12); } -.page { min-height: 100vh; padding: 0 20px 112px; } +.page { min-height: 100vh; min-height: 100dvh; padding: 0 20px 112px; } .main-tab-page { padding-bottom: 80px; } .safe-top { padding-top: max(8px, env(safe-area-inset-top)); } .home-header-top { padding-top: max(9px, env(safe-area-inset-top)); } diff --git a/components/auth-callback-screen.tsx b/components/auth-callback-screen.tsx index 82ff26b..881b0bb 100644 --- a/components/auth-callback-screen.tsx +++ b/components/auth-callback-screen.tsx @@ -6,6 +6,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { SocialIcon } from "@/components/social-icon"; import { authApi, type OAuthCallbackResult } from "@/lib/api/auth-api"; +import { markAccountPanelReturn } from "@/lib/account-panel-return"; import { resolveApiUrl } from "@/lib/api/config"; import { logger } from "@/lib/logging"; import type { SocialProvider } from "@/lib/types"; @@ -55,6 +56,7 @@ export function AuthCallbackScreen() { const code = searchParams.get("code"); if (result === "LINK_SUCCESS") { authLogger.info("authentication_completed", { result }); + markAccountPanelReturn(); showToast("소셜 계정을 연동했어요.", "SUCCESS"); router.replace("/mypage"); return; @@ -128,7 +130,7 @@ export function AuthCallbackScreen() { return (
-
+

계정 연동

diff --git a/components/home-screen.tsx b/components/home-screen.tsx index fc0cd73..3e71241 100644 --- a/components/home-screen.tsx +++ b/components/home-screen.tsx @@ -17,7 +17,7 @@ import { getLocationName } from "@/lib/constants"; import { useToastStore } from "@/store/toast-store"; export function HomeScreen() { - const { location, setLocation, isDetecting, detectionError, needsManualInput, setNeedsManualInput, detectLocation } = useCurrentLocation({ refreshOnResume: true }); + const { location, setLocation, isDetecting, detectionError, needsManualInput, setNeedsManualInput, detectLocation } = useCurrentLocation(); const showToast = useToastStore((state) => state.showToast); const loadMoreRef = useRef(null); const locationLabel = location ? getLocationName(location, "short") : ""; @@ -55,7 +55,7 @@ export function HomeScreen() { const hasWeatherData = hasSummaryData || hasReportData; const hasWeatherError = summary.isError || reports.isError; const showLocationError = !location && !isDetecting && Boolean(detectionError); - const isInitialWeatherLoading = !locationLabel || (!hasWeatherData && (summary.isPending || reports.isPending)); + const isInitialWeatherLoading = !isDetecting && Boolean(locationLabel) && !hasWeatherData && (summary.isPending || reports.isPending); const showFullWeatherError = Boolean(locationLabel) && !hasWeatherData && hasWeatherError && !summary.isPending && !reports.isPending; return (
diff --git a/components/home-weather-controls.tsx b/components/home-weather-controls.tsx index f808132..78e2998 100644 --- a/components/home-weather-controls.tsx +++ b/components/home-weather-controls.tsx @@ -2,6 +2,7 @@ import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { ChevronDown, MapPin, RotateCw } from "lucide-react"; import { useSyncExternalStore } from "react"; +import { LocationDetectingIndicator } from "@/components/location-detecting-indicator"; let openedAt = 0; const openedAtListeners = new Set<() => void>(); @@ -42,11 +43,11 @@ export function HomeWeatherControls({ : "현재 시각"; return ( -
+
diff --git a/components/location-detecting-indicator.tsx b/components/location-detecting-indicator.tsx new file mode 100644 index 0000000..0f516f8 --- /dev/null +++ b/components/location-detecting-indicator.tsx @@ -0,0 +1,30 @@ +import { LoaderCircle } from "lucide-react"; + +export function LocationDetectingIndicator({ + className = "", + compact = false, + iconPosition = "left", +}: { + className?: string; + compact?: boolean; + iconPosition?: "left" | "right"; +}) { + const spinner = ( + + ); + + return ( + + {iconPosition === "left" && spinner} + 위치를 찾는 중 + {iconPosition === "right" && spinner} + + ); +} diff --git a/components/location-picker.tsx b/components/location-picker.tsx index 22f8887..3045b23 100644 --- a/components/location-picker.tsx +++ b/components/location-picker.tsx @@ -4,6 +4,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AlertCircle, Check, Flame, LockKeyhole, LocateFixed, MapPin, RefreshCw, Search, Star, X } from "lucide-react"; import { FormEvent, useEffect, useState, type ReactNode } from "react"; import { createPortal } from "react-dom"; +import { LocationDetectingIndicator } from "@/components/location-detecting-indicator"; import { getLocationName } from "@/lib/constants"; import { locationApi } from "@/lib/api/location-api"; import { truncateText } from "@/lib/text"; @@ -106,14 +107,14 @@ function LocationPickerDialog({ current, isDetecting, detectionError, required,
{!required && }
-
- -

현재 설정된 위치

{isDetecting ?
:

{selectedLocation ? getLocationName(selectedLocation, "full") : current ? getLocationName(current, "full") : "아직 설정되지 않았어요"}

}
- +
+ +
{isDetecting ? :

{selectedLocation ? getLocationName(selectedLocation, "full") : current ? getLocationName(current, "full") : "아직 설정되지 않았어요"}

}
+
{detectionError &&

{detectionError}

}
-
+
setActiveTab("SEARCH")} icon={} label="검색" /> setActiveTab("FAVORITES")} icon={} label="즐겨찾기" /> setActiveTab("POPULAR")} icon={} label="인기" /> @@ -130,8 +131,8 @@ function LocationPickerDialog({ current, isDetecting, detectionError, required, {!serverSearch.isError && searchResults.map((location) => )} {serverSearch.isFetching &&
{Array.from({ length: 3 }).map((_, index) =>
)}
} {serverSearch.isError && serverSearch.refetch()} />} - {value.trim() && !serverSearch.isFetching && !serverSearch.isError && searchResults.length === 0 &&

지원하는 지역을 찾지 못했어요.

} - {!value.trim() &&

찾고 싶은 시·구 또는 동 이름을 입력해 주세요.

} + {value.trim() && !serverSearch.isFetching && !serverSearch.isError && searchResults.length === 0 &&

검색 결과가 없습니다.

} + {!value.trim() &&

검색 결과가 없습니다.

}
} {activeTab === "FAVORITES" && (isMember diff --git a/components/main-tab-navigation.tsx b/components/main-tab-navigation.tsx index a766359..30f6b6e 100644 --- a/components/main-tab-navigation.tsx +++ b/components/main-tab-navigation.tsx @@ -43,7 +43,9 @@ export function MainTabNavigation() { const profileActive = pathname === "/mypage"; const profileContent = (