diff --git a/src/assets/icons/chat.svg b/src/assets/icons/chat.svg new file mode 100644 index 0000000..4d388b6 --- /dev/null +++ b/src/assets/icons/chat.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/index.ts b/src/assets/icons/index.ts index da6ca54..76b8c61 100644 --- a/src/assets/icons/index.ts +++ b/src/assets/icons/index.ts @@ -5,6 +5,8 @@ export { default as MapIcon } from './map.svg'; export { default as BookmarkIcon } from './bookmark.svg'; export { default as MyPageIcon } from './mypage.svg'; export { default as AppLogoIcon } from './logo.svg'; +export { default as LogoIcon } from './logoicon.svg'; +export { default as LogoLetter } from './logoletter.svg'; export { default as KakaoIcon } from './kakaoicon.svg'; export { default as NaverIcon } from './navericon.svg'; export { default as GoogleIcon } from './googleicon.svg'; @@ -44,6 +46,7 @@ export { default as WeatherInfoIcon } from './weather-info.svg'; export { default as BackArrow } from './backArrow.svg'; export { default as CameraIcon } from './camera.svg'; export { default as RightArrowIcon } from './rightarrow.svg'; +export { default as RightArrow2Icon } from './rightarrow2.svg'; export { default as WishIcon } from './wish.svg'; export { default as KebabMenuIcon } from './kebabmenu.svg'; export { default as Map2Icon } from './map2.svg'; @@ -66,6 +69,10 @@ export { default as MyLocation } from './mylocation.svg'; export { default as EmptyLocation } from './emptylocation.svg'; export { default as EmptyWish } from './emptywish.svg'; export { default as AlertIcon } from './alert.svg'; +export { default as NoticeIcon } from './notice.svg'; +export { default as NoticeDotIcon } from './noticedot.svg'; +export { default as ChatIcon } from './chat.svg'; +export { default as MainPlaneIcon } from './mainplane.svg'; export { default as RouteIcon } from './route.svg'; export { default as WishStar } from './wishstar.svg'; diff --git a/src/assets/icons/logoicon.svg b/src/assets/icons/logoicon.svg new file mode 100644 index 0000000..9762947 --- /dev/null +++ b/src/assets/icons/logoicon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/icons/logoletter.svg b/src/assets/icons/logoletter.svg new file mode 100644 index 0000000..c179b46 --- /dev/null +++ b/src/assets/icons/logoletter.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/icons/mainplane.svg b/src/assets/icons/mainplane.svg new file mode 100644 index 0000000..2753fbe --- /dev/null +++ b/src/assets/icons/mainplane.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/notice.svg b/src/assets/icons/notice.svg new file mode 100644 index 0000000..72da5c5 --- /dev/null +++ b/src/assets/icons/notice.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/noticedot.svg b/src/assets/icons/noticedot.svg new file mode 100644 index 0000000..6fc35c1 --- /dev/null +++ b/src/assets/icons/noticedot.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/icons/rightarrow2.svg b/src/assets/icons/rightarrow2.svg new file mode 100644 index 0000000..7cf19ed --- /dev/null +++ b/src/assets/icons/rightarrow2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/mainjeju.png b/src/assets/images/mainjeju.png new file mode 100644 index 0000000..b457432 Binary files /dev/null and b/src/assets/images/mainjeju.png differ diff --git a/src/assets/images/maintokyo.png b/src/assets/images/maintokyo.png new file mode 100644 index 0000000..c89e988 Binary files /dev/null and b/src/assets/images/maintokyo.png differ diff --git a/src/components/ui/CustomBottomSheet.tsx b/src/components/ui/CustomBottomSheet.tsx index 7e3f482..f398713 100644 --- a/src/components/ui/CustomBottomSheet.tsx +++ b/src/components/ui/CustomBottomSheet.tsx @@ -104,7 +104,8 @@ export const CustomBottomSheet = ({ borderTopLeftRadius: cornerRadius ?? 16, borderTopRightRadius: cornerRadius ?? 16, overflow: 'hidden', - + zIndex: 20, + elevation: 20, }, animatedStyle, ]} diff --git a/src/components/ui/MainRecChip.tsx b/src/components/ui/MainRecChip.tsx new file mode 100644 index 0000000..ad3570b --- /dev/null +++ b/src/components/ui/MainRecChip.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { View, Text } from 'react-native'; + +export interface MainRecChipProps { + label: string; + className?: string; +} + +export const MainRecChip: React.FC = ({ label, className }) => { + return ( + + {label} + + ); +}; + +export default MainRecChip; \ No newline at end of file diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index cd8da46..7468437 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -3,6 +3,8 @@ export { NavigationBar } from './NavigationBar'; export type { NavigationBarProps } from './NavigationBar'; export { Chip } from './Chip'; export type { ChipProps } from './Chip'; +export { MainRecChip } from './MainRecChip'; +export type { MainRecChipProps } from './MainRecChip'; export { TabNavigation } from './TabNavigation'; export type { TabNavigationProps, TabItem } from './TabNavigation'; diff --git a/src/navigation/types.ts b/src/navigation/types.ts index 3c62ef1..5032137 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -11,7 +11,7 @@ export type SearchStackParamList = { export type RootTabParamList = { Home: undefined; Search: NavigatorScreenParams | undefined; - Map: undefined; + MyTrip: undefined; Bookmark: undefined; MyPage: undefined; }; diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 8ec6e3e..9b475d5 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -1,16 +1,18 @@ -import { useNavigation } from '@react-navigation/native'; +import { useNavigation } from '@react-navigation/native'; import type { CompositeNavigationProp } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import React, { useCallback, useState } from 'react'; -import { View, Text, TouchableOpacity, Pressable, TextInput } from 'react-native'; +import { View, Text, TouchableOpacity, Pressable, TextInput, ScrollView, Image } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { Shadow } from 'react-native-shadow-2'; import type { HomeStackParamList } from '@/navigation'; import type { RootStackParamList } from '@/navigation'; import Animated, { useSharedValue, withTiming, useAnimatedStyle, interpolate } from 'react-native-reanimated'; import CustomBottomSheet from '@/components/ui/CustomBottomSheet'; -import { RobotIcon, SendIcon, X } from '@/assets/icons'; +import { MainRecChip } from '@/components/ui'; +import { RobotIcon, SendIcon, X, NoticeIcon, LogoIcon, LogoLetter, ChatIcon } from '@/assets/icons'; import { COLORS } from '@/constants/colors'; -import { ChatCaseContent } from '@/screens/home/components'; +import { ChatCaseContent, MainTripCard } from '@/screens/home/components'; import { CHAT_HEADER_HEIGHT, CHAT_INPUT_BOTTOM_SPACING, @@ -19,24 +21,58 @@ import { CHAT_SHEET_HEIGHT, } from '@/screens/home/constants'; -// ============ Types ============ type NavigationProp = CompositeNavigationProp< NativeStackNavigationProp, NativeStackNavigationProp >; +const RECOMMENDED_DESTINATIONS = [ + { + id: '1', + title: '제주도', + country: '한국', + description: '아름다운 자연과 독특한 문화가 있는 한국의 보석 같은 섬', + imageUrl: require('@/assets/images/mainjeju.png'), + tags: ['자연', '맛집', '자연'], + }, + { + id: '2', + title: '제주도', + country: '한국', + description: '아름다운 자연과 독특한 문화가 있는 한국의 보석 같은 섬', + imageUrl: require('@/assets/images/mainjeju.png'), + tags: ['자연', '맛집'], + }, +]; + const HomeScreen: React.FC = () => { const navigation = useNavigation(); const translateY = useSharedValue(CHAT_SHEET_HEIGHT); const SNAP_MIN = CHAT_SHEET_HEIGHT; const [isChatOpen, setIsChatOpen] = useState(false); const [chatCaseOrder, setChatCaseOrder] = useState(-1); + const [hasPlannedTrip, setHasPlannedTrip] = useState(false); + const [isInTripScheduleView, setIsInTripScheduleView] = useState(false); const currentCaseIndex = chatCaseOrder < 0 ? 0 : chatCaseOrder; + const hasNotification = true; const handleNavigateToDetail = useCallback(() => { navigation.navigate('Alert'); }, [navigation]); + const handleNavigateToAddTrip = useCallback(() => { + setHasPlannedTrip(true); + setIsInTripScheduleView(false); + }, []); + + const handleOpenTripSchedule = useCallback(() => { + setIsInTripScheduleView(true); + }, []); + + const handleNavigateToMyTrip = useCallback(() => { + navigation.navigate('MainTabs', { screen: 'MyTrip' }); + }, [navigation]); + const handleOpenChat = useCallback(() => { setChatCaseOrder((prev) => (prev + 1) % 3); translateY.value = withTiming(0, { duration: 350 }); @@ -47,22 +83,12 @@ const HomeScreen: React.FC = () => { }, [translateY, SNAP_MIN]); const backdropStyle = useAnimatedStyle(() => { - const opacity = interpolate(translateY.value, [0, SNAP_MIN], [1, 0]); + const opacity = interpolate(translateY.value, [0, SNAP_MIN], [0.3, 0]); return { opacity }; }, [translateY, SNAP_MIN]); return ( - - - - 알림 리스트 - - - - {/* 배경 오버레이 */} { right: 0, bottom: 0, backgroundColor: 'rgba(0, 0, 0, 0.3)', + zIndex: 10, + elevation: 10, }, backdropStyle, ]} @@ -80,16 +108,87 @@ const HomeScreen: React.FC = () => { - {/* 채팅 플로팅 버튼 */} - {/* TODO: 재원님 여기 만드시면 돼요 */} - - 채팅 + + + + + + + + + + + + {hasNotification && ( + + )} + + + + + + + + + + + + + 추천 여행지 + 지금 떠나기 좋은 여행지를 모았어요 + + + + {RECOMMENDED_DESTINATIONS.map((item) => ( + + + + + + {item.title} + {item.country} + + + + + + {item.description} + + + {item.tags.map((tag, index) => ( + + ))} + + + + + ))} + + + + + + - {/* 채팅 바텀시트 */} { onStateChange={setIsChatOpen} showIndicator={false} > - {/* 바텀시트 헤더 */} { - {/* 헤더 아래 콘텐츠 영역 */} - {/* 케이스별 콘텐츠 */} - {/* 하단 입력창 */} - + { ); }; + export default HomeScreen; -export { HomeScreen }; \ No newline at end of file +export { HomeScreen }; diff --git a/src/screens/home/components/MainTripCard.tsx b/src/screens/home/components/MainTripCard.tsx new file mode 100644 index 0000000..61fb0d4 --- /dev/null +++ b/src/screens/home/components/MainTripCard.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { MainTripCardEmpty } from './MainTripCardEmpty'; +import { MainTripCardPlanned } from './MainTripCardPlanned'; +import { MainTripCardInProgress } from './MainTripCardInProgress'; + +interface MainTripCardProps { + hasPlannedTrip: boolean; + isInTripScheduleView: boolean; + onAddTrip: () => void; + onOpenTripSchedule: () => void; + onViewAllSchedule: () => void; +} + +const MainTripCard: React.FC = ({ + hasPlannedTrip, + isInTripScheduleView, + onAddTrip, + onOpenTripSchedule, + onViewAllSchedule, +}) => { + if (!hasPlannedTrip) { + return ; + } + + if (!isInTripScheduleView) { + return ; + } + + return ; +}; + +export default MainTripCard; +export { MainTripCard }; diff --git a/src/screens/home/components/MainTripCardEmpty.tsx b/src/screens/home/components/MainTripCardEmpty.tsx new file mode 100644 index 0000000..f6d967c --- /dev/null +++ b/src/screens/home/components/MainTripCardEmpty.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { View, Text, TouchableOpacity } from 'react-native'; +import { ContentContainer } from '@/components/ui'; +import { AirplaneIcon, PlusIcon } from '@/assets/icons'; + +interface MainTripCardEmptyProps { + onAddTrip: () => void; +} + +const MainTripCardEmpty: React.FC = ({ onAddTrip }) => { + return ( + + + + + 여행을 계획해보세요 + + 새로운 여행지를 추가하고{'\n'}일정을 관리해보세요 + + + + 여행 추가하기 + + + ); +}; + +export default MainTripCardEmpty; +export { MainTripCardEmpty }; diff --git a/src/screens/home/components/MainTripCardInProgress.tsx b/src/screens/home/components/MainTripCardInProgress.tsx new file mode 100644 index 0000000..81ef19a --- /dev/null +++ b/src/screens/home/components/MainTripCardInProgress.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, Image } from 'react-native'; +import { Shadow } from 'react-native-shadow-2'; +import { TimeB, MarkerGrayIcon, RightArrow2Icon } from '@/assets/icons'; + +interface MainTripCardInProgressProps { + onViewAllSchedule: () => void; +} + +const MainTripCardInProgress: React.FC = ({ onViewAllSchedule }) => { + return ( + + + + + + + + 여행중 + + 도쿄 여행 + + + + + + + 다음 일정 + + + + + + 09:00 + 11:00 + + + 아사쿠사 센소지 + + + 아사쿠사, 도쿄 + + 도쿄에서 가장 오래된 사원 방문 + + + + + + + 12:00 + + 츠키지 시장 점심 + + + 14:30 + + 시부야 스크램블 교차로 + + + + + + 전체 일정 보기 + + + + + + + ); +}; + +export default MainTripCardInProgress; +export { MainTripCardInProgress }; diff --git a/src/screens/home/components/MainTripCardPlanned.tsx b/src/screens/home/components/MainTripCardPlanned.tsx new file mode 100644 index 0000000..847f0a1 --- /dev/null +++ b/src/screens/home/components/MainTripCardPlanned.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, Image } from 'react-native'; +import { Shadow } from 'react-native-shadow-2'; +import { DateIcon, MainPlaneIcon, VectorGrayIcon } from '@/assets/icons'; + +interface MainTripCardPlannedProps { + onOpenTripSchedule: () => void; +} + +const MainTripCardPlanned: React.FC = ({ onOpenTripSchedule }) => { + return ( + + + + + + + + 다가오는 여행 + + 도쿄 여행 + + + 3/23 - 3/28 + + + + + + + 5개의 일정이 계획되었어요 + + 일정 보기 + + + + + + + 여행까지 + 14일 남음 + + + + + + + + + ); +}; + +export default MainTripCardPlanned; +export { MainTripCardPlanned }; diff --git a/src/screens/home/components/index.ts b/src/screens/home/components/index.ts index 516597b..b780f00 100644 --- a/src/screens/home/components/index.ts +++ b/src/screens/home/components/index.ts @@ -1 +1,5 @@ -export { ChatCaseContent } from './ChatCaseContent'; \ No newline at end of file +export { ChatCaseContent } from './ChatCaseContent'; +export { MainTripCard } from './MainTripCard'; +export { MainTripCardEmpty } from './MainTripCardEmpty'; +export { MainTripCardPlanned } from './MainTripCardPlanned'; +export { MainTripCardInProgress } from './MainTripCardInProgress'; diff --git a/tailwind.config.js b/tailwind.config.js index cbd3e49..0f05988 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -27,7 +27,7 @@ const colors = { errmassage: '#FF4444', kakaoYellow: '#FEE500', naverGreen: '#03A94D', - + greenstate: '#27C840', errormessage: '#FF4444', }; @@ -58,6 +58,7 @@ module.exports = { boxShadow: { card: '0 0 3px rgba(0,0,0,0.15)', logincard: '0 0 5px rgba(0,0,0,0.25)', + recommended: '0 0 10px rgba(0,0,0,0.25)', }, }, },