Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
61e35ee
feat: 북마크 및 완주한 경로 검색 기능 추가
Monixc Sep 11, 2025
9bfe2e4
feat: 북마크 및 완주한 경로 데이터 로드 기능 추가
Monixc Sep 11, 2025
a343abd
feat: Detail 화면 탭 네비게이션 수정
Monixc Sep 11, 2025
87e3b10
Merge branch 'main' of https://github.com/prgrms-fullstack-devcourse/…
Monixc Sep 12, 2025
11e19a7
fix: 네비게이션 타입 및 파라미터 수정|
Monixc Sep 12, 2025
6bb0fc2
feat: 경로 상세 페이지 컴포넌트 추가
Monixc Sep 12, 2025
5f7a0f9
feat: 코스 상태 및 액션 추가
Monixc Sep 12, 2025
f852faa
feat: 코스 검증 및 토폴로지 API 최적화
Monixc Sep 12, 2025
f87bc64
feat: 날짜 및 거리, 시간, 페이스 포맷터 추가
Monixc Sep 12, 2025
49a04fa
feat: Long Press 훅 추가
Monixc Sep 12, 2025
5f270e4
feat: 이미지 로딩 훅 추가
Monixc Sep 12, 2025
3d63a63
feat: 공유 기능을 위한 useShare 훅 추가
Monixc Sep 12, 2025
aada0f1
feat: 코스 관련 기능 개선 및 통계 관리 최적화
Monixc Sep 12, 2025
8043018
feat: 코스 데이터 및 위치 관련 기능 개선
Monixc Sep 13, 2025
2d63309
fix: Encountered two children 오류 해결을 위한 중복 키 처리 코드
Monixc Sep 13, 2025
4d2ce12
fix: draw 페이지 무한 로딩 문제 해결
Monixc Sep 13, 2025
135e3bb
style: ui 개선-탭 구성 변경
Monixc Sep 13, 2025
43520c7
style: UI 색상 및 스타일 개선
Monixc Sep 13, 2025
6907c9a
style: FloatingButton 컴포넌트의 배경 스타일 수정
Monixc Sep 13, 2025
79f697a
fix: useRunModals에서 navigation 타입 수정
Monixc Sep 13, 2025
79338ba
feat: 추천 경로 기능 추가 및 UI 개선
Monixc Sep 13, 2025
6f996b7
fix: RecommendationContainer에서 departure 좌표 형식 수정
Monixc Sep 13, 2025
6de1cf0
fix: useLongPress 훅에서 불필요한 상태 제거
Monixc Sep 13, 2025
2762037
feat: useRunModals에서 코스 선택 시 최소 이동 거리 체크 및 API 응답 로그 추가
Monixc Sep 13, 2025
ddfa5cb
style: UI 요소의 위치 및 스타일 조정
Monixc Sep 13, 2025
d51d4ab
style: RunTabNavigator 및 Detail 페이지의 배경색 변경
Monixc Sep 13, 2025
328fc6d
feat: 런닝 기록 및 대시보드 API 훅 추가
Monixc Sep 13, 2025
3c38d36
feat: 추천 경로 카드에 안내 카드 추가 및 UI 개선
Monixc Sep 13, 2025
72f025e
style: Card 및 RecommendationContainer 컴포넌트의 스타일 수정
Monixc Sep 14, 2025
249229d
fix: RunTabNavigator 및 Detail 페이지의 텍스트 수정
Monixc Sep 14, 2025
87cfe76
feat: 지도 컴포넌트에 onMapReady 콜백 추가 및 그리기 기능 개선
Monixc Sep 14, 2025
ea1d043
feat: LoadingIndicator 컴포넌트에 메시지 프로퍼티 추가 및 RouteSave 페이지 내비게이션 수정
Monixc Sep 14, 2025
f4beefa
feat: Draw 탭 이탈 시 그린 선 초기화 로직 수정 및 RunTabNavigator 리셋 컴포넌트 추가
Monixc Sep 14, 2025
ad265da
feat: 캡처 기능 개선 및 그리기 소스 표시 로직 추가
Monixc Sep 14, 2025
06ab1c0
feat: eas.json에 Android 빌드 타입 추가
Monixc Sep 14, 2025
a29e436
feat: eas.json에 Android 빌드 타입 추가 및 불필요한 공백 제거
Monixc Sep 14, 2025
7413ab5
feat: Google 로그인 기능 개선 및 오류 처리 로깅 추가
Monixc Sep 14, 2025
996f747
로그 메시지 영어로 변경 및 오류 메시지 개선
Monixc Sep 14, 2025
dc3b876
fix: 상태 및 api 관련 오류 수정
Monixc Sep 14, 2025
6a62317
refactor: 로딩 상태 확인 및 API 호출 로직 개선
Monixc Sep 15, 2025
7cb4784
Merge pull request #47 from prgrms-fullstack-devcourse/feature/ui-v2
Monixc Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion eas.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@
"preview": {
"distribution": "internal"
},
"production": {},
"production": {
"android": {
"buildType": "apk"
}
},
"development-simulator": {
"developmentClient": true,
"distribution": "internal",
"ios": {
"simulator": true
},
"android": {
"buildType": "apk"
},
"environment": "development"
}
},
Expand Down
94 changes: 59 additions & 35 deletions src/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface CardProps {
onPress?: () => void;
style?: ViewStyle;
fullWidth?: boolean;
variant?: 'default' | 'light';
}

export default function Card({
Expand All @@ -31,6 +32,7 @@ export default function Card({
onPress,
style,
fullWidth = false,
variant = 'default',
}: CardProps) {
const { width: screenWidth } = Dimensions.get('window');
const [imageError, setImageError] = useState(false);
Expand All @@ -52,6 +54,7 @@ export default function Card({
<CardContainer
fullWidth={fullWidth}
mode={finalMode}
variant={variant}
onPress={onPress}
style={style}
activeOpacity={0.8}
Expand All @@ -77,7 +80,7 @@ export default function Card({
</LoadingContainer>
)}
{content?.hasStar && (
<StarIcon size={20} color="#FFD700" fill="#FFD700" />
<StarIcon size={20} color="#2d2d2d" fill="#2d2d2d" />
)}
</ImageContainer>
</CardContainer>
Expand All @@ -86,16 +89,19 @@ export default function Card({

if (finalMode === 'image-with-text') {
const contentWidth = screenWidth - 64;
const imageSize = contentWidth * 0.45;
const imageSize = contentWidth * 0.36; // 0.45에서 0.36으로 변경 (약 20% 감소)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

이미지 크기 계산에 사용된 0.36은 매직 넘버입니다. 코드의 가독성과 유지보수성을 위해 의미 있는 이름의 상수로 정의하여 사용하는 것이 좋습니다.1

    const IMAGE_SIZE_RATIO = 0.36;
    const imageSize = contentWidth * IMAGE_SIZE_RATIO; // 0.45에서 0.36으로 변경 (약 20% 감소)

Style Guide References

Footnotes

  1. 매직 넘버(의미를 알 수 없는 숫자)는 코드 가독성을 해치므로, 이름 있는 상수로 정의하여 사용해야 합니다.

return (
<CardContainer
fullWidth={fullWidth}
mode={finalMode}
variant={variant}
onPress={onPress}
style={style}
activeOpacity={0.8}
>
{content?.cardTitle && <CardTitle>{content.cardTitle}</CardTitle>}
{content?.cardTitle && (
<CardTitle variant={variant}>{content.cardTitle}</CardTitle>
)}
<ContentRow>
{imageSource && (
<CardImage
Expand All @@ -107,14 +113,18 @@ export default function Card({
)}
{content && (
<CardContentContainer>
{content.title && <Title>{content.title}</Title>}
{content.subtitle && <Subtitle>{content.subtitle}</Subtitle>}
{content.title && (
<Title variant={variant}>{content.title}</Title>
)}
{content.subtitle && (
<Subtitle variant={variant}>{content.subtitle}</Subtitle>
)}
{content.stats && (
<StatsContainer>
{content.stats.map((stat, index) => (
<StatItem key={index}>
<StatLabel>{stat.label}</StatLabel>
<StatValue>{stat.value}</StatValue>
<StatLabel variant={variant}>{stat.label}</StatLabel>
<StatValue variant={variant}>{stat.value}</StatValue>
</StatItem>
))}
</StatsContainer>
Expand All @@ -130,6 +140,7 @@ export default function Card({
<CardContainer
fullWidth={fullWidth}
mode={finalMode}
variant={variant}
onPress={onPress}
style={style}
activeOpacity={0.8}
Expand All @@ -142,9 +153,11 @@ export default function Card({
const CardContainer = styled(TouchableOpacity)<{
fullWidth: boolean;
mode: CardMode;
}>(({ fullWidth, mode }) => ({
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderColor: 'rgba(255, 255, 255, 0.2)',
variant: 'default' | 'light';
}>(({ fullWidth, mode, variant }) => ({
backgroundColor: variant === 'light' ? '#ffffff' : 'rgba(255, 255, 255, 0.1)',
borderColor:
variant === 'light' ? 'rgba(0, 0, 0, 0.1)' : 'rgba(255, 255, 255, 0.2)',
Comment on lines +158 to +160

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

배경색과 테두리 색상이 하드코딩되어 있습니다. 스타일 가이드에 따라 재사용 가능한 색상은 테마 또는 상수 파일에서 관리해야 합니다.1 theme.ts에 색상을 정의하고 참조하는 방식으로 수정하는 것을 권장합니다.

  backgroundColor: variant === 'light' ? theme.colors.white : theme.colors.background.card,
  borderColor:
    variant === 'light' ? theme.colors.border.light : theme.colors.border.dark,

Style Guide References

Footnotes

  1. 재사용 가능한 색상, 폰트, 간격 등은 theme 또는 constants 파일로 관리하여 일관성을 유지하고 유지보수를 용이하게 해야 합니다.

borderWidth: 1,
borderRadius: 12,
padding: mode === 'only-image' ? 0 : 16,
Expand All @@ -153,12 +166,14 @@ const CardContainer = styled(TouchableOpacity)<{
aspectRatio: fullWidth ? undefined : 1,
}));

const CardTitle = styled.Text({
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
marginBottom: 8,
});
const CardTitle = styled.Text<{ variant?: 'default' | 'light' }>(
({ variant }) => ({
color: variant === 'light' ? '#000000' : '#ffffff',
fontSize: 16,
fontWeight: '600',
marginBottom: 8,
}),
);

const ContentRow = styled.View({
flexDirection: 'row',
Expand All @@ -185,18 +200,21 @@ const CardContentContainer = styled.View({
flex: 1,
});

const Title = styled.Text({
color: '#ffffff',
const Title = styled.Text<{ variant?: 'default' | 'light' }>(({ variant }) => ({
color: variant === 'light' ? '#000000' : '#ffffff',
fontSize: 18,
fontWeight: 'bold',
marginBottom: 4,
});
}));

const Subtitle = styled.Text({
color: 'rgba(255, 255, 255, 0.7)',
fontSize: 14,
marginBottom: 8,
});
const Subtitle = styled.Text<{ variant?: 'default' | 'light' }>(
({ variant }) => ({
color:
variant === 'light' ? 'rgba(0, 0, 0, 0.7)' : 'rgba(255, 255, 255, 0.7)',
fontSize: 14,
marginBottom: 8,
}),
);

const StatsContainer = styled.View({
flexDirection: 'row',
Expand All @@ -207,17 +225,23 @@ const StatItem = styled.View({
alignItems: 'center',
});

const StatLabel = styled.Text({
color: 'rgba(255, 255, 255, 0.8)',
fontSize: 12,
marginBottom: 2,
});
const StatLabel = styled.Text<{ variant?: 'default' | 'light' }>(
({ variant }) => ({
color:
variant === 'light' ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)',
fontSize: 12,
marginBottom: 2,
}),
);

const StatValue = styled.Text({
color: 'rgba(255, 255, 255, 0.8)',
fontSize: 14,
fontWeight: '600',
});
const StatValue = styled.Text<{ variant?: 'default' | 'light' }>(
({ variant }) => ({
color:
variant === 'light' ? 'rgba(0, 0, 0, 0.8)' : 'rgba(255, 255, 255, 0.8)',
fontSize: 14,
fontWeight: '600',
}),
);

const StarIcon = styled(Star)({
position: 'absolute',
Expand Down Expand Up @@ -254,7 +278,7 @@ const LoadingContainer = styled.View({
});

const LoadingText = styled.Text({
color: '#007AFF',
color: '#2d2d2d',
fontSize: 12,
fontWeight: '500',
});
15 changes: 12 additions & 3 deletions src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { INITIAL_ZOOM_LEVEL } from '@/constants/location';
interface MapProps {
mapRef: RefObject<Mapbox.MapView | null>;
cameraRef: RefObject<Mapbox.Camera | null>;
initialLocation: Position;
initialLocation: Position | null;
onUserLocationUpdate?: (location: Mapbox.Location) => void;
onMapReady?: () => void;
children?: React.ReactNode;
showUserLocation?: boolean;
}
Expand All @@ -18,15 +19,23 @@ export default function Map({
cameraRef,
initialLocation,
onUserLocationUpdate,
onMapReady,
children,
showUserLocation = true,
}: MapProps) {
// initialLocation이 없으면 기본 서울 좌표 사용
const safeInitialLocation = initialLocation || [127.0276, 37.4979];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

기본 위치 좌표가 하드코딩되어 있습니다. 이와 같은 값은 매직 넘버로 간주될 수 있으며, constants 파일에 상수로 정의하여 관리하는 것이 좋습니다.1

Suggested change
const safeInitialLocation = initialLocation || [127.0276, 37.4979];
const safeInitialLocation = initialLocation || DEFAULT_SEOUL_COORDINATE;

Style Guide References

Footnotes

  1. 상수는 하드코딩하지 말고 constants.ts 또는 theme.ts에 관리해야 합니다.


return (
<StyledMapView ref={mapRef} styleURL={Mapbox.StyleURL.Street}>
<StyledMapView
ref={mapRef}
styleURL={Mapbox.StyleURL.Street}
onDidFinishLoadingMap={onMapReady}
>
<Mapbox.Camera
ref={cameraRef}
defaultSettings={{
centerCoordinate: initialLocation,
centerCoordinate: safeInitialLocation,
zoomLevel: INITIAL_ZOOM_LEVEL,
}}
/>
Expand Down
29 changes: 24 additions & 5 deletions src/components/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Modal as RNModal, Text, TouchableOpacity, View } from 'react-native';
import styled from '@emotion/native';
import { LinearGradient } from 'expo-linear-gradient';
import { theme } from '@/styles/theme';

interface ModalProps {
Expand Down Expand Up @@ -38,13 +39,23 @@ export default function Modal({
<ModalMessage>{message}</ModalMessage>
<ButtonContainer>
<ModalButton onPress={onCancel} disabled={disabled || loading}>
<ModalButtonGradient
colors={['#1a1a1a', '#2d2d2d', '#404040']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
<ModalButtonText>{cancelText}</ModalButtonText>
</ModalButton>
<ModalButton
onPress={onConfirm}
isPrimary
disabled={disabled || loading}
>
<ModalButtonGradient
colors={['#1a1a1a', '#2d2d2d', '#404040']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
/>
<ModalButtonText isPrimary>
{loading ? '저장 중...' : confirmText}
</ModalButtonText>
Expand Down Expand Up @@ -101,16 +112,24 @@ const ModalButton = styled(TouchableOpacity)<{
flex: 1;
padding: 12px 16px;
border-radius: 8px;
background-color: ${({ isPrimary, disabled }) => {
if (disabled) return theme.colors.gray[300];
return isPrimary ? theme.colors.primary[500] : theme.colors.gray[100];
}};
align-items: center;
position: relative;
overflow: hidden;
opacity: ${({ disabled }) => (disabled ? 0.6 : 1)};
`;

const ModalButtonGradient = styled(LinearGradient)`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 8px;
`;

const ModalButtonText = styled(Text)<{ isPrimary?: boolean }>`
font-size: 16px;
font-weight: 500;
color: ${({ isPrimary }) => (isPrimary ? '#ffffff' : theme.colors.gray[700])};
color: #ffffff;
z-index: 1;
`;
4 changes: 2 additions & 2 deletions src/components/TabNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,13 @@ const TabButton = styled.TouchableOpacity<{
alignItems: 'center',
paddingVertical: 8,
borderBottomWidth: isActive ? 2 : 0,
borderBottomColor: '#ff6b35',
borderBottomColor: '#2d2d2d',
}));

const TabText = styled.Text<{
isActive: boolean;
}>(({ isActive }) => ({
fontSize: 14,
fontWeight: isActive ? '600' : '400',
color: isActive ? '#ff6b35' : '#666666',
color: isActive ? '#2d2d2d' : '#666666',
}));
Loading