코스모스 (Course-More-Us) - 3일차 개발 기록
개발 기간: 2025년 06월 17일
개발자: yangsuyoung
프로젝트: 데이트 코스 추천 서비스 "코스모스"
📋 개발 개요
3일차는 관리자 페이지 리팩터링, UI/UX 개선, 그리고 소셜 로그인 공통 로직 리팩터링에 집중했습니다. 기존 코드의 일관성을 높이고 재사용 가능한 컴포넌트 체계를 구축하여 유지보수성을 크게 향상시켰습니다.
🚀 주요 구현 사항
1. 관리자 페이지 리팩터링 ✅
공통 컴포넌트 적극 활용
기존의 분산된 UI 요소들을 표준화된 공통 컴포넌트로 통일했습니다.
수정된 파일들:
app/routes/admin.users._index.tsx
app/routes/admin.places._index.tsx
app/routes/admin.places.new.tsx
적용된 개선사항:
- Button 컴포넌트 적용: 일관된 버튼 스타일 및 상호작용
- ROUTES 상수 활용: 하드코딩된 경로를 상수로 대체
- formatDate 유틸리티 적용: 날짜 표시 일관성 확보
- Rating null 체크 추가: 안전한 데이터 렌더링
// 수정 전
<button className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
수정
</button>
// 수정 후
<Button variant="primary" size="sm">
수정
</Button>
2. 메인 페이지 로그인 버튼 스타일 개선 ✅
문제 상황
메인 페이지의 보라색-핑크 그래디언트 배경에서 기존 로그인 버튼이 잘 보이지 않는 가시성 문제가 발생했습니다.
해결 방안
Button 컴포넌트 확장:
white
variant 추가:bg-white text-purple-600 hover:bg-gray-100
purple
variant 추가: 추후 확장성을 위한 준비- 메인 페이지에서
variant="white"
적용
// app/components/ui/Button.tsx에 추가
const variants = {
// ... 기존 variants
white: 'bg-white text-purple-600 hover:bg-gray-100 focus:ring-purple-500 shadow-md',
purple: 'bg-purple-600 text-white hover:bg-purple-700 focus:ring-purple-500'
}
3. 인증 페이지 배경 스타일 통일화 ✅
브랜드 일관성 확보
메인 페이지와 동일한 그래디언트 배경을 로그인/회원가입 페이지에도 적용하여 브랜드 일관성을 확보했습니다.
적용된 스타일:
- 통일된 배경:
bg-gradient-to-br from-purple-400 via-pink-500 to-red-500
- 반투명 카드:
bg-white/95 backdrop-blur-sm rounded-2xl p-8 shadow-2xl
- 텍스트 색상: 흰색 기반으로 변경하여 가독성 향상
4. 로그인 폼 UI 세부 개선 ✅
사용자 피드백 반영
실제 사용자 스크린샷을 바탕으로 세부적인 UI 문제들을 해결했습니다.
개선된 요소들:
- Input 필드 테두리:
border-gray-300 hover:border-gray-400
추가 - Focus 상태:
focus:border-purple-300
명확한 포커스 표시 - 비활성화 버튼:
bg-gray-500 border border-gray-600 text-gray-300
으로 가시성 향상
5. 소셜 로그인 공통 로직 리팩터링 🔥
문제 분석
기존 소셜 로그인 구현에서 발견된 문제점들:
- 중복된 핸들러 함수:
handleKakaoLogin
,handleGoogleLogin
등 반복적인 코드 - 개별 상태 관리: 각 제공자별로 별도의 로딩 상태 관리
- 확장성 부족: 새로운 소셜 제공자 추가 시 많은 코드 중복
- 유지보수 어려움: 로직 변경 시 여러 곳 수정 필요
해결 방안: 공통 훅 및 컴포넌트 구조
1) useSocialLogin 훅 생성
파일: app/hooks/useSocialLogin.ts
export type SocialProvider = 'google' | 'kakao' | 'apple' | 'facebook';
interface UseSocialLoginOptions {
flow?: 'login' | 'signup';
onSuccess?: () => void;
onError?: (error: string) => void;
}
export function useSocialLogin(options: UseSocialLoginOptions = {}) {
const [loadingStates, setLoadingStates] = useState<Record<SocialProvider, boolean>>({
google: false, kakao: false, apple: false, facebook: false
});
const handleSocialLogin = async (provider: SocialProvider) => {
// 통합된 소셜 로그인 로직
// 중복 클릭 방지, 에러 처리, 로딩 상태 관리 등
};
return {
loadingStates,
handleSocialLogin,
isLoading: (provider: SocialProvider) => loadingStates[provider]
};
}
주요 특징:
- 타입 안전성: TypeScript로 모든 제공자 타입 정의
- 통합 상태 관리: 모든 소셜 제공자의 로딩 상태를 하나의 객체로 관리
- 유연한 설정: flow, onSuccess, onError 콜백 지원
- 에러 처리: 제공자별 한국어 에러 메시지 제공
2) SocialLoginButton 컴포넌트 생성
파일: app/components/common/SocialLoginButton.tsx
interface SocialLoginButtonProps {
provider: SocialProvider;
isLoading: boolean;
onClick: () => void;
disabled?: boolean;
className?: string;
}
// 제공자별 브랜드 설정
const socialProviderConfig = {
google: {
name: '구글',
className: 'bg-white border border-gray-300 text-gray-700',
icon: <GoogleIcon />
},
kakao: {
name: '카카오',
className: 'bg-yellow-400 text-gray-900',
icon: <KakaoIcon />
}
// ... Apple, Facebook 등
}
주요 특징:
- 브랜드 일관성: 각 제공자의 공식 색상 및 아이콘 사용
- 자동 텍스트: mode에 따라 "로그인" vs "시작하기" 자동 변경
- 완전 재사용: 모든 페이지에서 동일한 컴포넌트 사용
- 확장성: 새 제공자 추가 시 설정만 업데이트
3) 기존 페이지 리팩터링
로그인/회원가입 페이지 변화:
// 수정 전 (60+ 줄의 중복 코드)
const [isKakaoLoading, setIsKakaoLoading] = useState(false);
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
const handleKakaoLogin = async () => {
if (isKakaoLoading) return;
setIsKakaoLoading(true);
try {
const supabase = createSupabaseClient();
const { error } = await supabase.auth.signInWithOAuth({
provider: 'kakao',
options: { redirectTo: `${window.location.origin}/auth/callback?flow=login` }
});
// ... 에러 처리
} catch (error) {
// ... 예외 처리
}
};
// 수정 후 (3줄로 단순화)
const { handleSocialLogin, isLoading: isSocialLoading } = useSocialLogin({
flow: 'login'
});
// JSX도 대폭 단순화
<SocialLoginButton
provider="kakao"
isLoading={isSocialLoading('kakao')}
onClick={() => handleSocialLogin('kakao')}
mode="login"
/>
리팩터링 결과
정량적 개선:
- 중복 코드 제거: 60+ 줄의 중복 코드 제거
- 파일 수: 4개 파일 추가, 기존 2개 파일 대폭 수정
- 코드 라인: +244 추가, -211 삭제 (순증 +33줄로 기능 확장)
정성적 개선:
- 확장성: 새 소셜 제공자 추가 시 설정만 변경
- 일관성: 모든 페이지에서 동일한 UI/UX
- 타입 안전성: TypeScript 기반 완전한 타입 체크
- 유지보수성: 로직 변경 시 한 곳만 수정
6. 컴포넌트 활용 철학 확립 ✅
새로운 개발 원칙
사용자 요청에 따라 향후 모든 컴포넌트 개발에 적용할 원칙들을 확립했습니다:
- 기존 컴포넌트 우선 활용
- Button, Input, Modal 등 검증된 컴포넌트 사용
- 새 컴포넌트 생성 전 기존 컴포넌트 확장 검토
- 확장 가능한 설계
- variant, size 등의 props로 다양한 스타일 지원
- 옵션 추가 시 기존 사용처에 영향 없도록 설계
- 일관된 디자인 시스템
- 색상, 타이포그래피, 간격 등 디자인 토큰 활용
- 브랜드 가이드라인 준수
- 시스템 통합 활용
- ROUTES 상수 활용
- 유틸리티 함수 적극 사용
- 타입 정의 재사용
📊 기술적 개선 사항
1. TypeScript 타입 안전성 강화
SocialProvider 타입 정의
export type SocialProvider = 'google' | 'kakao' | 'apple' | 'facebook';
- 모든 소셜 제공자를 타입으로 제한
- 컴파일 타임에 오타 및 잘못된 제공자 사용 방지
인터페이스 표준화
interface UseSocialLoginOptions {
flow?: 'login' | 'signup';
onSuccess?: () => void;
onError?: (error: string) => void;
}
2. 상태 관리 최적화
통합된 로딩 상태
const [loadingStates, setLoadingStates] = useState<Record<SocialProvider, boolean>>({
google: false, kakao: false, apple: false, facebook: false
});
- 개별 useState 대신 객체 기반 통합 관리
- 메모리 효율성 및 일관성 향상
3. 에러 처리 표준화
제공자별 한국어 메시지
const providerNames = {
google: '구글', kakao: '카카오',
apple: 'Apple', facebook: '페이스북'
};
const errorMessage = `${providerNames[provider]} 로그인 중 오류가 발생했습니다: ${error.message}`;
4. 컴포넌트 설계 패턴
Composition Pattern 적용
// 설정과 컴포넌트 분리
const socialProviderConfig = { /* 설정 */ };
export default function SocialLoginButton({ provider, ...props }) {
const config = socialProviderConfig[provider];
// 설정 기반 렌더링
}
🎯 Git 커밋 히스토리
관리자 페이지 리팩터링
refactor: 남은 관리자 페이지들 리팩터링 완료
- Button 컴포넌트 및 ROUTES 상수 적용
- rating null 체크 추가
UI/UX 개선
fix: 메인 페이지 로그인 버튼 스타일 개선
design: 로그인/회원가입 페이지 배경 스타일 통일
fix: 로그인 폼 UI 개선
fix: 로그인 버튼 비활성화 상태 색상 개선
Google 로그인 구현
feat: Google 로그인 기능 추가
- 로그인/회원가입 페이지에 Google OAuth 버튼 추가
- 카카오와 동일한 플로우로 구현
소셜 로그인 리팩터링
refactor: 소셜 로그인 공통 로직 리팩터링
- useSocialLogin 훅 및 SocialLoginButton 컴포넌트 생성으로 중복 코드 제거
- 모든 소셜 제공자 지원
💡 배운 점 및 통찰
1. 리팩터링의 가치
문제: 중복된 코드로 인한 유지보수 어려움
해결: 공통 로직 추출 및 재사용 가능한 컴포넌트 설계
결과: 60+ 줄의 중복 코드 제거, 확장성 대폭 향상
2. 점진적 개선의 중요성
- 1단계: 기본 기능 구현 (카카오 로그인)
- 2단계: 기능 확장 (구글 로그인 추가)
- 3단계: 리팩터링 (공통 로직 추출)
- 결과: 안정적이고 확장 가능한 구조
3. 사용자 피드백의 중요성
실제 사용자의 스크린샷과 피드백을 통해:
- 로그인 버튼 가시성 문제 발견
- Input 필드 구분선 부재 인식
- 비활성화 상태 색상 개선 필요성 확인
4. 타입 안전성과 개발 효율성
TypeScript의 적극 활용으로:
- 컴파일 타임 오류 방지
- IDE 자동완성 지원 향상
- 리팩터링 시 안전성 보장
🔮 다음 단계 계획
1. C 단계: 고급 추천 시스템 구현
- 거리 기반 추천 로직: 사용자 위치 기반 데이트 코스 추천
- 카카오 맵 API 연동: 실시간 위치 정보 및 경로 안내
- 개인화 추천 알고리즘: 사용자 선호도 학습 기반 추천
2. 추가 소셜 로그인 확장
이제 새로운 소셜 로그인 추가가 매우 쉬워졌습니다:
- Apple 로그인: 설정 활성화만으로 즉시 사용 가능
- Facebook 로그인: 동일한 구조로 빠른 추가 가능
- 네이버 로그인: 한국 사용자를 위한 추가 옵션
3. 성능 최적화
- 컴포넌트 지연 로딩: 소셜 로그인 버튼 lazy loading
- 상태 최적화: React.memo 및 useMemo 적용
- 번들 최적화: 소셜 제공자별 코드 분할
4. 테스트 코드 작성
- 단위 테스트: useSocialLogin 훅 테스트
- 컴포넌트 테스트: SocialLoginButton 렌더링 테스트
- 통합 테스트: 전체 로그인 플로우 테스트
📈 개발 통계
코드 변경 통계
- 생성된 파일: 4개
app/hooks/useSocialLogin.ts
app/components/common/SocialLoginButton.tsx
app/components/common/index.ts
app/hooks/index.ts
- 수정된 파일: 2개
app/routes/auth.login.tsx
app/routes/auth.signup.tsx
- 총 라인 수: +244 추가, -211 삭제
컴포넌트 재사용성 지표
- 재사용되는 컴포넌트: SocialLoginButton (4개소에서 사용)
- 재사용되는 훅: useSocialLogin (2개 페이지에서 사용)
- 중복 제거율: 약 85% (60줄 → 9줄)
타입 안전성 개선
- 새로운 타입 정의: 2개 (SocialProvider, UseSocialLoginOptions)
- 타입 체크 추가: 모든 소셜 제공자 관련 함수에 타입 적용
- 런타임 에러 방지: 컴파일 타임 타입 체크로 사전 방지
🏆 핵심 성과
1. 코드 품질 향상
- 중복 제거: 60+ 줄의 중복 코드 완전 제거
- 재사용성: 모든 소셜 로그인이 공통 컴포넌트 사용
- 일관성: 통일된 UI/UX 및 에러 처리
2. 개발 효율성 증대
- 새 제공자 추가: 5분 내 새로운 소셜 로그인 추가 가능
- 유지보수: 로직 변경 시 한 곳만 수정
- 확장성: 미래 요구사항에 유연하게 대응
3. 사용자 경험 개선
- 일관된 브랜딩: 모든 페이지에서 통일된 디자인
- 명확한 피드백: 로딩 상태 및 에러 메시지 개선
- 접근성: 색상 대비 및 포커스 상태 개선
개발 완료: 소셜 로그인 공통 로직 리팩터링 및 UI/UX 개선 완료
다음 목표: C 단계 고급 추천 시스템 구현 준비
'🧑💻 바이브 코딩' 카테고리의 다른 글
[바이브 코딩 #6] 데이트 코스 추천 서비스 개발기 (0) | 2025.06.29 |
---|---|
[바이브 코딩 #5] 데이트 코스 추천 서비스 개발기 (1) | 2025.06.28 |
[바이브 코딩 #4] 데이트 코스 추천 서비스 개발기 (4) | 2025.06.27 |
[바이브 코딩 #2] 데이트 코스 추천 서비스 개발기 (0) | 2025.06.23 |
[바이브 코딩 #1] 데이트 코스 추천 서비스 개발기 (6) | 2025.06.15 |