Skip to content

Commit 7a0db8b

Browse files
authored
Merge pull request #17 from prgrms-aibe-devcourse/chore/1-header-nav-auth-ui-fix
[Chore] 헤더 nav바 정리 및 로그인 회원가입 페이지 깨진 디자인 수정
2 parents 7170877 + 14c21a2 commit 7a0db8b

8 files changed

Lines changed: 1091 additions & 222 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ Thumbs.db
3434
.vscode/*
3535
!.vscode/extensions.json
3636
!.vscode/settings.json
37+
38+
# Git fetch metadata accidentally created in the workspace root
39+
/FETCH_HEAD

src/app/components/AuthLayout.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function AuthLayout() {
3636
<header className="relative z-10 mx-auto flex w-[min(1180px,calc(100vw-32px))] items-center justify-between py-5">
3737
<Link
3838
to="/"
39-
className="flex items-center gap-3 no-underline"
39+
className="codedock-brand-link flex items-center gap-3 no-underline"
4040
style={{
4141
color: "var(--white)",
4242
fontSize: "24px",
@@ -46,8 +46,7 @@ export function AuthLayout() {
4646
aria-label="랜딩으로 돌아가기"
4747
>
4848
<CoffeeLogo
49-
className="h-14 w-14 flex-shrink-0"
50-
style={{ filter: `drop-shadow(0 0 14px ${colors.primary}, 0.3))` }}
49+
className="codedock-header-logo h-14 w-14 flex-shrink-0"
5150
/>
5251
<CodeDockWordmark accentColor={colors.primaryHex} />
5352
</Link>

src/app/components/CoffeeLogo.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export function CoffeeLogo({ className = "", style, alive = false, eyeX, eyeY, m
1414
const isCtaMood = mood === "cta";
1515
const isRiskMood = mood === "risk";
1616
const isSuccessMood = mood === "success";
17+
const mouthPath = isRiskMood ? "M166 202c7-8 17-8 24 0" : "M174 194c5 6 11 6 16 0";
1718
const floatTransition = { duration: 4.2, repeat: Infinity, ease: "easeInOut" } as const;
1819
const tinyTransition = { duration: 2.8, repeat: Infinity, ease: "easeInOut" } as const;
1920
const activityDuration = 11.5;
@@ -109,6 +110,7 @@ export function CoffeeLogo({ className = "", style, alive = false, eyeX, eyeY, m
109110
style={{ transformBox: "fill-box", transformOrigin: "50% 46%" }}
110111
>
111112
<motion.g
113+
className="codedock-logo__ear codedock-logo__ear--left"
112114
animate={alive ? leftEarAnimation : undefined}
113115
transition={earTransition}
114116
style={{ transformBox: "fill-box", transformOrigin: "42% 100%" }}
@@ -124,6 +126,7 @@ export function CoffeeLogo({ className = "", style, alive = false, eyeX, eyeY, m
124126
</motion.g>
125127

126128
<motion.g
129+
className="codedock-logo__ear codedock-logo__ear--right"
127130
animate={alive ? rightEarAnimation : undefined}
128131
transition={{ ...earTransition, delay: isRiskMood ? 0.08 : 0.14 }}
129132
style={{ transformBox: "fill-box", transformOrigin: "58% 100%" }}
@@ -145,8 +148,9 @@ export function CoffeeLogo({ className = "", style, alive = false, eyeX, eyeY, m
145148
strokeWidth="7"
146149
/>
147150

148-
<motion.g style={alive ? { x: eyeX, y: eyeY } : undefined}>
151+
<motion.g className="codedock-logo__eyes" style={alive ? { x: eyeX, y: eyeY } : undefined}>
149152
<motion.g
153+
className="codedock-logo__eye-pair"
150154
animate={alive ? { scaleY: [1, 1, 0.18, 1, 1] } : undefined}
151155
transition={{
152156
duration: isRiskMood ? 2.7 : 4.8,
@@ -161,14 +165,33 @@ export function CoffeeLogo({ className = "", style, alive = false, eyeX, eyeY, m
161165
</motion.g>
162166
</motion.g>
163167

168+
{isRiskMood && (
169+
<motion.g
170+
className="codedock-logo__brows"
171+
animate={alive ? { y: [0, -1, 0], opacity: [0.82, 1, 0.82] } : undefined}
172+
transition={{ duration: 1.2, repeat: Infinity, ease: "easeInOut" }}
173+
>
174+
<path d="M141 157l23 7" stroke="#7A1420" strokeWidth="6" strokeLinecap="round" />
175+
<path d="M213 157l-23 7" stroke="#7A1420" strokeWidth="6" strokeLinecap="round" />
176+
</motion.g>
177+
)}
178+
164179
<motion.path
165-
d="M174 194c5 6 11 6 16 0"
180+
className="codedock-logo__mouth"
181+
d={mouthPath}
166182
fill="none"
167-
stroke="#0B1628"
183+
stroke={isRiskMood ? "#7A1420" : "#0B1628"}
168184
strokeWidth="6"
169185
strokeLinecap="round"
170-
animate={alive ? { y: [0, isCtaMood ? 1.8 : 1, 0], scaleX: isCtaMood ? [1, 1.28, 1] : [1, 1.04, 1] } : undefined}
171-
transition={isCtaMood ? { duration: 2.15, repeat: Infinity, ease: "easeInOut" } : tinyTransition}
186+
animate={
187+
alive
188+
? {
189+
y: isRiskMood ? [0, -0.8, 0] : [0, isCtaMood ? 1.8 : 1, 0],
190+
scaleX: isRiskMood ? [1, 1.08, 1] : isCtaMood ? [1, 1.28, 1] : [1, 1.04, 1],
191+
}
192+
: undefined
193+
}
194+
transition={isRiskMood ? { duration: 1.15, repeat: Infinity, ease: "easeInOut" } : isCtaMood ? { duration: 2.15, repeat: Infinity, ease: "easeInOut" } : tinyTransition}
172195
style={{ transformBox: "fill-box", transformOrigin: "50% 50%" }}
173196
/>
174197

@@ -274,6 +297,7 @@ export function CoffeeLogo({ className = "", style, alive = false, eyeX, eyeY, m
274297
</motion.g>
275298

276299
<motion.g
300+
className="codedock-logo__mug"
277301
animate={
278302
alive
279303
? isCtaMood

src/app/components/Layout.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,9 @@ import { useTheme } from "../contexts/ThemeContext";
1111

1212
const navItems = [
1313
{ path: "/workspace", label: "Dashboard" },
14-
{ path: "/project", label: "Project" },
1514
{ path: "/prs", label: "PRs" },
1615
{ path: "/issues", label: "Issues" },
1716
{ path: "/chat", label: "Workspace" },
18-
{ path: "/api-spec", label: "API" },
19-
{ path: "/erd", label: "ERD" },
20-
{ path: "/docs", label: "Docs" },
2117
];
2218

2319
const currentUser = {
@@ -84,7 +80,7 @@ export function Layout() {
8480
<div className="relative mx-auto flex w-[min(1400px,100%)] items-center justify-between gap-4">
8581
<Link
8682
to="/"
87-
className="flex items-center gap-3 no-underline transition-all duration-300"
83+
className="codedock-brand-link flex items-center gap-3 no-underline transition-all duration-300"
8884
style={{
8985
color: "var(--white)",
9086
fontSize: "26px",
@@ -97,8 +93,7 @@ export function Layout() {
9793
aria-label="CodeDock 랜딩으로 이동"
9894
>
9995
<CoffeeLogo
100-
className="h-16 w-16 flex-shrink-0"
101-
style={{ filter: `drop-shadow(0 0 14px ${colors.primary}, 0.3))` }}
96+
className="codedock-header-logo h-16 w-16 flex-shrink-0"
10297
/>
10398
<CodeDockWordmark accentColor={colors.primaryHex} />
10499
</Link>

src/app/components/PublicLayout.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export function PublicLayout() {
5454
<div className="mx-auto flex w-[min(1180px,100%)] items-center justify-between gap-3">
5555
<Link
5656
to="/"
57-
className="flex min-w-0 items-center gap-2 no-underline sm:gap-3"
57+
className="codedock-brand-link flex min-w-0 items-center gap-2 no-underline sm:gap-3"
5858
style={{
5959
color: "var(--white)",
6060
fontSize: "26px",
@@ -64,8 +64,7 @@ export function PublicLayout() {
6464
aria-label="CodeDock 랜딩"
6565
>
6666
<CoffeeLogo
67-
className="h-12 w-12 flex-shrink-0 sm:h-16 sm:w-16"
68-
style={{ filter: `drop-shadow(0 0 14px ${colors.primary}, 0.3))` }}
67+
className="codedock-header-logo h-12 w-12 flex-shrink-0 sm:h-16 sm:w-16"
6968
/>
7069
<CodeDockWordmark accentColor={colors.primaryHex} className="hidden sm:inline-flex" />
7170
</Link>

src/app/pages/LoginPage.tsx

Lines changed: 93 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ const loginDemoMessagesKo = [
2424
{ speaker: "팀 채팅", text: "리뷰 기준 문서에 반영할게요.", tone: "#CFF8FF" },
2525
];
2626

27+
const loginWelcomeMessagesKo = [
28+
"오늘의 PR 산책은 제가 먼저 다녀왔어요.\n위험한 변경은 줄 세워두고, 문서는 따뜻하게 데워둘게요.",
29+
"코드 바다에 오신 걸 환영합니다.\n리뷰 파도는 CodeDock이 잔잔하게 만들어둘게요.",
30+
"커피는 없지만 컨텍스트는 준비됐어요.\nPR, 문서, 팀 대화까지 제가 한 화면에 착 붙여둘게요.",
31+
"버그는 숨바꼭질을 좋아하죠.\n오늘은 CodeDock이 술래가 되어 먼저 찾아볼게요.",
32+
"리뷰 대기열이 길어도 괜찮아요.\n제가 중요한 파일부터 콕 집어서 길을 열어둘게요.",
33+
];
34+
35+
const loginWelcomeMessagesEn = [
36+
"I already took today's PRs for a walk.\nI'll line up the risky changes and keep the docs warm.",
37+
"Welcome aboard the code harbor.\nCodeDock will keep the review waves pleasantly calm.",
38+
"No coffee here, but the context is ready.\nI'll keep PRs, docs, and team chat neatly docked.",
39+
"Bugs love hide-and-seek.\nToday, CodeDock will count first and find them faster.",
40+
"A long review queue is fine.\nI'll point out the important files and clear the runway.",
41+
];
42+
2743
const loginTabDemosKo = [
2844
{
2945
icon: Sparkles,
@@ -76,7 +92,9 @@ export function LoginPage() {
7692
const [showPassword, setShowPassword] = useState(false);
7793
const [rememberMe, setRememberMe] = useState(true);
7894
const [message, setMessage] = useState("");
95+
const [isLoginButtonHovering, setIsLoginButtonHovering] = useState(false);
7996
const [activeDemoIndex, setActiveDemoIndex] = useState(0);
97+
const [welcomeMessageIndex] = useState(() => Math.floor(Math.random() * loginWelcomeMessagesKo.length));
8098
const trimmedEmail = email.trim();
8199
const emailAtIndex = trimmedEmail.indexOf("@");
82100
const shouldRevealPassword = emailAtIndex > 0 && trimmedEmail.slice(emailAtIndex + 1).trim().length > 0;
@@ -133,14 +151,15 @@ export function LoginPage() {
133151
},
134152
]
135153
: loginTabDemosKo;
154+
const loginWelcomeMessage = isEnglish ? loginWelcomeMessagesEn[welcomeMessageIndex] : loginWelcomeMessagesKo[welcomeMessageIndex];
136155
const loginCopy = {
137156
access: isEnglish ? "CodeDock Access" : "CodeDock 접속",
138157
login: isEnglish ? "Login" : "로그인",
139158
accountTitle: isEnglish ? "Access your account" : "계정에 접속하기",
140-
emailPrompt: isEnglish ? "Tell me the email to sign in with." : "접속할 이메일을 알려주세요.",
159+
emailPrompt: isEnglish ? "Which email do you sign in with?" : "로그인할 이메일을 알려주세요.",
141160
emailLabel: isEnglish ? "Email" : "이메일",
142161
emailPlaceholder: "name@company.com",
143-
passwordPrompt: isEnglish ? "Great. Enter your password too." : "좋아요. 비밀번호도 입력해주세요.",
162+
passwordPrompt: isEnglish ? "Got it! Now enter your password." : "이메일 확인했어요! 비밀번호를 입력해주세요.",
144163
passwordLabel: isEnglish ? "Password" : "비밀번호",
145164
passwordPlaceholder: isEnglish ? "Enter password" : "비밀번호 입력",
146165
remember: isEnglish ? "Keep me signed in" : "로그인 상태 유지",
@@ -154,11 +173,8 @@ export function LoginPage() {
154173
hidePassword: isEnglish ? "Hide password" : "비밀번호 숨기기",
155174
miniBrand: "CodeDock",
156175
liveWorkspace: isEnglish ? "LIVE WORKSPACE" : "실시간 작업 공간",
157-
welcomeEyebrow: isEnglish ? "Welcome aboard" : "환영합니다",
158-
welcomeTitle: isEnglish ? "Welcome to the joyful world of coding." : "즐거운 코딩의 세계로 오신 걸 환영합니다.",
159-
welcomeBody: isEnglish
160-
? "CodeDock keeps reviews, documents, and team conversations flowing while you build."
161-
: "리뷰와 문서, 팀 대화는 CodeDock이 정리할게요.\n오늘도 편하게 개발을 시작하세요.",
176+
welcomeTitle: "Hello CodeDock!",
177+
welcomeBody: loginWelcomeMessage,
162178
};
163179
const activeDemo = loginTabDemos[activeDemoIndex];
164180
const demoCount = loginTabDemos.length;
@@ -248,11 +264,8 @@ export function LoginPage() {
248264
exit={{ opacity: 0, y: -8, height: 0, filter: "blur(8px)" }}
249265
transition={{ duration: 0.42, ease: "easeOut" }}
250266
>
251-
<p className="m-0 text-sm font-black tracking-tight" style={{ color: colors.primaryHex }}>
252-
{loginCopy.welcomeEyebrow}
253-
</p>
254267
<h2
255-
className="m-0 mt-2 leading-tight tracking-tight"
268+
className="m-0 leading-tight tracking-tight"
256269
style={{
257270
color: "var(--white)",
258271
fontSize: "clamp(30px, 4.4vw, 54px)",
@@ -727,6 +740,10 @@ export function LoginPage() {
727740

728741
<button
729742
type="submit"
743+
onMouseEnter={() => setIsLoginButtonHovering(true)}
744+
onMouseLeave={() => setIsLoginButtonHovering(false)}
745+
onFocus={() => setIsLoginButtonHovering(true)}
746+
onBlur={() => setIsLoginButtonHovering(false)}
730747
className="mt-1 flex h-14 w-full items-center justify-center gap-2 rounded-2xl border-0 px-6 tracking-tight transition hover:scale-[1.01]"
731748
style={{
732749
background: `linear-gradient(135deg, ${colors.primaryHex}, ${colors.secondary})`,
@@ -736,7 +753,7 @@ export function LoginPage() {
736753
fontWeight: 950,
737754
}}
738755
>
739-
{loginCopy.submit}
756+
<LoginSubmitLabel isHovering={isLoginButtonHovering} defaultLabel={loginCopy.submit} />
740757
<ArrowRight size={19} strokeWidth={2.5} />
741758
</button>
742759
</motion.div>
@@ -805,6 +822,70 @@ interface LoginChatPromptProps {
805822
typingDelay?: number;
806823
}
807824

825+
function LoginSubmitLabel({ isHovering, defaultLabel }: { isHovering: boolean; defaultLabel: string }) {
826+
const targetText = "Hello CodeDock!";
827+
const [visibleCount, setVisibleCount] = useState(0);
828+
const visibleText = targetText.slice(0, visibleCount);
829+
830+
useEffect(() => {
831+
if (!isHovering) {
832+
setVisibleCount(0);
833+
return;
834+
}
835+
836+
setVisibleCount(0);
837+
}, [isHovering]);
838+
839+
useEffect(() => {
840+
if (!isHovering || visibleCount >= targetText.length) {
841+
return;
842+
}
843+
844+
const typingTimer = window.setTimeout(() => {
845+
setVisibleCount((count) => Math.min(targetText.length, count + 1));
846+
}, visibleCount === 0 ? 120 : 38);
847+
848+
return () => window.clearTimeout(typingTimer);
849+
}, [isHovering, targetText.length, visibleCount]);
850+
851+
return (
852+
<span className="inline-flex min-w-[150px] items-center justify-center text-center">
853+
<AnimatePresence mode="wait" initial={false}>
854+
{isHovering ? (
855+
<motion.span
856+
key="login-code-typing"
857+
className="inline-flex items-center justify-center font-black"
858+
initial={{ opacity: 0, y: 5, filter: "blur(4px)" }}
859+
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
860+
exit={{ opacity: 0, y: -5, filter: "blur(4px)" }}
861+
transition={{ duration: 0.16, ease: "easeOut" }}
862+
style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" }}
863+
>
864+
<span>{visibleText}</span>
865+
<motion.span
866+
className="ml-1 inline-block h-5 w-1 rounded-full"
867+
style={{ background: "#021014" }}
868+
animate={{ opacity: [0, 1, 0] }}
869+
transition={{ duration: 0.62, repeat: Infinity, ease: "easeInOut" }}
870+
/>
871+
</motion.span>
872+
) : (
873+
<motion.span
874+
key="login-default-label"
875+
className="inline-block"
876+
initial={{ opacity: 0, y: 5 }}
877+
animate={{ opacity: 1, y: 0 }}
878+
exit={{ opacity: 0, y: -5 }}
879+
transition={{ duration: 0.16, ease: "easeOut" }}
880+
>
881+
{defaultLabel}
882+
</motion.span>
883+
)}
884+
</AnimatePresence>
885+
</span>
886+
);
887+
}
888+
808889
function LoginChatPrompt({ text, delay = 0, typingDelay = 620 }: LoginChatPromptProps) {
809890
const { colors } = useTheme();
810891
const [isTyping, setIsTyping] = useState(true);

0 commit comments

Comments
 (0)