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
Binary file added public/images/bussiness_mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/bussiness_tablet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/landing/landing_expert1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/landing/landing_expert1_mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/landing/landing_expert2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/landing/landing_expert2_mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/landing/landing_expert3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/landing/landing_expert3_mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 15 additions & 10 deletions src/app/_components/common/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const Header = () => {
const [openLogin, setOpenLogin] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isMobileLoginOpen, setIsMobileLoginOpen] = useState(false);
const [isMobileAlertOpen, setIsMobileAlertOpen] = useState(false);
const [mobileAlertType, setMobileAlertType] = useState<'business' | 'price' | null>(null);
const { isAuthenticated, checkAuth, logout } = useAuthStore();
const router = useRouter();
const { user, fetchUser, clearUser } = useUserStore();
Expand Down Expand Up @@ -110,7 +110,7 @@ const Header = () => {
const handleNavClick = (e: React.MouseEvent) => {
if (window.innerWidth < 1024) {
e.preventDefault();
setIsMobileAlertOpen(true);
setMobileAlertType('business');
}
};

Expand Down Expand Up @@ -171,7 +171,7 @@ const Header = () => {
onClick={(e) => {
if (window.innerWidth < 1024) {
e.preventDefault();
setIsMobileAlertOpen(true);
setMobileAlertType('business');
} else if (!isAuthenticated) {
e.preventDefault();
setOpenLogin(true);
Expand All @@ -184,7 +184,7 @@ const Header = () => {
type="button"
onClick={() => {
if (window.innerWidth < 1024) {
setIsMobileAlertOpen(true);
setMobileAlertType('price');
} else if (!isAuthenticated) {
setOpenLogin(true);
} else {
Expand All @@ -207,7 +207,6 @@ const Header = () => {
? 'text-white'
: 'text-gray-900'
}`}
onClick={handleNavClick}
>
전문가
</Link>
Expand All @@ -219,7 +218,12 @@ const Header = () => {
? 'text-white'
: 'text-gray-900'
}`}
onClick={handleNavClick}
onClick={(e) => {
if (window.innerWidth < 1024) {
e.preventDefault();
setMobileAlertType('price');
}
}}
>
요금제
</Link>
Expand Down Expand Up @@ -339,7 +343,7 @@ const Header = () => {
<button
type="button"
className={mobileNavLink}
onClick={() => setIsMobileAlertOpen(true)}
onClick={() => setMobileAlertType('business')}
>
사업계획서
</button>
Expand All @@ -358,7 +362,7 @@ const Header = () => {
<button
type="button"
className={mobileNavLink}
onClick={() => setIsMobileAlertOpen(true)}
onClick={() => setMobileAlertType('price')}
>
요금제
</button>
Expand Down Expand Up @@ -397,8 +401,9 @@ const Header = () => {

{/* Mobile Nav Alert Modal */}
<MobileNavAlertModal
open={isMobileAlertOpen}
onClose={() => setIsMobileAlertOpen(false)}
open={mobileAlertType !== null}
onClose={() => setMobileAlertType(null)}
showBackground={mobileAlertType === 'business'}
/>
</>
);
Expand Down
30 changes: 27 additions & 3 deletions src/app/_components/common/MobileNavAlertModal.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,41 @@
'use client';
import React from 'react';
import Image from 'next/image';
import ErrorIcon from '@/assets/icons/error.svg';

type MobileNavAlertModalProps = {
open: boolean;
onClose: () => void;
showBackground?: boolean;
};

const MobileNavAlertModal = ({ open, onClose }: MobileNavAlertModalProps) => {
const MobileNavAlertModal = ({ open, onClose, showBackground = false }: MobileNavAlertModalProps) => {
if (!open) return null;

return (
<div className="fixed inset-0 z-[200] lg:[display:none]">
{showBackground && (
<>
<Image
src="/images/bussiness_mobile.png"
alt="사업계획서 배경"
fill
className="object-cover md:hidden"
quality={100}
unoptimized
priority
/>
<Image
src="/images/bussiness_tablet.png"
alt="사업계획서 배경"
fill
className="hidden object-cover md:block"
quality={100}
unoptimized
priority
/>
Comment on lines +19 to +36
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

배경 이미지는 장식용으로 처리해주세요.

이 이미지는 정보 전달 역할이 아니라 시각적 배경이라서, 지금처럼 alt 텍스트를 두면 스크린리더가 불필요한 설명을 읽게 됩니다. alt=""로 비우고 aria-hidden을 주거나 CSS background로 옮기는 편이 접근성에 맞습니다.

♿ 제안 수정안
           <Image
             src="/images/bussiness_mobile.png"
-            alt="사업계획서 배경"
+            alt=""
+            aria-hidden="true"
             fill
             className="object-cover md:hidden"
             quality={100}
             unoptimized
             priority
           />
           <Image
             src="/images/bussiness_tablet.png"
-            alt="사업계획서 배경"
+            alt=""
+            aria-hidden="true"
             fill
             className="hidden object-cover md:block"
             quality={100}
             unoptimized
             priority
           />
📝 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
<Image
src="/images/bussiness_mobile.png"
alt="사업계획서 배경"
fill
className="object-cover md:hidden"
quality={100}
unoptimized
priority
/>
<Image
src="/images/bussiness_tablet.png"
alt="사업계획서 배경"
fill
className="hidden object-cover md:block"
quality={100}
unoptimized
priority
/>
<Image
src="/images/bussiness_mobile.png"
alt=""
aria-hidden="true"
fill
className="object-cover md:hidden"
quality={100}
unoptimized
priority
/>
<Image
src="/images/bussiness_tablet.png"
alt=""
aria-hidden="true"
fill
className="hidden object-cover md:block"
quality={100}
unoptimized
priority
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/_components/common/MobileNavAlertModal.tsx` around lines 19 - 36, The
two decorative <Image> usages in the MobileNavAlertModal component are currently
given descriptive alt text which makes screen readers announce irrelevant
content; change both images to be purely decorative by setting alt="" and adding
aria-hidden="true" (or otherwise marking them decorative via CSS background if
you prefer), e.g., update the Image elements rendered in MobileNavAlertModal to
use alt="" and aria-hidden="true" so assistive tech ignores them.

</>
)}
<div
className="absolute inset-0 bg-black/20"
onClick={onClose}
Expand All @@ -25,8 +49,8 @@ const MobileNavAlertModal = ({ open, onClose }: MobileNavAlertModalProps) => {
PC환경에서 서비스를 이용해주세요
</p>
<div className="flex flex-col items-center text-[14px] font-medium leading-[1.5] text-gray-600">
<p>모바일 환경에서는 지원되지 않습니다.</p>
<p>안정적인 경험을 위해 데스크탑 환경을 권장해요!</p>
<p>해당 기능은 모바일을 지원하지 않아요.</p>
<p>데스크탑에서 접속해주세요.</p>
</div>
</div>
<button
Expand Down
4 changes: 4 additions & 0 deletions src/app/_components/landing/Landing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Landing = () => {
const router = useRouter();
const [isModalOpen, setIsModalOpen] = useState(false);
const [isLoginModalOpen, setIsLoginModalOpen] = useState(false);
const [alertShowBackground, setAlertShowBackground] = useState(false);
const [isAlertOpen, setIsAlertOpen] = useState(false);
const { isAuthenticated, checkAuth } = useAuthStore();

Expand All @@ -38,6 +39,7 @@ const Landing = () => {
className="bg-primary-500 ds-subtext hover:bg-primary-600 active:bg-primary-700 h-[44px] w-full cursor-pointer rounded-full px-6 font-medium text-white md:ds-text md:h-[50px] md:w-[220px] md:px-8"
onClick={() => {
if (window.innerWidth < 1024) {
setAlertShowBackground(true);
setIsAlertOpen(true);
} else if (isAuthenticated) {
router.push('/business');
Expand All @@ -52,6 +54,7 @@ const Landing = () => {
className="ds-subtext h-[44px] w-full cursor-pointer rounded-full bg-white px-6 font-semibold text-gray-900 hover:bg-gray-100 active:bg-gray-200 md:ds-text md:h-[50px] md:w-auto md:px-8"
onClick={() => {
if (window.innerWidth < 1024) {
setAlertShowBackground(false);
setIsAlertOpen(true);
} else if (isAuthenticated) {
setIsModalOpen(true);
Expand Down Expand Up @@ -87,6 +90,7 @@ const Landing = () => {
<MobileNavAlertModal
open={isAlertOpen}
onClose={() => setIsAlertOpen(false)}
showBackground={alertShowBackground}
/>

<StickyBar show={showSticky} />
Expand Down
2 changes: 1 addition & 1 deletion src/app/_components/landing/LandingChecklist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const LandingChecklist = () => {
unoptimized={true}
/>
</div>
<MobileNavAlertModal open={isAlertOpen} onClose={() => setIsAlertOpen(false)} />
<MobileNavAlertModal open={isAlertOpen} onClose={() => setIsAlertOpen(false)} showBackground />
</div>
);
};
Expand Down
1 change: 1 addition & 0 deletions src/app/_components/landing/LandingPaySection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ const LandingPaySection = () => {
<MobileNavAlertModal
open={isAlertOpen}
onClose={() => setIsAlertOpen(false)}
showBackground
/>

<div className="mt-[30px] flex flex-col px-2 md:mt-12 lg:mt-12 lg:px-0">
Expand Down
12 changes: 7 additions & 5 deletions src/hooks/useCountdown.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { useState, useEffect } from 'react';

const calculateTimeLeft = (targetDate: string) => {
if (typeof window === 'undefined') {
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
}

const target = new Date(targetDate).getTime();
const now = Date.now();
const diff = target - now;
Expand All @@ -21,10 +17,14 @@ const calculateTimeLeft = (targetDate: string) => {
return { days: 0, hours: 0, minutes: 0, seconds: 0 };
};

const initialTimeLeft = { days: 0, hours: 0, minutes: 0, seconds: 0 };

export const useCountdown = (targetDate: string) => {
const [timeLeft, setTimeLeft] = useState(() => calculateTimeLeft(targetDate));
const [timeLeft, setTimeLeft] = useState(initialTimeLeft);
const [mounted, setMounted] = useState(false);
Comment on lines +20 to +24
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

초기 0 반환 때문에 카운트다운이 잠깐 만료된 것처럼 보입니다.

지금 구현은 SSR/초기 hydration에서 항상 0일 0시간 0분 0초를 먼저 그린 뒤 effect에서 실제 값으로 바뀝니다. hydration 에러는 피했지만, 랜딩 배너가 잠깐 종료된 것처럼 보여 첫 인상이 깨집니다. mount 전에는 값을 숨기거나 동일 크기 placeholder를 렌더링하도록 분리하는 편이 안전합니다.

Also applies to: 38-38

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useCountdown.ts` around lines 20 - 24, The hook currently
initializes timeLeft to initialTimeLeft which renders "0d 0h 0m 0s" on
SSR/hydration and looks expired; change useCountdown so it doesn't expose the
zero state before the component mounts: keep mounted state (mounted) and set it
true inside a useEffect, and either (A) return a second value like isMounted (or
null/undefined timeLeft) so callers can render a hidden/sized placeholder before
mount, or (B) have the hook itself return a placeholder object (same layout
size) until mounted; update references to initialTimeLeft, useCountdown,
timeLeft and mounted accordingly and apply the same change at the other
occurrence noted (line ~38) so no zero countdown is rendered pre-mount.


useEffect(() => {
setMounted(true);
const updateTimer = () => {
setTimeLeft(calculateTimeLeft(targetDate));
};
Expand All @@ -35,5 +35,7 @@ export const useCountdown = (targetDate: string) => {
return () => clearInterval(interval);
}, [targetDate]);

if (!mounted) return initialTimeLeft;

return timeLeft;
};