Skip to content

Commit 14c21a2

Browse files
committed
design: 회원가입, 로그인 깨지는 부분 픽스 및 ui 개선
1 parent 8d1de8e commit 14c21a2

8 files changed

Lines changed: 860 additions & 214 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: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ export function AuthLayout() {
4747
>
4848
<CoffeeLogo
4949
className="codedock-header-logo h-14 w-14 flex-shrink-0"
50-
style={{ filter: `drop-shadow(0 0 14px ${colors.primary}, 0.3))` }}
5150
/>
5251
<CodeDockWordmark accentColor={colors.primaryHex} />
5352
</Link>

src/app/components/CoffeeLogo.tsx

Lines changed: 23 additions & 4 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;
@@ -164,15 +165,33 @@ export function CoffeeLogo({ className = "", style, alive = false, eyeX, eyeY, m
164165
</motion.g>
165166
</motion.g>
166167

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+
167179
<motion.path
168180
className="codedock-logo__mouth"
169-
d="M174 194c5 6 11 6 16 0"
181+
d={mouthPath}
170182
fill="none"
171-
stroke="#0B1628"
183+
stroke={isRiskMood ? "#7A1420" : "#0B1628"}
172184
strokeWidth="6"
173185
strokeLinecap="round"
174-
animate={alive ? { y: [0, isCtaMood ? 1.8 : 1, 0], scaleX: isCtaMood ? [1, 1.28, 1] : [1, 1.04, 1] } : undefined}
175-
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}
176195
style={{ transformBox: "fill-box", transformOrigin: "50% 50%" }}
177196
/>
178197

src/app/components/Layout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ export function Layout() {
9494
>
9595
<CoffeeLogo
9696
className="codedock-header-logo h-16 w-16 flex-shrink-0"
97-
style={{ filter: `drop-shadow(0 0 14px ${colors.primary}, 0.3))` }}
9897
/>
9998
<CodeDockWordmark accentColor={colors.primaryHex} />
10099
</Link>

src/app/components/PublicLayout.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ export function PublicLayout() {
6565
>
6666
<CoffeeLogo
6767
className="codedock-header-logo 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))` }}
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)