diff --git a/.gitignore b/.gitignore index fe174d56..e97d3b74 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ storybook-static # Claude Code .claude/ .claude/skills -.claude/agents \ No newline at end of file +.claude/agents + +# Local: Cursor commit message trailer stripper (keep on machine only) +scripts/strip-cursor-attribution.mjs diff --git a/index.html b/index.html index 682d8cf3..073ad93b 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + whereyouad diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 00000000..f2b6747d --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icon/common/check-white.svg b/src/assets/icon/common/check-white.svg new file mode 100644 index 00000000..3b569791 --- /dev/null +++ b/src/assets/icon/common/check-white.svg @@ -0,0 +1,3 @@ + diff --git a/src/assets/icon/common/copy.svg b/src/assets/icon/common/copy.svg deleted file mode 100644 index 4e75b25f..00000000 --- a/src/assets/icon/common/copy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/icon/common/edit.svg b/src/assets/icon/common/edit.svg deleted file mode 100644 index 1420a4fb..00000000 --- a/src/assets/icon/common/edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/icon/common/userProfile.svg b/src/assets/icon/common/userProfile.svg deleted file mode 100644 index 4aedf754..00000000 --- a/src/assets/icon/common/userProfile.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/assets/logo/service-logo/logo-mark-white.svg b/src/assets/logo/service-logo/logo-mark-white.svg deleted file mode 100644 index 65eae731..00000000 --- a/src/assets/logo/service-logo/logo-mark-white.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/logo/service-logo/logo-mark.svg b/src/assets/logo/service-logo/logo-mark.svg deleted file mode 100644 index 502a5774..00000000 --- a/src/assets/logo/service-logo/logo-mark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/assets/logo/service-logo/logo.svg b/src/assets/logo/service-logo/logo.svg new file mode 100644 index 00000000..f2b6747d --- /dev/null +++ b/src/assets/logo/service-logo/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/mockup/iOS app dock.png b/src/assets/mockup/iOS app dock.png index f09affee..139d5d90 100644 Binary files a/src/assets/mockup/iOS app dock.png and b/src/assets/mockup/iOS app dock.png differ diff --git a/src/assets/mockup/logo_test/logo_2.png b/src/assets/mockup/logo_test/logo_2.png deleted file mode 100644 index 3585b8b3..00000000 Binary files a/src/assets/mockup/logo_test/logo_2.png and /dev/null differ diff --git a/src/components/auth/common/AuthFormShell.tsx b/src/components/auth/common/AuthFormShell.tsx new file mode 100644 index 00000000..5f4c084f --- /dev/null +++ b/src/components/auth/common/AuthFormShell.tsx @@ -0,0 +1,30 @@ +import { type ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; + +const shellBase = "mx-auto w-full max-w-130 px-6"; + +type TAuthFormShellVariant = "page" | "social" | "step"; + +const variantClasses: Record = { + page: "py-12", + social: "flex flex-col items-center", + step: "pb-12", +}; + +interface IAuthFormShellProps { + children: ReactNode; + variant?: TAuthFormShellVariant; + className?: string; +} + +export default function AuthFormShell({ + children, + variant = "step", + className, +}: IAuthFormShellProps) { + return ( +
+ {children} +
+ ); +} diff --git a/src/components/auth/common/PasswordForm.tsx b/src/components/auth/common/PasswordForm.tsx index 6bb7ecf8..174ba45f 100644 --- a/src/components/auth/common/PasswordForm.tsx +++ b/src/components/auth/common/PasswordForm.tsx @@ -5,6 +5,7 @@ import type { z } from "zod"; import { signupPasswordSchema } from "@/utils/validation"; +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/button/Button"; @@ -37,7 +38,7 @@ export default function PasswordForm({ }; return ( -
+

{title}

- + ); } diff --git a/src/components/auth/flows/find-email/EnterPhoneStep.tsx b/src/components/auth/flows/find-email/EnterPhoneStep.tsx index ad79e158..9c9806d7 100644 --- a/src/components/auth/flows/find-email/EnterPhoneStep.tsx +++ b/src/components/auth/flows/find-email/EnterPhoneStep.tsx @@ -11,6 +11,7 @@ import { findEmailSchema } from "@/utils/validation"; import { useAuth } from "@/hooks/auth/useAuth"; import { useTimer } from "@/hooks/common/useTimer"; +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/button/Button"; @@ -113,7 +114,7 @@ export default function EnterPhoneStep({ onNext }: IEnterPhoneStepProps) { }, [watchedCode, watchedPhone]); return ( -
+

이메일 찾기를 위해 휴대폰 인증을 진행할게요 @@ -211,6 +212,6 @@ export default function EnterPhoneStep({ onNext }: IEnterPhoneStepProps) { 비밀번호 찾기

- + ); } diff --git a/src/components/auth/flows/find-email/ShowEmailResultStep.tsx b/src/components/auth/flows/find-email/ShowEmailResultStep.tsx index 9a1b3223..03182de9 100644 --- a/src/components/auth/flows/find-email/ShowEmailResultStep.tsx +++ b/src/components/auth/flows/find-email/ShowEmailResultStep.tsx @@ -2,6 +2,7 @@ import { useNavigate } from "react-router-dom"; import { maskEmail } from "@/utils/maskEmail"; +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import Button from "@/components/common/button/Button"; import useAuthStore from "@/store/useAuthStore"; @@ -11,7 +12,7 @@ export default function ShowEmailResultStep() { const { email } = useAuthStore(); return ( -
+

입력하신 정보로 WYA에 가입된 계정을 찾았어요 @@ -43,6 +44,6 @@ export default function ShowEmailResultStep() { > 로그인으로 돌아가기 -

+ ); } diff --git a/src/components/auth/flows/reset-password/EmailVerificationStep.tsx b/src/components/auth/flows/reset-password/EmailVerificationStep.tsx index 8069789e..0385b7b7 100644 --- a/src/components/auth/flows/reset-password/EmailVerificationStep.tsx +++ b/src/components/auth/flows/reset-password/EmailVerificationStep.tsx @@ -1,6 +1,7 @@ import { useAuth } from "@/hooks/auth/useAuth"; import { useEmailVerification } from "@/hooks/auth/useEmailVerification"; +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/button/Button"; @@ -31,7 +32,7 @@ export default function EmailVerificationStep({ } = useEmailVerification({ onNext, sendMutation: useRequestPasswordReset }); return ( -
+

비밀번호 찾기를 위해 이메일 인증을 진행할게요 @@ -121,6 +122,6 @@ export default function EmailVerificationStep({

)} - + ); } diff --git a/src/components/auth/flows/signup/EnterEmailStep.tsx b/src/components/auth/flows/signup/EnterEmailStep.tsx index 6eec2a2b..ee533db4 100644 --- a/src/components/auth/flows/signup/EnterEmailStep.tsx +++ b/src/components/auth/flows/signup/EnterEmailStep.tsx @@ -1,5 +1,6 @@ import { useEmailVerification } from "@/hooks/auth/useEmailVerification"; +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/button/Button"; @@ -27,7 +28,7 @@ export default function EnterEmailStep({ onNext }: IEnterEmailStepProps) { } = useEmailVerification({ onNext }); return ( -
+

회원가입을 위해 이메일 인증을 진행할게요 @@ -116,6 +117,6 @@ export default function EnterEmailStep({ onNext }: IEnterEmailStepProps) {

)} - + ); } diff --git a/src/components/auth/flows/signup/ProfileSetupStep.tsx b/src/components/auth/flows/signup/ProfileSetupStep.tsx index 9880a2fc..d2a7aef5 100644 --- a/src/components/auth/flows/signup/ProfileSetupStep.tsx +++ b/src/components/auth/flows/signup/ProfileSetupStep.tsx @@ -10,6 +10,7 @@ import { signupProfileSchema } from "@/utils/validation"; import { useAuth } from "@/hooks/auth/useAuth"; +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import CommonAuthInput from "@/components/auth/common/CommonAuthInput"; import Button from "@/components/common/button/Button"; @@ -69,7 +70,7 @@ export default function ProfileSetupStep({ password }: IProfileSetupStepProps) { }; return ( -
+

사용자의 기본 정보를 입력해 주세요 @@ -143,6 +144,6 @@ export default function ProfileSetupStep({ password }: IProfileSetupStepProps) {

- + ); } diff --git a/src/components/auth/intro/AiTalkBubble.tsx b/src/components/auth/intro/AiTalkBubble.tsx deleted file mode 100644 index 6af1b13f..00000000 --- a/src/components/auth/intro/AiTalkBubble.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import SparkleIcon from "@/assets/icon/ai/sparkle.svg?react"; - -interface IAiTalkBubbleProps { - text: string; -} - -export default function AiTalkBubble({ text }: IAiTalkBubbleProps) { - return ( -
-
- - {text} -
- -
- ); -} diff --git a/src/components/auth/intro/IntroAIAnalytics.tsx b/src/components/auth/intro/IntroAIAnalytics.tsx deleted file mode 100644 index 07dea6a5..00000000 --- a/src/components/auth/intro/IntroAIAnalytics.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useEffect, useState } from "react"; - -import AiTalkBubble from "./AiTalkBubble"; - -export default function IntroAIAnalytics({ isActive }: { isActive: boolean }) { - const [showBubble, setShowBubble] = useState(false); - - useEffect(() => { - if (isActive) { - const bubbleTimer = setTimeout(() => { - setShowBubble(true); - }, 1400); - - return () => { - clearTimeout(bubbleTimer); - }; - } else { - setShowBubble(false); - } - }, [isActive]); - - const bars = [ - { height: "25%", delay: "0ms", isBlue: false }, - { height: "45%", delay: "100ms", isBlue: false }, - { height: "35%", delay: "200ms", isBlue: false }, - { height: "90%", delay: "300ms", isBlue: true }, - { height: "50%", delay: "400ms", isBlue: false }, - { height: "80%", delay: "550ms", isBlue: false }, - ]; - - return ( -
-
-
-
- - AI 성과 분석 - -

- 복잡한 데이터 분석,{"\n"} - AI가 대신 해드릴게요 -

-

- 광고 성과를 자동으로 분석해서{"\n"} - 중요한 인사이트만 쏙쏙 뽑아드려요 -

-
-
- - -
-
- ); -} diff --git a/src/components/auth/intro/IntroAdManagement.tsx b/src/components/auth/intro/IntroAdManagement.tsx deleted file mode 100644 index 1c16524e..00000000 --- a/src/components/auth/intro/IntroAdManagement.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import LogoGoogleAds from "@/assets/logo/social-logo/circle/googleAds-circle.svg?react"; -import LogoKakao from "@/assets/logo/social-logo/circle/kakao-circle.svg?react"; -import LogoNaver from "@/assets/logo/social-logo/circle/naver-circle.svg?react"; - -const PLATFORMS = [ - { - id: "kakao", - icon: LogoKakao, - bgColor: "bg-social-kakao", - className: "text-social-text-kakao", - size: "w-32 h-32", - }, - { - id: "google", - icon: LogoGoogleAds, - bgColor: "bg-social-google", - className: "", - size: "w-32 h-32", - }, - { - id: "naver", - icon: LogoNaver, - bgColor: "bg-social-naver", - className: "text-white", - size: "w-32 h-32", - }, -]; - -const CAROUSEL_ITEMS = [...PLATFORMS, ...PLATFORMS]; - -export default function IntroAdManagement({ isActive }: { isActive: boolean }) { - return ( -
-
-
-
- - 통합 관리 - -

- 흩어진 광고 성과를{"\n"} - 한곳에서 관리하세요 -

-

- 여러 매체의 광고 데이터를{"\n"} - 실시간으로 모아서 보여드려요 -

-
-
- - -
-
- ); -} diff --git a/src/components/auth/intro/IntroLogo.tsx b/src/components/auth/intro/IntroLogo.tsx deleted file mode 100644 index 0dc3a787..00000000 --- a/src/components/auth/intro/IntroLogo.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import SymbolWhite from "@/assets/logo/service-logo/symbol-white.svg?react"; - -export default function IntroLogo({ isActive }: { isActive: boolean }) { - return ( -
-
-
- -
-

Where you ad

-
-
- ); -} diff --git a/src/components/auth/intro/OnboardingIntro.tsx b/src/components/auth/intro/OnboardingIntro.tsx deleted file mode 100644 index 09a637ca..00000000 --- a/src/components/auth/intro/OnboardingIntro.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useEffect, useState } from "react"; - -import IntroSlide2 from "@/components/auth/intro/IntroAdManagement"; -import IntroSlide3 from "@/components/auth/intro/IntroAIAnalytics"; -import IntroSlide1 from "@/components/auth/intro/IntroLogo"; - -export default function OnboardingIntro() { - const [currentSlide, setCurrentSlide] = useState(0); - - useEffect(() => { - const timer = setInterval(() => { - setCurrentSlide((prev) => (prev + 1) % 3); - }, 5000); - - return () => clearInterval(timer); - }, []); - - const getIndicatorColor = (index: number) => { - const isDarkBackground = currentSlide === 0; - const isActive = index === currentSlide; - - if (isDarkBackground) { - return isActive ? "w-8 bg-white" : "w-2.5 bg-white/40 hover:bg-white/60"; - } else { - return isActive - ? "w-8 bg-logo-1" - : "w-2.5 bg-logo-1/40 hover:bg-logo-1/60"; - } - }; - - return ( -
- - - -
- {[0, 1, 2].map((index) => ( -
-
- ); -} diff --git a/src/components/auth/skeleton/AuthFormSkeleton.tsx b/src/components/auth/skeleton/AuthFormSkeleton.tsx index feb39491..db668fd8 100644 --- a/src/components/auth/skeleton/AuthFormSkeleton.tsx +++ b/src/components/auth/skeleton/AuthFormSkeleton.tsx @@ -1,27 +1,26 @@ +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import { Skeleton } from "@/components/common/skeleton/Skeleton"; export default function AuthFormSkeleton() { return ( -
-
-
- - + +
+ + +
+ +
+
+ +
-
-
- - -
+ +
- -
- -
-
+
); } diff --git a/src/components/auth/skeleton/LoginPageSkeleton.tsx b/src/components/auth/skeleton/LoginPageSkeleton.tsx index 69ccffd0..554ec7d9 100644 --- a/src/components/auth/skeleton/LoginPageSkeleton.tsx +++ b/src/components/auth/skeleton/LoginPageSkeleton.tsx @@ -1,3 +1,4 @@ +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import { Skeleton, SkeletonCircle, @@ -5,7 +6,7 @@ import { export default function LoginPageSkeleton() { return ( -
+
@@ -46,6 +47,6 @@ export default function LoginPageSkeleton() {
-
+ ); } diff --git a/src/components/auth/skeleton/SignupEmailStepSkeleton.tsx b/src/components/auth/skeleton/SignupEmailStepSkeleton.tsx index aabdf408..e3cbdc80 100644 --- a/src/components/auth/skeleton/SignupEmailStepSkeleton.tsx +++ b/src/components/auth/skeleton/SignupEmailStepSkeleton.tsx @@ -1,31 +1,30 @@ +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import { Skeleton } from "@/components/common/skeleton/Skeleton"; export default function SignupEmailStepSkeleton() { return ( -
-
-
- - -
- -
-
-
- -
- -
+ +
+ + +
-
+
+
+
+ +
-
- -
+
+ +
+ +
+
-
+
); } diff --git a/src/components/auth/skeleton/SignupPageSkeleton.tsx b/src/components/auth/skeleton/SignupPageSkeleton.tsx index 034c7f21..f8319cf0 100644 --- a/src/components/auth/skeleton/SignupPageSkeleton.tsx +++ b/src/components/auth/skeleton/SignupPageSkeleton.tsx @@ -1,8 +1,9 @@ +import AuthFormShell from "@/components/auth/common/AuthFormShell"; import { Skeleton } from "@/components/common/skeleton/Skeleton"; export default function SignupPageSkeleton() { return ( -
+
@@ -10,9 +11,9 @@ export default function SignupPageSkeleton() {
-
+
-
+
); } diff --git a/src/components/landing/LandingFooter.tsx b/src/components/landing/LandingFooter.tsx index a958f611..8de23428 100644 --- a/src/components/landing/LandingFooter.tsx +++ b/src/components/landing/LandingFooter.tsx @@ -1,19 +1,19 @@ export default function LandingFooter() { return ( -