Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,7 @@ export default function Header({
className={`h-16 ${darkMode ? "invert" : ""}`}
/>
) : (
<h1
className="font-semibold text-[#333333] m-0"
style={{ fontSize: "1.2rem" }}
>
<h1 className="font-semibold text-[#333333] m-0 text-lg">
{getPageTitle()}
</h1>
)}
Expand Down
25 changes: 10 additions & 15 deletions src/components/common/GradientButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ type Props = {
gradientTo?: string;
textColor?: string;
borderWidth?: number;
width?: number;
height?: number;
borderRadius?: number;
fontSize?: number;
className?: string;
buttonClassName?: string;
disabled?: boolean;
fullWidth?: boolean;
};

export default function GradientButton({
Expand All @@ -24,34 +23,30 @@ export default function GradientButton({
gradientTo = "#678BF7",
textColor = "#FFFFFF",
borderWidth = 3,
width = 40,
height = 12,
borderRadius,
fontSize,
className = "",
buttonClassName = "",
disabled = false,
fullWidth = false,
}: Props) {
return (
// 바깥 div: 그라데이션 배경 (테두리 역할)
<div
className={`rounded-full ${disabled ? "opacity-50 cursor-not-allowed" : ""} ${className}`}
className={`rounded-full ${fullWidth ? "w-full" : ""} ${disabled ? "opacity-50 cursor-not-allowed" : ""} ${className}`}
style={{
borderRadius: `${borderRadius}px`,
borderRadius: borderRadius ? `${borderRadius}px` : undefined,
background: `linear-gradient(135deg, ${gradientFrom}, ${gradientTo})`,
padding: borderWidth,
}}
>
{/* 안쪽 버튼: 단색 배경 */}
<button
onClick={onClick}
disabled={disabled}
className={`flex items-center justify-center gap-2 rounded-full font-semibold text-base transition-opacity ${disabled ? "cursor-not-allowed" : "active:opacity-80"}`}
className={`gradient-btn flex items-center justify-center gap-2 rounded-full font-semibold transition-opacity ${fullWidth ? "w-full" : ""} ${disabled ? "cursor-not-allowed" : "active:opacity-80"} ${buttonClassName}`}
style={{
borderRadius: `${borderRadius}px`,
borderRadius: borderRadius ? `${borderRadius}px` : undefined,
backgroundColor: bgColor,
padding: `${height}px ${width}px`, // 세로 가로
color: `${textColor}`,
fontSize: `${fontSize}rem`,
padding: "10px 24px",
color: textColor,
}}
>
{children}
Expand Down
13 changes: 4 additions & 9 deletions src/components/common/PolicyScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,7 @@ export default function PolicyScroll({
return (
<div>
<div className="flex items-center justify-between mb-4">
<h2
className="font-semibold text-[#333333]"
style={{ fontSize: "1.125em" }}
>
{title}
</h2>
<h2 className="font-semibold text-[#333333] text-lg">{title}</h2>
</div>

<div
Expand All @@ -178,10 +173,10 @@ export default function PolicyScroll({
>
{getPolicyIcon(policy.type)}
</div>
<div className="text-[#999999] mb-1" style={{ fontSize: "0.75em" }}>
[{policy.type}]
<div className="text-[#999999] mb-1 text-xs">[{policy.type}]</div>
<div className="text-[#333333] font-medium text-base">
{policy.title}
</div>
<div className="text-[#333333] font-medium">{policy.title}</div>
</div>
))}
</div>
Expand Down
25 changes: 25 additions & 0 deletions src/hooks/usePieChartSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect, useState } from "react";

// usePieChartSize.ts
export function usePieChartSize() {
const calc = () => {
const vw = window.innerWidth;
if (vw >= 1400) return { size: 320, strokeWidth: 25 };
if (vw >= 768)
return {
size: Math.round(Math.min(vw * 0.5, 460)),
strokeWidth: Math.round(Math.min(vw * 0.04, 40)), // vw의 3%, 최대 40
};
return { size: 320, strokeWidth: 25 };
};
Comment on lines +5 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

SSR/하이드레이션 환경에서 window 접근 시 오류 발생 가능

calc 함수가 window.innerWidth에 직접 접근하므로, SSR 환경이나 초기 하이드레이션 단계에서 ReferenceError가 발생할 수 있습니다.

🛠️ 제안된 수정
 const calc = () => {
+  if (typeof window === "undefined") return { size: 320, strokeWidth: 25 };
   const vw = window.innerWidth;
   if (vw >= 1400) return { size: 320, strokeWidth: 25 };
   if (vw >= 768)
     return {
       size: Math.round(Math.min(vw * 0.5, 460)),
-      strokeWidth: Math.round(Math.min(vw * 0.04, 40)), // vw의 3%, 최대 40
+      strokeWidth: Math.round(Math.min(vw * 0.04, 40)), // vw의 4%, 최대 40
     };
   return { size: 320, strokeWidth: 25 };
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const calc = () => {
const vw = window.innerWidth;
if (vw >= 1400) return { size: 320, strokeWidth: 25 };
if (vw >= 768)
return {
size: Math.round(Math.min(vw * 0.5, 460)),
strokeWidth: Math.round(Math.min(vw * 0.04, 40)), // vw의 3%, 최대 40
};
return { size: 320, strokeWidth: 25 };
};
const calc = () => {
if (typeof window === "undefined") return { size: 320, strokeWidth: 25 };
const vw = window.innerWidth;
if (vw >= 1400) return { size: 320, strokeWidth: 25 };
if (vw >= 768)
return {
size: Math.round(Math.min(vw * 0.5, 460)),
strokeWidth: Math.round(Math.min(vw * 0.04, 40)), // vw의 4%, 최대 40
};
return { size: 320, strokeWidth: 25 };
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/usePieChartSize.ts` around lines 5 - 14, The calc function in
usePieChartSize currently accesses window.innerWidth directly causing
ReferenceError in SSR/hydration; update calc (and any callers) to guard access
by checking typeof window !== "undefined" before reading window.innerWidth or
accept an optional width parameter (e.g., calc(width?: number)) and use a
sensible default fallback (like 1024) when window is not available, then compute
size/strokeWidth the same way; ensure usePieChartSize calls the
guarded/parameterized calc during server render to avoid direct window access.


const [chart, setChart] = useState(calc);

useEffect(() => {
const handler = () => setChart(calc());
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, []);

return chart;
}
113 changes: 106 additions & 7 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,114 @@
}

/* 큰글씨 모드에서 Tailwind rem 기반 텍스트 클래스 오버라이드 */
.large-text-content .text-xs { font-size: 0.85em !important; }
.large-text-content .text-sm { font-size: 0.975em !important; }
.large-text-content .text-base { font-size: 1.13em !important; }
.large-text-content .text-lg { font-size: 1.27em !important; }
.large-text-content .text-xl { font-size: 1.41em !important; }
.large-text-content .text-2xl { font-size: 1.69em !important; }
.large-text-content .text-3xl { font-size: 2.1em !important; }
.large-text-content .text-xs {
font-size: 0.85em !important;
}
.large-text-content .text-sm {
font-size: 0.975em !important;
}
.large-text-content .text-base {
font-size: 1.13em !important;
}
.large-text-content .text-lg {
font-size: 1.27em !important;
}
.large-text-content .text-xl {
font-size: 1.41em !important;
}
.large-text-content .text-2xl {
font-size: 1.69em !important;
}
.large-text-content .text-3xl {
font-size: 2.1em !important;
}

/* 큰글씨 모드에서 상태바는 고정 크기 유지 */
.large-text-content .status-bar {
font-size: 14px !important;
}

/* 400px 이하에서 모든 텍스트 크기 축소 */
@media (max-width: 400px) {
.text-xs {
font-size: 0.65rem !important;
} /* 10px */
.text-sm {
font-size: 0.75rem !important;
} /* 12px */
.text-base {
font-size: 0.8rem !important;
} /* 13px */
.text-lg {
font-size: 0.9rem !important;
} /* 14px */
.text-xl {
font-size: 1rem !important;
} /* 16px */
.text-2xl {
font-size: 1.2rem !important;
} /* 19px */
.text-3xl {
font-size: 1.4rem !important;
} /* 22px */

/* 간격도 함께 축소 */
.gap-3 {
gap: 0.5rem !important;
}
.gap-4 {
gap: 0.75rem !important;
}
.mb-4 {
margin-bottom: 0.75rem !important;
}
.p-4 {
padding: 0.75rem !important;
}
.px-6 {
padding-left: 1rem !important;
padding-right: 1rem !important;
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/* 태블릿 뷰 (768px 이상)에서 텍스트 크기 확대 */
@media (min-width: 768px) and (max-width: 1399px) {
.text-xs {
font-size: 0.875rem !important;
} /* 14px */
.text-sm {
font-size: 1rem !important;
} /* 16px */
.text-base {
font-size: 1.125rem !important;
} /* 18px */
.text-lg {
font-size: 1.25rem !important;
} /* 20px */
.text-xl {
font-size: 1.5rem !important;
} /* 24px */
.text-2xl {
font-size: 1.875rem !important;
} /* 30px */
.text-3xl {
font-size: 2.25rem !important;
} /* 36px */

/* 간격도 함께 확대 */
.gap-3 {
gap: 1rem !important;
}
.gap-4 {
gap: 1.25rem !important;
}
.mb-4 {
margin-bottom: 1.25rem !important;
}
.p-4 {
padding: 1.25rem !important;
}
.px-6 {
padding-left: 1.5rem !important;
padding-right: 1.5rem !important;
}
}
6 changes: 3 additions & 3 deletions src/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export default function Layout({ children }: LayoutProps) {
return (
// 1. 전체 화면을 고정하고 스크롤을 막기
<div
className={`flex justify-center h-[100dvh] min-h-[600px] overflow-hidden bg-[#f5f5f5] font-sans ${darkMode ? "dark" : ""}`}
className={`flex justify-center h-[100dvh] min-h-[600px] overflow-hidden bg-[#f5f5f5] font-sans tablet:bg-white ${darkMode ? "dark" : ""}`}
>
<div
className={`relative w-[480px] max-w-full min-w-[330px] h-full flex flex-col bg-no-repeat bg-top transition-all duration-300 ${darkMode ? "invert" : ""} ${largeTextMode ? "large-text-content" : ""}`}
className={`relative w-full desktop:w-[480px] max-w-full min-w-[330px] h-full flex flex-col bg-no-repeat bg-top transition-all duration-300 ${darkMode ? "invert" : ""} ${largeTextMode ? "large-text-content" : ""}`}
style={{
backgroundImage: `url(${backgroundImg})`,
backgroundSize: "100% auto",
Expand All @@ -65,7 +65,7 @@ export default function Layout({ children }: LayoutProps) {
{/* 3. 메인 스크롤 영역: min-h-0이 중요!!! flex 자식의 최소 높이를 0으로 풀어야 내부 스크롤이 잡힘 */}
<main
ref={mainRef}
className="flex-1 overflow-y-auto relative px-2 custom-scrollbar min-h-0"
className="flex-1 overflow-y-auto relative px-2 tablet:px-8 desktop:px-2 custom-scrollbar min-h-0"
>
{/* pt-10 정도로 늘려서 차트가 헤더를 침범하지 못하게 물리적 공간을 확보하세요. */}
<div className="pt-5 pb-10">{children}</div>
Expand Down
23 changes: 16 additions & 7 deletions src/page/Admin/AdminLoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,12 @@ export default function AdminLoginPage() {
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.3 }}
>
<h1 className="text-2xl font-semibold text-gray-900">관리자 로그인</h1>
<p className="text-gray-600 text-sm mt-1">관리자 계정으로 로그인해주세요</p>
<h1 className="text-2xl font-semibold text-gray-900">
관리자 로그인
</h1>
<p className="text-gray-600 text-sm mt-1">
관리자 계정으로 로그인해주세요
</p>
</motion.div>

{/* 입력 폼 */}
Expand All @@ -124,7 +128,9 @@ export default function AdminLoginPage() {
transition={{ duration: 0.8, delay: 0.4 }}
>
<div>
<label className="block text-lg font-medium text-gray-700 mb-3">이메일</label>
<label className="block text-lg font-medium text-gray-700 mb-3">
이메일
</label>
<input
type="email"
value={email}
Expand All @@ -136,7 +142,9 @@ export default function AdminLoginPage() {
/>
</div>
<div>
<label className="block text-lg font-medium text-gray-700 mb-3">비밀번호</label>
<label className="block text-lg font-medium text-gray-700 mb-3">
비밀번호
</label>
<input
type="password"
value={password}
Expand All @@ -157,19 +165,20 @@ export default function AdminLoginPage() {

{/* 로그인 버튼 */}
<motion.div
className="max-w-sm mt-12"
className="max-w-sm mt-12 w-full"
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
>
<GradientButton
fullWidth
onClick={handleLogin}
bgColor="#678BF7"
gradientFrom="#A8C8FF"
gradientTo="#678BF7"
width={120}
height={16}
disabled={isLoading}
buttonClassName="py-4"
borderRadius={15}
>
<span className="text-lg font-semibold">
{isLoading ? "로그인 중..." : "관리자 로그인"}
Expand Down
Loading
Loading