diff --git a/apps/web/app/admin/banners/page.tsx b/apps/web/app/admin/banners/page.tsx index a6f17c84fa..f4f5d1ca57 100644 --- a/apps/web/app/admin/banners/page.tsx +++ b/apps/web/app/admin/banners/page.tsx @@ -69,7 +69,7 @@ function BannerListItemSkeleton({ index }: { index: number }) {

- + @@ -77,14 +77,14 @@ function BannerListItemSkeleton({ index }: { index: number }) {

- + - + {index % 2 === 0 && ( - + @@ -626,13 +626,13 @@ function BannerManagementPage({ embedded }: Required

{banner.title}

- {banner.is_active ? '활성' : '비활성'} + {banner.is_active ? '활성' : '비활성'}

{banner.description || '설명 없음'} · 우선순위 {banner.priority}

- {banner.display_target.includes('sidebar') && 데스크톱 배너} - {banner.display_target.includes('mobile_popup') && 모바일 팝업} - {resolvedUrls?.link_url && } + {banner.display_target.includes('sidebar') && 데스크톱 배너} + {banner.display_target.includes('mobile_popup') && 모바일 팝업} + {resolvedUrls?.link_url && }
diff --git a/apps/web/app/admin/evaluations/page.tsx b/apps/web/app/admin/evaluations/page.tsx index b12b53550c..3ea9da9af8 100644 --- a/apps/web/app/admin/evaluations/page.tsx +++ b/apps/web/app/admin/evaluations/page.tsx @@ -3522,7 +3522,11 @@ function AdminEvaluationPage({

- 관리자 데이터 검수 + {embeddedModuleId === 'submissions' + ? '제보 관리' + : embeddedModuleId === 'reviews' + ? '리뷰 관리' + : '관리자 데이터 검수'}

{deepLinkFilter && ( @@ -3564,6 +3568,7 @@ function AdminEvaluationPage({ stats={stats} selectedStatuses={selectedStatuses} onSelectStatuses={setSelectedStatuses} + showStatusChips={!showSubmissionView} >
{canSwitchEvaluationView && ( @@ -3618,7 +3623,7 @@ function AdminEvaluationPage({ 제보 {totalPendingCount > 0 && ( <> - + {totalPendingCount > 99 ? '99+' : totalPendingCount} diff --git a/apps/web/app/api/admin/banners/route.ts b/apps/web/app/api/admin/banners/route.ts new file mode 100644 index 0000000000..4cf1f7515e --- /dev/null +++ b/apps/web/app/api/admin/banners/route.ts @@ -0,0 +1,44 @@ +import { NextResponse } from "next/server"; + +import { requireAdmin } from "@/lib/auth/require-admin"; +import { createSupabaseServiceRoleClient } from "@/lib/supabase/service-role"; + +export const runtime = "nodejs"; + +const AD_BANNER_SELECT = [ + "id", + "title", + "description", + "image_url", + "video_url", + "media_type", + "link_url", + "is_active", + "priority", + "display_target", + "created_at", + "updated_at", + "created_by", +].join(","); + +export async function GET() { + const auth = await requireAdmin(); + if (!auth.ok) return auth.response; + + try { + const supabase = createSupabaseServiceRoleClient(); + const { data, error } = await supabase + .from("ad_banners") + .select(AD_BANNER_SELECT) + .order("priority", { ascending: false }); + + if (error) { + return NextResponse.json({ code: "banner_read_failed" }, { status: 500 }); + } + + return NextResponse.json({ banners: data ?? [] }); + } catch { + return NextResponse.json({ code: "banner_read_failed" }, { status: 500 }); + } + +} diff --git a/apps/web/app/app-globals.css b/apps/web/app/app-globals.css index 51cea9868a..331ffdc4ed 100644 --- a/apps/web/app/app-globals.css +++ b/apps/web/app/app-globals.css @@ -10,8 +10,8 @@ @source not "../{tests-unit,test,tests,fixtures}/**"; /* Tailwind v4 explicit retention for utilities assembled outside scanned route source. */ @source inline("lg:block lg:flex lg:hidden lg:table-cell"); -@source inline("lg:border-r lg:grid lg:overflow-y-auto lg:px-0 lg:sticky sm:col-span-2 sm:grid-cols-2 sm:grid-cols-5 lg:col-span-2 lg:col-span-3 lg:col-span-4 lg:col-span-5 lg:flex-row lg:grid-cols-10 lg:flex-1 lg:grid-rows-[auto_minmax(0,1.15fr)_minmax(0,1fr)] lg:grid-cols-[minmax(0,1.05fr)_minmax(360px,0.95fr)] lg:h-full lg:justify-between lg:min-h-0 lg:overflow-hidden lg:overflow-visible xl:grid-cols-3 xl:grid-cols-4 xl:grid-cols-[1.1fr_0.9fr]"); -@source inline("font-sans font-black font-extrabold gap-0 flex-nowrap justify-end md:justify-end md:p-4 md:border-y-0 overflow-visible overflow-y-visible px-0.5 grid-cols-[5.5rem_minmax(0,1fr)_3rem] grid-rows-2 grid-rows-[auto_minmax(0,1fr)_auto] grid-rows-[minmax(0,1fr)_auto] min-h-[132px] min-h-[190px] min-h-[220px] min-h-[230px] min-h-[280px] min-w-[8%] min-w-fit sm:max-w-none sm:min-w-fit sm:p-1.5 sm:p-3.5 text-[13px] text-lg sm:text-xl tracking-[-0.035em] tracking-[0.01em] tracking-[0.04em] bg-border/70 bg-muted-foreground/25 bg-muted-foreground/28 bg-muted-foreground/30 bg-muted-foreground/35 bg-muted-foreground/45 bg-muted-foreground/55 bg-muted/25 bg-muted/35 bg-muted/45 bg-teal-50 bg-teal-500/55 bg-teal-500/75 border-teal-500/25 dark:bg-muted-foreground/35 dark:bg-muted/20 dark:bg-muted/35 dark:bg-teal-400/55 dark:bg-teal-400/75 dark:bg-teal-950/35 dark:border-teal-400/30 dark:text-emerald-300 md:block md:flex md:hidden md:flex-row md:flex-wrap md:items-start md:justify-between md:gap-1 md:w-auto md:overflow-visible md:pb-0 sm:inline bg-sky-50 bg-sky-50/20 bg-sky-500 bg-teal-500 bg-rose-50 bg-rose-500 bg-orange-50 bg-amber-500 bg-amber-600 text-white text-teal-700 text-sky-700 text-rose-700 text-amber-700 border-sky-500/35 dark:bg-sky-400 dark:bg-sky-950/20 dark:bg-teal-400 dark:bg-teal-500 dark:bg-rose-400 dark:bg-rose-500 dark:bg-amber-400 dark:bg-amber-500 dark:text-white dark:text-teal-300 dark:text-sky-300 dark:text-rose-300 dark:text-amber-300 dark:border-sky-400/30 dark:border-sky-400/45"); +@source inline("lg:border-r lg:grid lg:overflow-y-auto lg:px-0 lg:sticky sm:col-span-2 sm:grid-cols-2 sm:grid-cols-5 lg:col-span-2 lg:col-span-3 lg:col-span-4 lg:col-span-5 lg:col-span-6 lg:flex-row lg:grid-cols-10 lg:flex-1 lg:grid-rows-[auto_minmax(0,1.15fr)_minmax(0,1fr)] lg:grid-cols-[minmax(0,1.05fr)_minmax(360px,0.95fr)] lg:h-full lg:justify-between lg:min-h-0 lg:overflow-hidden lg:overflow-visible xl:grid-cols-3 xl:grid-cols-4 xl:grid-cols-[1.1fr_0.9fr]"); +@source inline("font-sans font-black font-extrabold gap-0 flex-nowrap justify-end md:justify-end md:p-4 md:border-y-0 overflow-visible overflow-y-visible px-0.5 grid-cols-[5.5rem_minmax(0,1fr)_3rem] grid-rows-2 grid-rows-[auto_minmax(0,1fr)_auto] grid-rows-[minmax(0,1fr)_auto] min-h-[132px] min-h-[190px] min-h-[220px] min-h-[230px] min-h-[280px] min-w-[8%] min-w-fit sm:max-w-none sm:min-w-fit sm:p-1.5 sm:p-3.5 text-sm text-lg sm:text-xl tracking-[-0.035em] tracking-[0.01em] tracking-[0.04em] bg-border/70 bg-muted-foreground/25 bg-muted-foreground/28 bg-muted-foreground/30 bg-muted-foreground/35 bg-muted-foreground/45 bg-muted-foreground/55 bg-muted/25 bg-muted/35 bg-muted/45 bg-teal-50 bg-teal-500/55 bg-teal-500/75 border-teal-500/25 dark:bg-muted-foreground/35 dark:bg-muted/20 dark:bg-muted/35 dark:bg-teal-400/55 dark:bg-teal-400/75 dark:bg-teal-950/35 dark:border-teal-400/30 dark:text-emerald-300 md:block md:flex md:hidden md:flex-row md:flex-wrap md:items-start md:justify-between md:gap-1 md:w-auto md:overflow-visible md:pb-0 sm:inline bg-sky-50 bg-sky-50/20 bg-sky-500 bg-teal-500 bg-rose-50 bg-rose-500 bg-orange-50 bg-amber-500 bg-amber-600 text-white text-teal-700 text-sky-700 text-rose-700 text-amber-700 border-sky-500/35 dark:bg-sky-400 dark:bg-sky-950/20 dark:bg-teal-400 dark:bg-teal-500 dark:bg-rose-400 dark:bg-rose-500 dark:bg-amber-400 dark:bg-amber-500 dark:text-white dark:text-teal-300 dark:text-sky-300 dark:text-rose-300 dark:text-amber-300 dark:border-sky-400/30 dark:border-sky-400/45"); @source inline("-z-10 text-red-800 fill-red-800/20 scale-110 text-foreground/65 active:text-foreground text-[12px] font-medium font-semibold leading-none tracking-normal"); @source inline("text-yellow-500 text-muted-foreground text-amber-600"); @source inline("top-[calc(env(safe-area-inset-top)_+_126px)] bottom-[calc(var(--mobile-bottom-nav-effective-height,var(--mobile-bottom-nav-height,60px))_+_env(safe-area-inset-bottom)_+_0.75rem)] z-[61] z-[70]"); @@ -688,12 +688,21 @@ All colors MUST be HSL. @layer base { @import "../styles/light-root-tokens.css"; + *, + ::before, + ::after, + ::backdrop, + ::file-selector-button { + border-color: hsl(var(--border)); + } + :root { --font-sans: var(--font-pretendard, "Pretendard"), "Apple SD Gothic Neo", "Malgun Gothic", "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif; --font-display-fallback: "Noto Serif KR", "Apple SD Gothic Neo", "Malgun Gothic", ui-serif, Georgia, serif; --font-noto-serif-kr: var(--font-display, "Noto Serif KR"), "Apple SD Gothic Neo", "Malgun Gothic", ui-serif, Georgia, serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --admin-sidebar-expanded-width: 14rem; + --phi: 1.618; + --admin-sidebar-expanded-width: calc(8.75rem * var(--phi)); --admin-sidebar-expanded-max-width: min(17.5rem, 27vw); } diff --git a/apps/web/app/global-map/page.tsx b/apps/web/app/global-map/page.tsx index 33f46d6055..4a6bc9e8b6 100644 --- a/apps/web/app/global-map/page.tsx +++ b/apps/web/app/global-map/page.tsx @@ -694,7 +694,7 @@ export default function GlobalMapPage() { {categories.length > 0 && ( {categories.slice(0, 3).map((category) => ( - + {category} ))} diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 1a3f89ccfe..5c964f5a7e 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -88,6 +88,16 @@ html[data-local-workspace="true"] [data-local-workspace-app="true"] { } @layer base { + /* `border` sets width only. Without this, the preflight shorthand paints every + unspecified edge with currentColor, so chips and toasts look black. */ + *, + ::before, + ::after, + ::backdrop, + ::file-selector-button { + border-color: hsl(var(--border)); + } + button, input, textarea, diff --git a/apps/web/app/home-app-globals.css b/apps/web/app/home-app-globals.css index ee635bccdf..fe59eb3e6c 100644 --- a/apps/web/app/home-app-globals.css +++ b/apps/web/app/home-app-globals.css @@ -20,6 +20,10 @@ @source "../lib/naver-map-overlay-position-helpers.ts"; @source "../lib/naver-map-overlay-timings.ts"; +html[data-home-chrome-ready="true"] [data-home-static-skeleton] { + display: none; +} + @media (max-width: 1279px) { html[data-mobile-search-open] .mobile-map-status-badge { display: none; @@ -171,6 +175,14 @@ All colors MUST be HSL. @layer base { @import "../styles/light-root-tokens.css"; + + *, + ::before, + ::after, + ::backdrop, + ::file-selector-button { + border-color: hsl(var(--border)); + } } @layer base { diff --git a/apps/web/app/home-deferred-globals.css b/apps/web/app/home-deferred-globals.css index 95f19b8896..87827a15d7 100644 --- a/apps/web/app/home-deferred-globals.css +++ b/apps/web/app/home-deferred-globals.css @@ -93,6 +93,14 @@ All colors MUST be HSL. @layer base { @import "../styles/light-root-tokens.css"; + + *, + ::before, + ::after, + ::backdrop, + ::file-selector-button { + border-color: hsl(var(--border)); + } } @layer base { diff --git a/apps/web/app/home-detail-globals.css b/apps/web/app/home-detail-globals.css index 3e2828c19e..d9410a7e05 100644 --- a/apps/web/app/home-detail-globals.css +++ b/apps/web/app/home-detail-globals.css @@ -92,6 +92,14 @@ All colors MUST be HSL. @layer base { @import "../styles/light-root-tokens.css"; + + *, + ::before, + ::after, + ::backdrop, + ::file-selector-button { + border-color: hsl(var(--border)); + } } @layer base { diff --git a/apps/web/app/home-runtime-shell.tsx b/apps/web/app/home-runtime-shell.tsx index 3479425845..5b7a09eccd 100644 --- a/apps/web/app/home-runtime-shell.tsx +++ b/apps/web/app/home-runtime-shell.tsx @@ -241,7 +241,8 @@ function MobileHomeLayout({ children }: { children: ReactNode }) { 본문 바로가기 -
+
+
{children}
@@ -317,13 +318,39 @@ function HomeLayoutContent({ children }: { children: ReactNode }) { ); } +function HomeStaticSkeleton() { + return ( +