-
Notifications
You must be signed in to change notification settings - Fork 1
feat: [336]랜딩 페이지 및 관련 컴포넌트 추가, 아이콘 및 이미지 파일 업데이트 #339
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a8224a6
feat: [336]랜딩 페이지 및 관련 컴포넌트 추가, 아이콘 및 이미지 파일 업데이트
cywin1018 ea6658e
feat: 체크리스트 항목에 saveCount 및 isSaved 속성 추가, 기타 코드 개선
cywin1018 457cc8a
Merge pull request #341 from TEAM-DoDream/qa340
cywin1018 ab0d6a6
feat: useEachTodosQuery에 retry 및 refetchOnWindowFocus 비활성화, staleTime…
cywin1018 9075271
Merge pull request #343 from TEAM-DoDream/qa340
cywin1018 d1bd6a3
feat: [336]랜딩 페이지 및 관련 컴포넌트 추가, 아이콘 및 이미지 파일 업데이트
cywin1018 8f182e9
Merge remote-tracking branch 'origin/landing-336' into landing-336
cywin1018 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import KakaoIcon from '@assets/icons/landing/kakao.svg?react'; | ||
|
|
||
| type KakaoShareButtonProps = { | ||
| onClick?: () => void; | ||
| className?: string; | ||
| fixed?: boolean; // 고정 하단 바 형태로 노출 | ||
| offsetBottomPx?: number; | ||
| }; | ||
|
|
||
| // 328 width container 기준: 좌우 padding 18px, 상하 18px, 세로 여백 90px | ||
| const KakaoShareButton = ({ | ||
| onClick, | ||
| className, | ||
| fixed = true, | ||
| offsetBottomPx = 24, | ||
| }: KakaoShareButtonProps) => { | ||
| const bottomWithSafeArea = `calc(${offsetBottomPx}px + env(safe-area-inset-bottom))`; | ||
| const base = | ||
| 'flex w-[328px] items-center justify-center rounded-[16px] px-[18px] py-[18px]'; | ||
| const layout = fixed | ||
| ? `fixed left-1/2 -translate-x-1/2 z-[9999] ${base}` | ||
| : `mx-auto mt-[58px] ${base}`; | ||
| // 시안 색상(카카오 노란색 계열) | ||
| const styles = 'bg-[#FFD400] text-gray-900 shadow-lg'; | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| onClick={onClick} | ||
| className={`${layout} ${styles} ${className ?? ''}`} | ||
| style={fixed ? { bottom: bottomWithSafeArea } : undefined} | ||
| > | ||
| <div className="flex w-full items-center justify-center gap-3"> | ||
| <KakaoIcon className="h-[25px] w-[25px]" /> | ||
| <span className="text-[18px] font-[600] leading-[26px]"> | ||
| 카카오톡에 링크 보내기 | ||
| </span> | ||
| </div> | ||
| </button> | ||
| ); | ||
| }; | ||
|
|
||
| export default KakaoShareButton; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import React from 'react'; | ||
|
|
||
| type MobileFrameProps = { | ||
| children: React.ReactNode; | ||
| className?: string; | ||
| }; | ||
|
|
||
| /** | ||
| * 모바일 전용 프레임: 360px 기준, 390px까지 확장 | ||
| * - 아주 작은 화면에서는 가로 스크롤을 방지하기 위해 w-full | ||
| * - min-[360px] 이상에서 360px 고정, min-[391px] 이상에서 390px 고정 | ||
| * - 안전 영역(inset) 반영 | ||
| */ | ||
| const MobileFrame = ({ children, className }: MobileFrameProps) => { | ||
| const baseClass = | ||
| 'mx-auto w-full max-w-[390px] min-[360px]:w-[360px] min-[391px]:w-[390px] min-h-screen bg-white'; | ||
| const paddingClass = 'px-4'; | ||
|
|
||
| return ( | ||
| <div className="flex w-full justify-center"> | ||
| <div | ||
| className={`${baseClass} ${paddingClass}${className ? ` ${className}` : ''}`} | ||
| style={{ | ||
| paddingTop: 'env(safe-area-inset-top)', | ||
| paddingBottom: 'env(safe-area-inset-bottom)', | ||
| }} | ||
| > | ||
| {children} | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default MobileFrame; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { useEffect, useState } from 'react'; | ||
| import UpIcon from '@assets/icons/up.svg?react'; | ||
|
|
||
| type ScrollTopButtonProps = { | ||
| showAfter?: number; // pixels scrolled before showing | ||
| offsetBottomPx?: number; | ||
| offsetRightPx?: number; | ||
| withinMobileFrame?: boolean; // 모바일 프레임(360~390px) 안쪽에 위치 | ||
| }; | ||
|
|
||
| const ScrollTopButton = ({ | ||
| showAfter = 200, | ||
| offsetBottomPx = 24, | ||
| offsetRightPx = 24, | ||
| withinMobileFrame = true, | ||
| }: ScrollTopButtonProps) => { | ||
| const [visible, setVisible] = useState(false); | ||
| const [rightOffset, setRightOffset] = useState<number>(offsetRightPx); | ||
|
|
||
| useEffect(() => { | ||
| const onScroll = () => { | ||
| setVisible(window.scrollY > showAfter); | ||
| }; | ||
| onScroll(); | ||
| window.addEventListener('scroll', onScroll); | ||
| return () => window.removeEventListener('scroll', onScroll); | ||
| }, [showAfter]); | ||
|
|
||
| // 뷰포트 너비 변화에 따라 모바일 프레임(360~390px) 내부 우측으로 정렬 | ||
| useEffect(() => { | ||
| if (!withinMobileFrame) { | ||
| setRightOffset(offsetRightPx); | ||
| return; | ||
| } | ||
| const computeRight = () => { | ||
| const vw = window.innerWidth; | ||
| const frameWidth = Math.max(360, Math.min(390, vw)); | ||
| const gutter = Math.max(0, (vw - frameWidth) / 2); | ||
| setRightOffset(gutter + offsetRightPx); | ||
| }; | ||
| computeRight(); | ||
| window.addEventListener('resize', computeRight); | ||
| return () => window.removeEventListener('resize', computeRight); | ||
| }, [offsetRightPx, withinMobileFrame]); | ||
|
|
||
| const handleClick = () => { | ||
| window.scrollTo({ top: 0, behavior: 'smooth' }); | ||
| }; | ||
|
|
||
| const bottomWithSafeArea = `calc(${offsetBottomPx}px + env(safe-area-inset-bottom))`; | ||
|
|
||
| return ( | ||
| <button | ||
| aria-label="scroll-to-top" | ||
| onClick={handleClick} | ||
| className={`fixed z-[9999] flex h-[42px] w-[42px] items-center justify-center rounded-full border border-gray-200 bg-white shadow-lg transition-opacity duration-200 hover:shadow-xl ${ | ||
| visible ? 'opacity-100' : 'pointer-events-none opacity-0' | ||
| }`} | ||
| style={{ bottom: bottomWithSafeArea, right: rightOffset }} | ||
| > | ||
| <UpIcon className="h-5 w-5 text-gray-400" /> | ||
| </button> | ||
| ); | ||
| }; | ||
|
|
||
| export default ScrollTopButton; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { Outlet } from 'react-router-dom'; | ||
|
|
||
| const BlankLayout = () => { | ||
| return ( | ||
| <div className="min-h-screen w-full bg-white"> | ||
| <main className="w-full"> | ||
| <Outlet /> | ||
| </main> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default BlankLayout; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| import MobileFrame from '@common/MobileFrame'; | ||
| import MobileLogo from '@assets/icons/mobileLogo.svg?react'; | ||
| import LandingHeroImg from '@assets/images/landing/9.png'; | ||
| import LandingBubbleImg from '@assets/images/landing/10.png'; | ||
| import Card1 from '@assets/images/landing/1.png'; | ||
| import Card2 from '@assets/images/landing/2.png'; | ||
| import LandingImg3 from '@assets/images/landing/3.png'; | ||
| import LandingImg4 from '@assets/images/landing/4.png'; | ||
| import LandingImg5 from '@assets/images/landing/5.png'; | ||
| import LandingImg6 from '@assets/images/landing/6.png'; | ||
| import ScrollTopButton from '@common/ScrollTopButton'; | ||
| import KakaoShareButton from '@common/KakaoShareButton'; | ||
| import InfoIcon from '@assets/icons/info.svg?react'; | ||
|
|
||
| const LandingPage = () => { | ||
| return ( | ||
| <MobileFrame className="mt-4 bg-gray-100"> | ||
| <main className="flex min-h-screen flex-col items-center justify-start pt-0"> | ||
| {/* Top bar (mobile header) */} | ||
| <div className="z-10 flex h-[50px] w-full items-center justify-center bg-white"> | ||
| <MobileLogo className="h-[28px] w-[81px]" /> | ||
| </div> | ||
|
|
||
| {/* Content area with gray-100 background */} | ||
| <div className="flex w-full flex-1 flex-col items-center bg-gray-100"> | ||
| {/* Gradient rounded card */} | ||
| <div className="z-10 mt-6 h-[505px] w-[328px] overflow-hidden rounded-[24px] border border-gray-200 shadow-sm"> | ||
| <div className="relative h-full w-full bg-[linear-gradient(180deg,#E8E6FF_0%,#B7AEFF_100%)]"> | ||
| <div className="px-6 pt-8 text-center"> | ||
| <p className="font-T02-M text-[14px] leading-[22px] text-white/90"> | ||
| 내게 딱 맞는 직업은 뭘까? | ||
| </p> | ||
| <p className="mt-4 text-[28px] leading-[40px] text-white font-T02-B"> | ||
| 인생 2막의 시작은 | ||
| <br /> | ||
| 두드림과 함께 하세요! | ||
| </p> | ||
| </div> | ||
| {/* Bottom white overlay inside the card with info notes */} | ||
| <div className="absolute bottom-0 left-0 right-0 h-[120px] bg-white/40"> | ||
| <div className="h-full w-full px-4 py-3"> | ||
| <div className="flex items-center gap-2 text-gray-800"> | ||
| <InfoIcon className="h-5 w-5 shrink-0 text-gray-700" /> | ||
| <p className="flex-1 text-[14px] font-[600] leading-[22px]"> | ||
| 두드림은 PC 웹에서만 이용 가능해요 | ||
| </p> | ||
| </div> | ||
| <div className="mt-2 flex items-center gap-2 text-gray-800"> | ||
| <InfoIcon className="h-5 w-5 shrink-0 text-gray-700" /> | ||
| <p className="mt-2 flex-1 text-[14px] font-[600] leading-[22px]"> | ||
| 카카오톡에 링크 보내기 버튼을 누른 뒤, PC로 접속해보세요! | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <img | ||
| src={LandingHeroImg} | ||
| alt="landing-hero" | ||
| className="absolute bottom-[140px] left-1/2 z-10 h-[200px] w-[200px] -translate-x-1/2" | ||
| /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Second headline */} | ||
| <p className="mt-10 w-[328px] text-[20px] font-[700] leading-[28px] text-gray-900"> | ||
| 퇴직 후 , 나에게 꼭 맞는 | ||
| <br /> | ||
| 두번째 커리어는? | ||
| </p> | ||
|
|
||
| {/* White rounded card under gradient card */} | ||
| <div className="relative mt-10 h-[295px] w-[328px] rounded-[24px] border border-gray-200 bg-white p-6 shadow-sm"> | ||
| {/* Bubble image bottom center */} | ||
| <img | ||
| src={LandingBubbleImg} | ||
| alt="landing-bubble" | ||
| className="absolute bottom-0 left-1/2 h-[120px] w-auto -translate-x-1/2" | ||
| /> | ||
|
Comment on lines
+74
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 폴드 이하 이미지 지연 로딩/접근성 개선 접근성 향상과 초기 페인트 가속을 위해 아래 이미지들에 lazy/async를 적용하고 alt를 내용 기반으로 보완하세요. - <img
- src={LandingBubbleImg}
- alt="landing-bubble"
+ <img
+ src={LandingBubbleImg}
+ alt="말풍선 장식 이미지"
className="absolute bottom-0 left-1/2 h-[120px] w-auto -translate-x-1/2"
+ loading="lazy"
+ decoding="async"
/>
@@
- <img
- src={Card2}
- alt="card-2"
+ <img
+ src={Card2}
+ alt="추천 카드 예시 2"
className="absolute right-[24px] top-4 z-0 h-[200px] w-auto rotate-[6deg] rounded-[20px]"
+ loading="lazy"
+ decoding="async"
/>
- <img
- src={Card1}
- alt="card-1"
+ <img
+ src={Card1}
+ alt="추천 카드 예시 1"
className="absolute left-[24px] top-0 z-10 h-[220px] w-auto -rotate-[6deg] rounded-[20px]"
+ loading="lazy"
+ decoding="async"
/>
@@
- <img
- src={LandingImg3}
- alt="landing-3"
+ <img
+ src={LandingImg3}
+ alt="서비스 기능 소개 일러스트 3"
className="mx-auto h-auto w-full"
+ loading="lazy"
+ decoding="async"
/>
@@
- <img
- src={LandingImg4}
- alt="landing-4"
+ <img
+ src={LandingImg4}
+ alt="서비스 기능 소개 일러스트 4"
className="mx-auto h-auto w-full"
+ loading="lazy"
+ decoding="async"
/>
@@
- <img
- src={LandingImg5}
- alt="landing-5"
+ <img
+ src={LandingImg5}
+ alt="서비스 기능 소개 일러스트 5"
className="mx-auto h-auto w-full"
+ loading="lazy"
+ decoding="async"
/>
@@
- <img
- src={LandingImg6}
- alt="landing-6"
+ <img
+ src={LandingImg6}
+ alt="서비스 기능 소개 일러스트 6"
className="mx-auto h-auto w-full"
+ loading="lazy"
+ decoding="async"
/>Also applies to: 108-117, 131-136, 148-152, 158-161, 165-168 🤖 Prompt for AI Agents |
||
| {/* Text bubbles */} | ||
| <div className="flex flex-col items-start gap-3"> | ||
| <div className="inline-flex h-[36px] max-w-full items-center rounded-[100px] bg-gray-100 px-4 py-6"> | ||
| <span className="text-[13px] leading-[24px] text-gray-500"> | ||
| 아이들을 다 키우고 나니 어떤 일을 해야할지 모르겠어 | ||
| </span> | ||
| </div> | ||
| <div className="inline-flex h-[36px] max-w-full items-center self-center rounded-[100px] bg-gray-100 px-4 py-3"> | ||
| <span className="text-[13px] leading-[24px] text-gray-500"> | ||
| 체력이 많이 들지 않는 직업은 없을까? | ||
| </span> | ||
| </div> | ||
| <div className="inline-flex h-[36px] max-w-full items-center rounded-[100px] bg-gray-100 px-4 py-3"> | ||
| <span className="text-[13px] leading-[24px] text-gray-500"> | ||
| 어떤 것부터 준비해야 할까? | ||
| </span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* CTA headline */} | ||
| <p className="mt-10 w-[328px] text-center text-[22px] font-[700] text-gray-900"> | ||
| 두드림에서 나에게 딱 맞는 | ||
| <br /> | ||
| 직업을 추천받아보세요! | ||
| </p> | ||
|
|
||
| {/* Overlapping cards illustration */} | ||
| <div className="relative mt-6 h-[220px] w-[328px]"> | ||
| <img | ||
| src={Card2} | ||
| alt="card-2" | ||
| className="absolute right-[24px] top-4 z-0 h-[200px] w-auto rotate-[6deg] rounded-[20px]" | ||
| /> | ||
| <img | ||
| src={Card1} | ||
| alt="card-1" | ||
| className="absolute left-[24px] top-0 z-10 h-[220px] w-auto -rotate-[6deg] rounded-[20px]" | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Helper message */} | ||
| <p className="mt-8 w-[328px] text-center text-[22px] font-[700] text-gray-900"> | ||
| 혼자 준비하려니 막막하셨나요? | ||
| </p> | ||
| <p className="font-T02-M mt-3 w-[328px] text-center text-[14px] leading-[22px] text-gray-700"> | ||
| 같이 준비하는 드리머들의 할 일을 보며 | ||
| <br />내 할 일을 작성해보세요 | ||
| </p> | ||
|
|
||
| {/* Illustration 3 and copy */} | ||
| <div className="mt-6 w-[328px]"> | ||
| <img | ||
| src={LandingImg3} | ||
| alt="landing-3" | ||
| className="mx-auto h-auto w-full" | ||
| /> | ||
| </div> | ||
| <p className="mt-6 w-[328px] text-center text-[22px] font-[700] text-gray-900"> | ||
| 두드림과 함께 해봐요! | ||
| </p> | ||
| <p className="font-T02-M mt-3 w-[328px] text-center text-[14px] leading-[22px] text-gray-700"> | ||
| 두드림에서 직업,학원,구직 정보까지 | ||
| <br /> | ||
| 필요한 정보를 함께 모아보세요 | ||
| </p> | ||
|
|
||
| {/* Illustration 4 */} | ||
| <div className="mt-6 w-[200px]"> | ||
| <img | ||
| src={LandingImg4} | ||
| alt="landing-4" | ||
| className="mx-auto h-auto w-full" | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Illustration 5,6 */} | ||
| <div className="mt-6 w-[320px]"> | ||
| <img | ||
| src={LandingImg5} | ||
| alt="landing-5" | ||
| className="mx-auto h-auto w-full" | ||
| /> | ||
| </div> | ||
| <div className="mb-14 mt-6 w-[320px]"> | ||
| <img | ||
| src={LandingImg6} | ||
| alt="landing-6" | ||
| className="mx-auto h-auto w-full" | ||
| /> | ||
| </div> | ||
| </div> | ||
| </main> | ||
| <ScrollTopButton offsetBottomPx={100} showAfter={0} withinMobileFrame /> | ||
| <KakaoShareButton fixed offsetBottomPx={24} /> | ||
| </MobileFrame> | ||
| ); | ||
| }; | ||
|
|
||
| export default LandingPage; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
LCP(히어로 이미지) 최적화: fetchpriority/명시 크기 지정
히어로 이미지는 LCP 후보이므로 우선순위를 높이고 레이아웃 시프트를 줄이기 위해 명시 크기와 fetchpriority를 권장합니다. alt도 더 구체적으로요.
📝 Committable suggestion
🤖 Prompt for AI Agents