From f2138b4fdd2d7b40aa03731c9530cc25b9fb1b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sun, 17 Nov 2024 05:18:16 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[SZ-533]refector:=20=ED=8D=BC=EB=84=90=20?= =?UTF-8?q?=EC=8A=A4=ED=85=9D1=20=ED=99=94=EB=A9=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/{ => funnelScreen}/FunnelScreen.jsx | 84 ++++--------------- app/funnelScreen/FunnelStepOne.tsx | 105 ++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 69 deletions(-) rename app/{ => funnelScreen}/FunnelScreen.jsx (69%) create mode 100644 app/funnelScreen/FunnelStepOne.tsx diff --git a/app/FunnelScreen.jsx b/app/funnelScreen/FunnelScreen.jsx similarity index 69% rename from app/FunnelScreen.jsx rename to app/funnelScreen/FunnelScreen.jsx index f484ccd..16d0566 100644 --- a/app/FunnelScreen.jsx +++ b/app/funnelScreen/FunnelScreen.jsx @@ -1,23 +1,21 @@ /* eslint-disable react/no-unstable-nested-components */ import React, { useContext } from 'react'; import { StyleSheet, Platform, Linking } from 'react-native'; -import { useFunnel } from '../hooks/funnel/useFunnel'; +import { useFunnel } from '../../hooks/funnel/useFunnel'; import { Layout, Text, - Select, - SelectItem, Button, TopNavigation, TopNavigationAction, Icon, } from '@ui-kitten/components'; -import * as Animatable from 'react-native-animatable'; import messaging from '@react-native-firebase/messaging'; import { useRouter } from 'expo-router'; import { FunnelContext } from '@/contexts/FunnelContext'; -import useModal from '../hooks/common/useModal'; -import ConfirmModal from '../components/common/molecules/ConfirmModal'; +import useModal from '../../hooks/common/useModal'; +import ConfirmModal from '../../components/common/molecules/ConfirmModal'; +import FunnelStepOne from './FunnelStepOne'; const FunnelScreen = () => { const { Funnel, setStep, goBack, currentStep } = useFunnel('Step1'); @@ -126,69 +124,17 @@ const FunnelScreen = () => { {/* Step 1 */} - - - 직업과 나이를 선택해주세요 - - - {/* 직업 드롭다운 */} - - - {/* 직업이 선택되면 나이 드롭다운 표시 */} - {selectedJobIndex !== null && ( - - - - )} - - {/* 나이가 선택되면 다음 버튼 표시 */} - {selectedAgeIndex !== null && ( - - - - )} - + {/* Step 2 */} diff --git a/app/funnelScreen/FunnelStepOne.tsx b/app/funnelScreen/FunnelStepOne.tsx new file mode 100644 index 0000000..2e167c3 --- /dev/null +++ b/app/funnelScreen/FunnelStepOne.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { + Button, + Layout, + Select, + SelectItem, + Text, +} from '@ui-kitten/components'; +import * as Animatable from 'react-native-animatable'; +import { StyleSheet } from 'react-native'; + +const FunnelStepOne = ({ + selectedJobIndex, + selectedAgeIndex, + setSelectedJobIndex, + setSelectedAgeIndex, + jobOptions, + ageOptions, + setStep, + ageSelectRef, + nextButtonRef, +}) => { + return ( + + + 직업과 나이를 선택해주세요 + + + {/* 직업 드롭다운 */} + + + {/* 직업이 선택되면 나이 드롭다운 표시 */} + {selectedJobIndex !== null && ( + + + + )} + + {/* 나이가 선택되면 다음 버튼 표시 */} + {selectedAgeIndex !== null && ( + + + + )} + + ); +}; + +export default FunnelStepOne; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + padding: 20, + backgroundColor: '#FFFFFF', + }, + title: { + textAlign: 'center', + marginBottom: 30, + }, + select: { + marginVertical: 10, + }, + button: { + marginTop: 30, + }, + animatableView: { + width: '100%', + }, +}); From c1739cd6403a233a68537cf7438a19af13228b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:21:14 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[SZ-533]feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/_layout.jsx | 143 +++++---- app/funnelScreen/FunnelScreen.jsx | 215 ------------- app/funnelScreen/FunnelStepOne.tsx | 105 ------- app/funnelView/funnelView.jsx | 82 +++++ app/funnelView/stepFiveView.tsx | 192 ++++++++++++ app/funnelView/stepFourView.tsx | 168 ++++++++++ app/funnelView/stepOneView.tsx | 138 ++++++++ app/funnelView/stepThreeView.tsx | 312 +++++++++++++++++++ app/funnelView/stepTwoView.tsx | 142 +++++++++ components/common/molecules/ConfirmModal.tsx | 38 ++- hooks/auth/useLogin.js | 7 +- theme/fontStyles.js | 134 +++----- theme/mapping.json | 9 + theme/theme.json | 23 +- 14 files changed, 1205 insertions(+), 503 deletions(-) delete mode 100644 app/funnelScreen/FunnelScreen.jsx delete mode 100644 app/funnelScreen/FunnelStepOne.tsx create mode 100644 app/funnelView/funnelView.jsx create mode 100644 app/funnelView/stepFiveView.tsx create mode 100644 app/funnelView/stepFourView.tsx create mode 100644 app/funnelView/stepOneView.tsx create mode 100644 app/funnelView/stepThreeView.tsx create mode 100644 app/funnelView/stepTwoView.tsx diff --git a/app/_layout.jsx b/app/_layout.jsx index 96dcb22..25f339e 100755 --- a/app/_layout.jsx +++ b/app/_layout.jsx @@ -16,6 +16,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { default as mapping } from '../theme/mapping.json'; +import FunnelProvider from '@/contexts/FunnelContext'; const SENTRY_MODE = env.SENTRY_MODE; Sentry.init({ @@ -53,73 +54,81 @@ const RootLayout = () => { theme={{ ...eva.light, ...theme }} customMapping={mapping} > - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/funnelScreen/FunnelScreen.jsx b/app/funnelScreen/FunnelScreen.jsx deleted file mode 100644 index 16d0566..0000000 --- a/app/funnelScreen/FunnelScreen.jsx +++ /dev/null @@ -1,215 +0,0 @@ -/* eslint-disable react/no-unstable-nested-components */ -import React, { useContext } from 'react'; -import { StyleSheet, Platform, Linking } from 'react-native'; -import { useFunnel } from '../../hooks/funnel/useFunnel'; -import { - Layout, - Text, - Button, - TopNavigation, - TopNavigationAction, - Icon, -} from '@ui-kitten/components'; -import messaging from '@react-native-firebase/messaging'; -import { useRouter } from 'expo-router'; -import { FunnelContext } from '@/contexts/FunnelContext'; -import useModal from '../../hooks/common/useModal'; -import ConfirmModal from '../../components/common/molecules/ConfirmModal'; -import FunnelStepOne from './FunnelStepOne'; - -const FunnelScreen = () => { - const { Funnel, setStep, goBack, currentStep } = useFunnel('Step1'); - const route = useRouter(); - const { setFunnelDone } = useContext(FunnelContext); - const { isVisible, setIsVisible } = useModal(); - - // 직업과 나이 옵션 데이터 - const jobOptions = ['개발자', '디자이너', '기획자', '마케터', '기타']; - const ageOptions = ['10대', '20대', '30대', '40대', '50대 이상']; - - // 상태 관리 - const [selectedJobIndex, setSelectedJobIndex] = React.useState(null); - const [selectedAgeIndex, setSelectedAgeIndex] = React.useState(null); - - // 알림 권한 요청 함수 - const requestNotificationPermission = async () => { - try { - const authStatus = await messaging().hasPermission(); - - const enabled = - authStatus === messaging.AuthorizationStatus.AUTHORIZED || - authStatus === messaging.AuthorizationStatus.PROVISIONAL; - - if (enabled) { - // 권한이 이미 허용됨 - return true; - } else { - if (Platform.OS === 'ios') { - // iOS에서 권한 요청 - const authStatusOnIOS = await messaging().requestPermission(); - const enabledOnIOS = - authStatusOnIOS === messaging.AuthorizationStatus.AUTHORIZED || - authStatusOnIOS === messaging.AuthorizationStatus.PROVISIONAL; - return enabledOnIOS; - } else if (Platform.OS === 'android') { - // Android에서 버전 확인 - const androidVersion = Platform.Version; - if (androidVersion >= 33) { - // Android 13 이상에서 권한 요청 - const result = await messaging().requestPermission(); - const enabledOnAndroid = - result === messaging.AuthorizationStatus.AUTHORIZED; - return enabledOnAndroid; - } else { - // Android 13 미만은 권한 요청 불필요 - return true; - } - } else { - // 기타 플랫폼 (web 등) - return false; - } - } - } catch (error) { - console.error('Notification permission error:', error); - return false; - } - }; - - // 애니메이션 Ref - const ageSelectRef = React.useRef(null); - const nextButtonRef = React.useRef(null); - - // 직업이 선택되었을 때 나이 드롭다운 애니메이션 실행 - React.useEffect(() => { - if (selectedJobIndex !== null && ageSelectRef.current) { - ageSelectRef.current.fadeInUp(500); - } - }, [selectedJobIndex]); - - // 나이가 선택되었을 때 다음 버튼 애니메이션 실행 - React.useEffect(() => { - if (selectedAgeIndex !== null && nextButtonRef.current) { - nextButtonRef.current.fadeInUp(500); - } - }, [selectedAgeIndex]); - - // Back Icon Component - const BackIcon = props => ; - - // Back Action Component - const BackAction = () => ( - - ); - - // Handle Back Button Press - const handleBackAction = () => { - if (currentStep === 'Step1') { - route.back(); - } else { - goBack(); - } - }; - - return ( - - {/* 커스텀 헤더 */} - - - {/* 펀넬 내용 */} - - {/* Step 1 */} - - - - - {/* Step 2 */} - - - - 알림을 허용하시겠습니까? - - - - { - setIsVisible(false); - setStep('Step3'); - }} - onCancel={() => { - setIsVisible(false); - Linking.openSettings(); - }} - titleKey="" - messageKey="" - confirmTextKey="" - cancelTextKey="" - /> - - - {/* Step 3 */} - - - - 설정이 완료되었습니다! - - - - - - - ); -}; - -export default FunnelScreen; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - padding: 20, - backgroundColor: '#FFFFFF', - }, - title: { - textAlign: 'center', - marginBottom: 30, - }, - select: { - marginVertical: 10, - }, - button: { - marginTop: 30, - }, - animatableView: { - width: '100%', - }, -}); diff --git a/app/funnelScreen/FunnelStepOne.tsx b/app/funnelScreen/FunnelStepOne.tsx deleted file mode 100644 index 2e167c3..0000000 --- a/app/funnelScreen/FunnelStepOne.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { - Button, - Layout, - Select, - SelectItem, - Text, -} from '@ui-kitten/components'; -import * as Animatable from 'react-native-animatable'; -import { StyleSheet } from 'react-native'; - -const FunnelStepOne = ({ - selectedJobIndex, - selectedAgeIndex, - setSelectedJobIndex, - setSelectedAgeIndex, - jobOptions, - ageOptions, - setStep, - ageSelectRef, - nextButtonRef, -}) => { - return ( - - - 직업과 나이를 선택해주세요 - - - {/* 직업 드롭다운 */} - - - {/* 직업이 선택되면 나이 드롭다운 표시 */} - {selectedJobIndex !== null && ( - - - - )} - - {/* 나이가 선택되면 다음 버튼 표시 */} - {selectedAgeIndex !== null && ( - - - - )} - - ); -}; - -export default FunnelStepOne; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - padding: 20, - backgroundColor: '#FFFFFF', - }, - title: { - textAlign: 'center', - marginBottom: 30, - }, - select: { - marginVertical: 10, - }, - button: { - marginTop: 30, - }, - animatableView: { - width: '100%', - }, -}); diff --git a/app/funnelView/funnelView.jsx b/app/funnelView/funnelView.jsx new file mode 100644 index 0000000..d1c7c7b --- /dev/null +++ b/app/funnelView/funnelView.jsx @@ -0,0 +1,82 @@ +/* eslint-disable react/no-unstable-nested-components */ +import React from 'react'; +import { useFunnel } from '../../hooks/funnel/useFunnel'; +import { + Layout, + TopNavigation, + TopNavigationAction, + Icon, +} from '@ui-kitten/components'; +import { useRouter } from 'expo-router'; +import NameInputScreen from './stepOneView'; +import AgeSelectionScreen from './stepTwoView'; +import SleepTimeScreen from './stepThreeView'; +import DelayReasonsScreen from './stepFourView'; +import CompletionScreen from './stepFiveView'; + +const FunnelScreen = () => { + const { Funnel, setStep, goBack, currentStep } = useFunnel('Step1'); + const route = useRouter(); + + const BackIcon = props => ; + + const BackAction = () => ( + + ); + + const handleBackAction = () => { + if (currentStep === 'Step1') { + route.back(); + } else { + goBack(); + } + }; + + return ( + + + + + + { + setStep('Step2'); + }} + /> + + + { + setStep('Step3'); + }} + /> + + SleepTimeScreen + + { + setStep('Step4'); + }} + /> + + + { + setStep('Step5'); + }} + /> + + + + + + + ); +}; + +export default FunnelScreen; diff --git a/app/funnelView/stepFiveView.tsx b/app/funnelView/stepFiveView.tsx new file mode 100644 index 0000000..27b1583 --- /dev/null +++ b/app/funnelView/stepFiveView.tsx @@ -0,0 +1,192 @@ +import React, { useEffect, useState } from 'react'; +import { + Layout, + Text, + Button, + StyleService, + useStyleSheet, +} from '@ui-kitten/components'; +import { View, Platform, Linking, TouchableOpacity } from 'react-native'; +import * as Animatable from 'react-native-animatable'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import { router } from 'expo-router'; +import messaging from '@react-native-firebase/messaging'; +import ConfirmModal from '@/components/common/molecules/ConfirmModal'; +import useModal from '@/hooks/common/useModal'; + +const requestNotificationPermission = async () => { + try { + const authStatus = await messaging().hasPermission(); + + const enabled = + authStatus === messaging.AuthorizationStatus.AUTHORIZED || + authStatus === messaging.AuthorizationStatus.PROVISIONAL; + + if (enabled) { + // 권한이 이미 허용됨 + return true; + } else { + if (Platform.OS === 'ios') { + // iOS에서 권한 요청 + const authStatusOnIOS = await messaging().requestPermission(); + const enabledOnIOS = + authStatusOnIOS === messaging.AuthorizationStatus.AUTHORIZED || + authStatusOnIOS === messaging.AuthorizationStatus.PROVISIONAL; + return enabledOnIOS; + } else if (Platform.OS === 'android') { + // Android에서 버전 확인 + const androidVersion = Platform.Version; + if (androidVersion >= 33) { + // Android 13 이상에서 권한 요청 + const result = await messaging().requestPermission(); + const enabledOnAndroid = + result === messaging.AuthorizationStatus.AUTHORIZED; + return enabledOnAndroid; + } else { + // Android 13 미만은 권한 요청 불필요 + return true; + } + } else { + // 기타 플랫폼 (web 등) + return false; + } + } + } catch (error) { + console.error('Notification permission error:', error); + return false; + } +}; + +const CompletionScreen = () => { + const [showNotificationScreen, setShowNotificationScreen] = useState(false); + const styles = useStyleSheet(themedStyles); + const { isVisible, setIsVisible } = useModal(); + + useEffect(() => { + // 3초 후에 알림 허용 화면으로 전환 + const timer = setTimeout(() => { + setShowNotificationScreen(true); + }, 3000); + + return () => clearTimeout(timer); + }, []); + + const handleAllowNotifications = async () => { + const granted = await requestNotificationPermission(); + if (granted) { + router.dismissAll(); + router.replace('/(tabs)'); + } else { + setIsVisible(true); + } + }; + + if (!showNotificationScreen) { + return ( + + + 환영합니다 OOO님 + 가입이 완료되었어요 + + + ); + } + + return ( + + + + 할 일을 잊지 않도록{'\n'}알려드릴게요 + + + setIsVisible(true)}> + 지금은 괜찮아요 + + + + + + { + setIsVisible(false); + Linking.openSettings(); + router.dismissAll(); + router.replace('/(tabs)'); + }} + onCancel={() => { + setIsVisible(false); + router.dismissAll(); + router.replace('/(tabs)'); + }} + titleKey="views.index.AlertModalTitle" + messageKey="views.index.AlertModalMessage" + confirmTextKey="common.yes" + cancelTextKey="common.no" + /> + + + ); +}; + +const themedStyles = StyleService.create({ + container: { + flex: 1, + backgroundColor: 'background-basic-color-1', + paddingHorizontal: scale(20), + }, + completionContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + welcomeText: { + fontSize: moderateScale(18), + marginBottom: verticalScale(8), + color: 'text-basic-color', + }, + completionText: { + fontSize: moderateScale(24), + fontWeight: 'bold', + color: 'text-basic-color', + }, + notificationContainer: { + flex: 1, + justifyContent: 'space-between', + paddingTop: verticalScale(88), + paddingBottom: verticalScale(30), + }, + notificationTitle: { + fontSize: moderateScale(28), + fontWeight: 'bold', + textAlign: 'left', + lineHeight: moderateScale(35), + }, + buttonContainer: { + gap: verticalScale(16), + }, + skipText: { + textAlign: 'center', + color: 'text-hint-color', + fontSize: moderateScale(14), + }, + button: { + borderRadius: moderateScale(8), + }, +}); + +export default CompletionScreen; diff --git a/app/funnelView/stepFourView.tsx b/app/funnelView/stepFourView.tsx new file mode 100644 index 0000000..5923b03 --- /dev/null +++ b/app/funnelView/stepFourView.tsx @@ -0,0 +1,168 @@ +import React, { useState } from 'react'; +import { + Layout, + Text, + Button, + StyleService, + useStyleSheet, +} from '@ui-kitten/components'; +import { View, TouchableOpacity } from 'react-native'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import * as Animatable from 'react-native-animatable'; + +const DelayReasonsScreen = ({ onNext, currentStep = 4, totalSteps = 5 }) => { + const [selectedReasons, setSelectedReasons] = useState([]); + const styles = useStyleSheet(themedStyles); + + const reasons = [ + '할 일들이 너무 크게 느껴져요', + '꾸준히 이어가기 어려워요', + '우선순위를 정하기 어려워요', + '동기 부여가 없어요', + '집중력이 부족해요', + ]; + + const toggleReason = index => { + setSelectedReasons(prev => { + const isSelected = prev.includes(index); + if (isSelected) { + return prev.filter(i => i !== index); + } else if (prev.length < 3) { + return [...prev, index]; + } + return prev; + }); + }; + + const renderStepIndicators = () => ( + + {[...Array(totalSteps)].map((_, index) => ( + + ))} + + ); + + return ( + + {renderStepIndicators()} + + 미루는 이유를 알려주세요 + 최대 3개까지 선택할 수 있어요 + + + {reasons.map((reason, index) => ( + toggleReason(index)} + > + + {reason} + + + ))} + + {selectedReasons.length > 0 && ( + + + + )} + + + ); +}; + +const themedStyles = StyleService.create({ + container: { + flex: 1, + backgroundColor: 'background-basic-color-1', + paddingHorizontal: scale(20), + }, + contentContainer: { + flex: 1, + justifyContent: 'space-between', + paddingBottom: verticalScale(20), + }, + stepContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + marginBottom: verticalScale(40), + }, + stepIndicator: { + width: scale(8), + height: scale(8), + borderRadius: scale(4), + backgroundColor: 'color-basic-400', + marginHorizontal: scale(3), + }, + activeStepIndicator: { + backgroundColor: 'color-primary-500', + width: scale(16), + height: scale(8), + }, + title: { + textAlign: 'left', + fontSize: moderateScale(28), + fontWeight: 'bold', + marginBottom: verticalScale(8), + }, + subtitle: { + textAlign: 'left', + fontSize: moderateScale(16), + color: 'text-hint-color', + marginBottom: verticalScale(30), + }, + reasonsContainer: { + gap: verticalScale(10), + }, + reasonOption: { + padding: moderateScale(16), + borderRadius: moderateScale(8), + backgroundColor: 'background-basic-color-2', + borderWidth: 1, + borderColor: 'transparent', + }, + selectedReason: { + backgroundColor: 'color-primary-100', + borderColor: 'color-primary-500', + }, + reasonText: { + fontSize: moderateScale(16), + color: 'text-basic-color', + }, + selectedReasonText: { + color: 'color-primary-700', + fontWeight: '600', + }, + button: { + borderRadius: moderateScale(8), + }, +}); + +export default DelayReasonsScreen; diff --git a/app/funnelView/stepOneView.tsx b/app/funnelView/stepOneView.tsx new file mode 100644 index 0000000..57a4fa9 --- /dev/null +++ b/app/funnelView/stepOneView.tsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { + Layout, + Input, + Button, + Text, + StyleService, + useStyleSheet, +} from '@ui-kitten/components'; +import * as Animatable from 'react-native-animatable'; +import { View, KeyboardAvoidingView, Platform } from 'react-native'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; + +const NameInputScreen = ({ onNext, currentStep = 0, totalSteps = 5 }) => { + const [name, setName] = React.useState(''); + const styles = useStyleSheet(themedStyles); + + const renderStepIndicators = () => { + return ( + + {[...Array(totalSteps)].map((_, index) => ( + + ))} + + ); + }; + + return ( + + + {renderStepIndicators()} + + 이름을 입력해주세요 + + + + + + + {name !== '' && ( + + + + )} + + + + ); +}; + +const themedStyles = StyleService.create({ + keyboardAvoidingView: { + flex: 1, + }, + container: { + flex: 1, + backgroundColor: 'background-basic-color-1', + paddingHorizontal: scale(20), + }, + contentContainer: { + flex: 1, + justifyContent: 'space-between', + paddingBottom: verticalScale(20), // 하단 간격 + }, + stepContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + marginBottom: verticalScale(40), + }, + stepIndicator: { + width: scale(8), + height: scale(8), + borderRadius: scale(4), + backgroundColor: 'color-basic-400', + marginHorizontal: scale(3), + }, + activeStepIndicator: { + backgroundColor: 'color-primary-500', + width: scale(16), + height: scale(8), + }, + title: { + textAlign: 'left', + marginBottom: verticalScale(30), + fontSize: moderateScale(28), + fontWeight: 'bold', + color: 'text-basic-color', + }, + inputContainer: {}, + input: { + borderRadius: moderateScale(8), + backgroundColor: 'background-basic-color-1', + borderColor: 'color-basic-400', + }, + inputText: { + fontSize: moderateScale(16), + }, + buttonContainer: {}, + button: { + borderRadius: moderateScale(8), + backgroundColor: 'color-primary-default', + }, +}); + +export default NameInputScreen; diff --git a/app/funnelView/stepThreeView.tsx b/app/funnelView/stepThreeView.tsx new file mode 100644 index 0000000..ca657ce --- /dev/null +++ b/app/funnelView/stepThreeView.tsx @@ -0,0 +1,312 @@ +import React, { useCallback, useMemo, useRef, useState } from 'react'; +import { + Layout, + Text, + Button, + StyleService, + useStyleSheet, +} from '@ui-kitten/components'; +import { View, TouchableOpacity, Pressable } from 'react-native'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import BottomSheet, { + BottomSheetView, + BottomSheetBackdrop, +} from '@gorhom/bottom-sheet'; +import * as Animatable from 'react-native-animatable'; + +const SleepTimeScreen = ({ onNext, currentStep = 2, totalSteps = 5 }) => { + const [selectedTime, setSelectedTime] = useState(null); + const [selectedPeriod, setSelectedPeriod] = useState(null); + const [currentSheet, setCurrentSheet] = useState(null); + const [showTimeSelector, setShowTimeSelector] = useState(false); + const styles = useStyleSheet(themedStyles); + + // refs + const timeSheetRef = useRef(null); + const periodSheetRef = useRef(null); + + // variables + const snapPoints = useMemo(() => ['50%'], []); + const timeOptions = Array.from({ length: 12 }, (_, i) => `${i + 1}시`); + const periodOptions = ['오전', '오후']; + + // callbacks + const handleSheetChanges = useCallback(index => { + if (index === -1) { + setCurrentSheet(null); + } + }, []); + + const handleClosePress = useCallback(() => { + if (currentSheet === 'time') { + timeSheetRef.current?.close(); + } else if (currentSheet === 'period') { + periodSheetRef.current?.close(); + } + setCurrentSheet(null); + }, [currentSheet]); + + const handlePeriodSelect = period => { + setSelectedPeriod(period); + periodSheetRef.current?.close(); + setShowTimeSelector(true); + }; + + const handleTimeSelect = time => { + setSelectedTime(time); + timeSheetRef.current?.close(); + }; + + const renderBackdrop = useCallback( + props => ( + + ), + [], + ); + + const renderStepIndicators = () => ( + + {[...Array(totalSteps)].map((_, index) => ( + + ))} + + ); + + return ( + + {renderStepIndicators()} + + 평소 몇시에 주무시나요? + + + { + setCurrentSheet('period'); + periodSheetRef.current?.expand(); + }} + > + + {selectedPeriod || '시간대'} + + + + + {showTimeSelector && ( + + { + setCurrentSheet('time'); + timeSheetRef.current?.expand(); + }} + > + + {selectedTime || '시간'} + + + + + )} + + + {selectedTime && selectedPeriod && ( + + + + )} + + + {/* Time Bottom Sheet */} + + + + 시간 선택 + + × + + + + {timeOptions.map((time, index) => ( + handleTimeSelect(time)} + > + {time} + + ))} + + + + + {/* Period Bottom Sheet */} + + + + 시간대 선택 + + × + + + + {periodOptions.map((period, index) => ( + handlePeriodSelect(period)} + > + {period} + + ))} + + + + + ); +}; + +const themedStyles = StyleService.create({ + container: { + flex: 1, + backgroundColor: 'background-basic-color-1', + paddingHorizontal: scale(20), + }, + stepContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + marginBottom: verticalScale(40), + }, + contentContainer: { + flex: 1, + justifyContent: 'space-between', + paddingBottom: verticalScale(20), // 하단 간격 + }, + stepIndicator: { + width: scale(8), + height: scale(8), + borderRadius: scale(4), + backgroundColor: 'color-basic-400', + marginHorizontal: scale(3), + }, + activeStepIndicator: { + backgroundColor: 'color-primary-500', + width: scale(16), + height: scale(8), + }, + title: { + textAlign: 'left', + marginBottom: verticalScale(30), + fontSize: moderateScale(28), + fontWeight: 'bold', + }, + selector: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: moderateScale(15), + borderRadius: moderateScale(8), + backgroundColor: 'background-basic-color-1', + borderWidth: 1, + borderColor: 'color-basic-400', + marginBottom: verticalScale(10), + }, + placeholderText: { + color: 'text-hint-color', + }, + selectedText: { + color: 'text-basic-color', + }, + arrow: { + color: 'color-basic-600', + }, + bottomSheetContainer: { + flex: 1, + backgroundColor: 'background-basic-color-1', + }, + bottomSheetHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: moderateScale(15), + borderBottomWidth: 1, + borderBottomColor: 'color-basic-300', + }, + bottomSheetTitle: { + fontSize: moderateScale(16), + fontWeight: 'bold', + }, + closeButton: { + fontSize: moderateScale(24), + color: 'color-basic-600', + padding: moderateScale(5), + }, + optionsContainer: { + padding: moderateScale(15), + }, + optionItem: { + padding: moderateScale(15), + }, + optionText: { + textAlign: 'center', + fontSize: moderateScale(16), + }, + buttonContainer: {}, + button: { + borderRadius: moderateScale(8), + }, + timeGridContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + padding: moderateScale(15), + }, + timeGridItem: { + width: '33.33%', + padding: moderateScale(10), + alignItems: 'center', + justifyContent: 'center', + }, + selectorContainer: {}, +}); + +export default SleepTimeScreen; diff --git a/app/funnelView/stepTwoView.tsx b/app/funnelView/stepTwoView.tsx new file mode 100644 index 0000000..0e51a9f --- /dev/null +++ b/app/funnelView/stepTwoView.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { + Layout, + StyleService, + useStyleSheet, + Text, + Button, +} from '@ui-kitten/components'; +import { View, TouchableOpacity } from 'react-native'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import * as Animatable from 'react-native-animatable'; + +const AgeSelectionScreen = ({ onNext, currentStep = 1, totalSteps = 5 }) => { + const [selectedAge, setSelectedAge] = React.useState(null); + const styles = useStyleSheet(themedStyles); + + const ageRanges = ['10대', '20대', '30대', '40대', '50대 이상']; + + const renderStepIndicators = () => { + return ( + + {[...Array(totalSteps)].map((_, index) => ( + + ))} + + ); + }; + + const renderAgeOption = (age, index) => { + const isSelected = selectedAge === index; + + return ( + setSelectedAge(index)} + style={[styles.ageOption, isSelected && styles.selectedAgeOption]} + > + + {age} + + + ); + }; + + return ( + + {renderStepIndicators()} + + 연령대를 알려주세요 + + + + {ageRanges.map((age, index) => renderAgeOption(age, index))} + + + {selectedAge !== null && ( + + + + )} + + + ); +}; + +const themedStyles = StyleService.create({ + container: { + flex: 1, + backgroundColor: 'background-basic-color-1', + paddingHorizontal: scale(20), + }, + stepContainer: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + marginBottom: verticalScale(40), + }, + stepIndicator: { + width: scale(8), + height: scale(8), + borderRadius: scale(4), + backgroundColor: 'color-basic-400', + marginHorizontal: scale(3), + }, + activeStepIndicator: { + backgroundColor: 'color-primary-500', + width: scale(16), + height: scale(8), + }, + title: { + textAlign: 'left', + marginBottom: verticalScale(30), + fontSize: moderateScale(28), + fontWeight: 'bold', + color: 'text-basic-color', + }, + optionsContainer: {}, + ageOption: { + backgroundColor: 'background-basic-color-2', + padding: moderateScale(16), + borderRadius: moderateScale(8), + marginBottom: verticalScale(10), + }, + selectedAgeOption: { + backgroundColor: 'color-primary-100', + }, + ageText: { + fontSize: moderateScale(16), + color: 'text-basic-color', + }, + selectedAgeText: { + color: 'color-primary-700', + fontWeight: 'bold', + }, + button: { + borderRadius: moderateScale(8), + }, + buttonContainer: {}, + contentContainer: { + flex: 1, + justifyContent: 'space-between', + paddingBottom: verticalScale(20), // 하단 간격 + }, +}); + +export default AgeSelectionScreen; diff --git a/components/common/molecules/ConfirmModal.tsx b/components/common/molecules/ConfirmModal.tsx index 93da1f4..aebb0a6 100644 --- a/components/common/molecules/ConfirmModal.tsx +++ b/components/common/molecules/ConfirmModal.tsx @@ -2,10 +2,7 @@ import React from 'react'; import { Modal, Layout, Text, Button, useTheme } from '@ui-kitten/components'; import { StyleSheet, View } from 'react-native'; import { useTranslation } from 'react-i18next'; -import { - heightPercentage, - widthPercentage, -} from '../../../utils/responsiveSize'; +import { scale, verticalScale } from 'react-native-size-matters'; import fontStyles from '../../../theme/fontStyles'; interface ConfirmModalProps { @@ -67,10 +64,21 @@ const ConfirmModal: React.FC = ({ status="basic" onPress={() => onCancel()} > - {t(cancelTextKey)} + + {t(cancelTextKey)} + @@ -83,8 +91,8 @@ const styles = StyleSheet.create({ backgroundColor: 'rgba(0, 0, 0, 0.5)', }, card: { - height: heightPercentage(165), - width: widthPercentage(328), + height: verticalScale(165), + width: scale(328), flexDirection: 'column', alignItems: 'center', justifyContent: 'center', @@ -95,27 +103,23 @@ const styles = StyleSheet.create({ borderRadius: 16, }, textContainer: { - height: heightPercentage(49), - width: widthPercentage(296), + width: scale(296), flexDirection: 'column', alignItems: 'center', justifyContent: 'space-between', gap: 8, }, - text: { - height: heightPercentage(23), - }, + text: {}, buttonContainer: { - height: heightPercentage(52), - width: widthPercentage(296), + width: scale(296), flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', gap: 12, }, button: { - width: widthPercentage(142), - height: heightPercentage(52), + width: scale(142), + height: verticalScale(52), alignItems: 'center', justifyContent: 'center', borderRadius: 12, diff --git a/hooks/auth/useLogin.js b/hooks/auth/useLogin.js index 2617300..8ad3c02 100644 --- a/hooks/auth/useLogin.js +++ b/hooks/auth/useLogin.js @@ -33,7 +33,12 @@ const useLogin = () => { setUserId(jwtTokenData.userId); setIsLoggedIn(true); - router.push('/(tabs)'); + if (user.isNew) { + router.push('/funnelView/funnelView'); + } else { + router.push('/funnelView/funnelView'); + // router.push('/(tabs)'); + } } }; diff --git a/theme/fontStyles.js b/theme/fontStyles.js index 23575ff..69e5e1b 100644 --- a/theme/fontStyles.js +++ b/theme/fontStyles.js @@ -16,114 +16,96 @@ const fontStyles = { H1: { B_130: { fontFamily: BOLD, - fontSize: 28, - lineHeight: Math.round(28 * 1.3) + 2, + fontSize: moderateScale(28), }, B_100: { fontFamily: BOLD, - fontSize: 28, - lineHeight: 30, + fontSize: moderateScale(28), }, M_130: { fontFamily: MEDIUM, - fontSize: 28, - lineHeight: Math.round(28 * 1.3) + 2, + fontSize: moderateScale(28), }, M_100: { fontFamily: MEDIUM, - fontSize: 28, - lineHeight: 30, + fontSize: moderateScale(28), }, R_130: { fontFamily: REGULAR, - fontSize: 28, - lineHeight: Math.round(28 * 1.3) + 2, + fontSize: moderateScale(28), }, R_100: { fontFamily: REGULAR, - fontSize: 28, - lineHeight: 30, + fontSize: moderateScale(28), }, }, H2: { B_130: { fontFamily: BOLD, - fontSize: 24, - lineHeight: Math.round(24 * 1.3) + 2, + fontSize: moderateScale(24), }, B_100: { fontFamily: BOLD, - fontSize: 24, - lineHeight: 26, + fontSize: moderateScale(24), }, M_130: { fontFamily: MEDIUM, - fontSize: 24, - lineHeight: Math.round(24 * 1.3) + 2, + fontSize: moderateScale(24), }, M_100: { fontFamily: MEDIUM, - fontSize: 24, - lineHeight: 26, + fontSize: moderateScale(24), }, R_130: { fontFamily: REGULAR, - fontSize: 24, - lineHeight: Math.round(24 * 1.3) + 2, + fontSize: moderateScale(24), }, R_100: { fontFamily: REGULAR, - fontSize: 24, - lineHeight: 26, + fontSize: moderateScale(24), }, }, H3: { B_130: { fontFamily: BOLD, - fontSize: 20, - lineHeight: Math.round(20 * 1.3) + 2, + fontSize: moderateScale(20), }, B_100: { fontFamily: BOLD, - fontSize: 20, - lineHeight: 22, + fontSize: moderateScale(20), }, M_130: { fontFamily: MEDIUM, - fontSize: 20, - lineHeight: Math.round(20 * 1.3) + 2, + fontSize: moderateScale(20), }, M_100: { fontFamily: MEDIUM, - fontSize: 20, - lineHeight: 22, + fontSize: moderateScale(20), }, R_130: { fontFamily: REGULAR, - fontSize: 20, - lineHeight: Math.round(20 * 1.3) + 2, + fontSize: moderateScale(20), }, R_100: { fontFamily: REGULAR, - fontSize: 20, - lineHeight: 22, + fontSize: moderateScale(20), }, }, }, @@ -132,76 +114,64 @@ const fontStyles = { S1: { B_130: { fontFamily: BOLD, - fontSize: 18, - lineHeight: Math.round(18 * 1.3) + 2, + fontSize: moderateScale(18), }, B_100: { fontFamily: BOLD, - fontSize: 18, - lineHeight: 20, + fontSize: moderateScale(18), }, M_130: { fontFamily: MEDIUM, - fontSize: 18, - lineHeight: Math.round(18 * 1.3) + 2, + fontSize: moderateScale(18), }, M_100: { fontFamily: MEDIUM, - fontSize: 18, - lineHeight: 20, + fontSize: moderateScale(18), }, R_130: { fontFamily: REGULAR, - fontSize: 18, - lineHeight: Math.round(18 * 1.3) + 2, + fontSize: moderateScale(18), }, R_100: { fontFamily: REGULAR, - fontSize: 18, - lineHeight: 20, + fontSize: moderateScale(18), }, }, S2: { B_130: { fontFamily: BOLD, - fontSize: 16, - lineHeight: Math.round(16 * 1.3) + 2, + fontSize: moderateScale(16), }, B_100: { fontFamily: BOLD, - fontSize: 16, - lineHeight: 18, + fontSize: moderateScale(16), }, M_130: { fontFamily: MEDIUM, - fontSize: 16, - lineHeight: Math.round(16 * 1.3) + 2, + fontSize: moderateScale(16), }, M_100: { fontFamily: MEDIUM, - fontSize: 16, - lineHeight: 18, + fontSize: moderateScale(30), }, R_130: { fontFamily: REGULAR, - fontSize: 16, - lineHeight: Math.round(16 * 1.3) + 2, + fontSize: moderateScale(16), }, R_100: { fontFamily: REGULAR, - fontSize: 16, - lineHeight: 18, + fontSize: moderateScale(16), }, }, }, @@ -210,75 +180,64 @@ const fontStyles = { P1: { B_130: { fontFamily: BOLD, - fontSize: 14, - lineHeight: Math.round(14 * 1.3) + 2, + fontSize: moderateScale(14), }, B_100: { fontFamily: BOLD, - fontSize: 14, - lineHeight: 16, + fontSize: moderateScale(14), }, M_130: { fontFamily: MEDIUM, - fontSize: 14, - lineHeight: Math.round(14 * 1.3) + 2, + fontSize: moderateScale(14), }, M_100: { fontFamily: MEDIUM, - fontSize: moderateScale(14, 0.3), + fontSize: moderateScale(14), }, R_130: { fontFamily: REGULAR, - fontSize: 14, - lineHeight: Math.round(14 * 1.3) + 2, + fontSize: moderateScale(14), }, R_100: { fontFamily: REGULAR, - fontSize: 14, - lineHeight: 16, + fontSize: moderateScale(14), }, }, P2: { B_130: { fontFamily: BOLD, - fontSize: 12, - lineHeight: Math.round(12 * 1.3) + 2, + fontSize: moderateScale(12), }, B_100: { fontFamily: BOLD, - fontSize: 12, - lineHeight: 14, + fontSize: moderateScale(12), }, M_130: { fontFamily: MEDIUM, - fontSize: 12, - lineHeight: Math.round(12 * 1.3) + 2, + fontSize: moderateScale(12), }, M_100: { fontFamily: MEDIUM, - fontSize: 12, - lineHeight: 14, + fontSize: moderateScale(12), }, R_130: { fontFamily: REGULAR, - fontSize: 12, - lineHeight: Math.round(12 * 1.3) + 2, + fontSize: moderateScale(12), }, R_100: { fontFamily: REGULAR, - fontSize: 12, - lineHeight: 14, + fontSize: moderateScale(12), }, }, }, @@ -286,20 +245,17 @@ const fontStyles = { Caption: { B_130: { fontFamily: BOLD, - fontSize: 10, - lineHeight: Math.round(10 * 1.3) + 2, + fontSize: moderateScale(10), }, M_130: { fontFamily: MEDIUM, - fontSize: 10, - lineHeight: Math.round(10 * 1.3) + 2, + fontSize: moderateScale(10), }, R_130: { fontFamily: REGULAR, - fontSize: 10, - lineHeight: Math.round(10 * 1.3) + 2, + fontSize: moderateScale(10), }, }, }; diff --git a/theme/mapping.json b/theme/mapping.json index a435608..2f1f04e 100644 --- a/theme/mapping.json +++ b/theme/mapping.json @@ -243,6 +243,15 @@ } } } + }, + "Button": { + "appearances": { + "variantGroups": { + "size": { + "large": {} + } + } + } } } } diff --git a/theme/theme.json b/theme/theme.json index 49dfd95..e711c77 100644 --- a/theme/theme.json +++ b/theme/theme.json @@ -1,13 +1,18 @@ { - "color-primary-100": "#D6E4FF", - "color-primary-200": "#ADC8FF", - "color-primary-300": "#84A9FF", - "color-primary-400": "#6690FF", - "color-primary-500": "#3366FF", - "color-primary-600": "#254EDB", - "color-primary-700": "#1939B7", - "color-primary-800": "#102693", - "color-primary-900": "#091A7A", + "color-primary-100": "#E6F0FF", + "color-primary-200": "#B3D6FF", + "color-primary-300": "#80BBFF", + "color-primary-400": "#4DA1FF", + "color-primary-500": "#0578FF", + "color-primary-600": "#0460CC", + "color-primary-700": "#034899", + "color-primary-800": "#023066", + "color-primary-900": "#011833", + + "background-basic-color-1": "#FFFFFF", + "background-basic-color-2": "#F4F6F8", + "background-basic-color-3": "#E8EBF0", + "background-basic-color-4": "#D9DEE4", "Black01": "#111111", "Black02": "#28323C", From 89e12533be7b6f9c0be904804ff573370fde00ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:14:23 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[SZ-533]feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/funnelView/funnelView.jsx | 53 ++++- app/funnelView/stepFiveView.tsx | 279 +++++++++++++------------- app/funnelView/stepFourView.tsx | 323 +++++++++++++++++++++++-------- app/funnelView/stepLastView.tsx | 202 +++++++++++++++++++ app/funnelView/stepOneView.tsx | 23 ++- app/funnelView/stepThreeView.tsx | 315 ++++++++---------------------- app/funnelView/stepTwoView.tsx | 25 ++- 7 files changed, 737 insertions(+), 483 deletions(-) create mode 100644 app/funnelView/stepLastView.tsx diff --git a/app/funnelView/funnelView.jsx b/app/funnelView/funnelView.jsx index d1c7c7b..6bf35ca 100644 --- a/app/funnelView/funnelView.jsx +++ b/app/funnelView/funnelView.jsx @@ -1,5 +1,5 @@ /* eslint-disable react/no-unstable-nested-components */ -import React from 'react'; +import React, { useState } from 'react'; import { useFunnel } from '../../hooks/funnel/useFunnel'; import { Layout, @@ -10,13 +10,20 @@ import { import { useRouter } from 'expo-router'; import NameInputScreen from './stepOneView'; import AgeSelectionScreen from './stepTwoView'; -import SleepTimeScreen from './stepThreeView'; -import DelayReasonsScreen from './stepFourView'; -import CompletionScreen from './stepFiveView'; +import SleepTimeScreen from './stepFourView'; +import DelayReasonsScreen from './stepFiveView'; +import CompletionScreen from './stepLastView'; +import JobSelectionScreen from './stepThreeView'; const FunnelScreen = () => { const { Funnel, setStep, goBack, currentStep } = useFunnel('Step1'); const route = useRouter(); + const [name, setName] = useState(''); + const [selectedAge, setSelectedAge] = useState(null); + const [selectedTime, setSelectedTime] = useState(null); + const [selectedPeriod, setSelectedPeriod] = useState(null); + const [selectedReasons, setSelectedReasons] = useState([]); + const [selectedJob, setSelectedJob] = useState(null); const BackIcon = props => ; @@ -47,6 +54,8 @@ const FunnelScreen = () => { onNext={() => { setStep('Step2'); }} + name={name} + setName={setName} /> @@ -54,25 +63,49 @@ const FunnelScreen = () => { onNext={() => { setStep('Step3'); }} + selectedAge={selectedAge} + setSelectedAge={setSelectedAge} /> - SleepTimeScreen + setStep('Step4')} + selectedJob={selectedJob} + setSelectedJob={setSelectedJob} + /> + + { - setStep('Step4'); + setStep('Step5'); }} + selectedTime={selectedTime} + setSelectedTime={setSelectedTime} + selectedPeriod={selectedPeriod} + setSelectedPeriod={setSelectedPeriod} /> - + { - setStep('Step5'); + setStep('StepLast'); }} + selectedReasons={selectedReasons} + setSelectedReasons={setSelectedReasons} /> - - + + diff --git a/app/funnelView/stepFiveView.tsx b/app/funnelView/stepFiveView.tsx index 27b1583..5dd07e2 100644 --- a/app/funnelView/stepFiveView.tsx +++ b/app/funnelView/stepFiveView.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import { Layout, Text, @@ -6,139 +6,104 @@ import { StyleService, useStyleSheet, } from '@ui-kitten/components'; -import { View, Platform, Linking, TouchableOpacity } from 'react-native'; -import * as Animatable from 'react-native-animatable'; +import { View, TouchableOpacity } from 'react-native'; import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; -import { router } from 'expo-router'; -import messaging from '@react-native-firebase/messaging'; -import ConfirmModal from '@/components/common/molecules/ConfirmModal'; -import useModal from '@/hooks/common/useModal'; +import * as Animatable from 'react-native-animatable'; -const requestNotificationPermission = async () => { - try { - const authStatus = await messaging().hasPermission(); +const DelayReasonsScreen = ({ + onNext, + currentStep = 4, + totalSteps = 5, + selectedReasons, + setSelectedReasons, +}) => { + const styles = useStyleSheet(themedStyles); + const [localSelectedReasons, setLocalSelectedReasons] = + React.useState(selectedReasons); - const enabled = - authStatus === messaging.AuthorizationStatus.AUTHORIZED || - authStatus === messaging.AuthorizationStatus.PROVISIONAL; + const reasons = [ + '할 일들이 너무 크게 느껴져요', + '꾸준히 이어가기 어려워요', + '우선순위를 정하기 어려워요', + '동기 부여가 없어요', + '집중력이 부족해요', + ]; - if (enabled) { - // 권한이 이미 허용됨 - return true; - } else { - if (Platform.OS === 'ios') { - // iOS에서 권한 요청 - const authStatusOnIOS = await messaging().requestPermission(); - const enabledOnIOS = - authStatusOnIOS === messaging.AuthorizationStatus.AUTHORIZED || - authStatusOnIOS === messaging.AuthorizationStatus.PROVISIONAL; - return enabledOnIOS; - } else if (Platform.OS === 'android') { - // Android에서 버전 확인 - const androidVersion = Platform.Version; - if (androidVersion >= 33) { - // Android 13 이상에서 권한 요청 - const result = await messaging().requestPermission(); - const enabledOnAndroid = - result === messaging.AuthorizationStatus.AUTHORIZED; - return enabledOnAndroid; - } else { - // Android 13 미만은 권한 요청 불필요 - return true; - } - } else { - // 기타 플랫폼 (web 등) - return false; + const toggleReason = index => { + setLocalSelectedReasons(prev => { + const isSelected = prev.includes(index); + if (isSelected) { + return prev.filter(i => i !== index); + } else if (prev.length < 3) { + return [...prev, index]; } - } - } catch (error) { - console.error('Notification permission error:', error); - return false; - } -}; - -const CompletionScreen = () => { - const [showNotificationScreen, setShowNotificationScreen] = useState(false); - const styles = useStyleSheet(themedStyles); - const { isVisible, setIsVisible } = useModal(); - - useEffect(() => { - // 3초 후에 알림 허용 화면으로 전환 - const timer = setTimeout(() => { - setShowNotificationScreen(true); - }, 3000); + return prev; + }); + }; - return () => clearTimeout(timer); - }, []); + const renderStepIndicators = () => ( + + {[...Array(totalSteps)].map((_, index) => ( + + ))} + + ); - const handleAllowNotifications = async () => { - const granted = await requestNotificationPermission(); - if (granted) { - router.dismissAll(); - router.replace('/(tabs)'); - } else { - setIsVisible(true); - } - }; + return ( + + {renderStepIndicators()} - if (!showNotificationScreen) { - return ( - + 미루는 이유를 알려주세요 + 최대 3개까지 선택할 수 있어요 + - 환영합니다 OOO님 - 가입이 완료되었어요 + {reasons.map((reason, index) => ( + toggleReason(index)} + > + + {reason} + + + ))} - - ); - } - - return ( - - - - 할 일을 잊지 않도록{'\n'}알려드릴게요 - - - setIsVisible(true)}> - 지금은 괜찮아요 - - - - - - { - setIsVisible(false); - Linking.openSettings(); - router.dismissAll(); - router.replace('/(tabs)'); - }} - onCancel={() => { - setIsVisible(false); - router.dismissAll(); - router.replace('/(tabs)'); - }} - titleKey="views.index.AlertModalTitle" - messageKey="views.index.AlertModalMessage" - confirmTextKey="common.yes" - cancelTextKey="common.no" - /> - + {localSelectedReasons.length > 0 && ( + + + + )} + ); }; @@ -149,44 +114,66 @@ const themedStyles = StyleService.create({ backgroundColor: 'background-basic-color-1', paddingHorizontal: scale(20), }, - completionContainer: { + contentContainer: { flex: 1, + justifyContent: 'space-between', + paddingBottom: verticalScale(20), + }, + stepContainer: { + flexDirection: 'row', justifyContent: 'center', alignItems: 'center', + marginBottom: verticalScale(40), }, - welcomeText: { - fontSize: moderateScale(18), - marginBottom: verticalScale(8), - color: 'text-basic-color', + stepIndicator: { + width: scale(8), + height: scale(8), + borderRadius: scale(4), + backgroundColor: 'color-basic-400', + marginHorizontal: scale(3), }, - completionText: { - fontSize: moderateScale(24), - fontWeight: 'bold', - color: 'text-basic-color', + activeStepIndicator: { + backgroundColor: 'color-primary-500', + width: scale(16), + height: scale(8), }, - notificationContainer: { - flex: 1, - justifyContent: 'space-between', - paddingTop: verticalScale(88), - paddingBottom: verticalScale(30), - }, - notificationTitle: { + title: { + textAlign: 'left', fontSize: moderateScale(28), fontWeight: 'bold', + marginBottom: verticalScale(8), + }, + subtitle: { textAlign: 'left', - lineHeight: moderateScale(35), + fontSize: moderateScale(16), + color: 'text-hint-color', + marginBottom: verticalScale(30), }, - buttonContainer: { - gap: verticalScale(16), + reasonsContainer: { + gap: verticalScale(10), }, - skipText: { - textAlign: 'center', - color: 'text-hint-color', - fontSize: moderateScale(14), + reasonOption: { + padding: moderateScale(16), + borderRadius: moderateScale(8), + backgroundColor: 'background-basic-color-2', + borderWidth: 1, + borderColor: 'transparent', + }, + selectedReason: { + backgroundColor: 'color-primary-100', + borderColor: 'color-primary-500', + }, + reasonText: { + fontSize: moderateScale(16), + color: 'text-basic-color', + }, + selectedReasonText: { + color: 'color-primary-700', + fontWeight: '600', }, button: { borderRadius: moderateScale(8), }, }); -export default CompletionScreen; +export default DelayReasonsScreen; diff --git a/app/funnelView/stepFourView.tsx b/app/funnelView/stepFourView.tsx index 5923b03..a489cc8 100644 --- a/app/funnelView/stepFourView.tsx +++ b/app/funnelView/stepFourView.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import { Layout, Text, @@ -6,34 +6,80 @@ import { StyleService, useStyleSheet, } from '@ui-kitten/components'; -import { View, TouchableOpacity } from 'react-native'; +import { View, TouchableOpacity, Pressable } from 'react-native'; import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import BottomSheet, { + BottomSheetView, + BottomSheetBackdrop, +} from '@gorhom/bottom-sheet'; import * as Animatable from 'react-native-animatable'; -const DelayReasonsScreen = ({ onNext, currentStep = 4, totalSteps = 5 }) => { - const [selectedReasons, setSelectedReasons] = useState([]); +const SleepTimeScreen = ({ + onNext, + currentStep = 3, + totalSteps = 5, + selectedTime, + setSelectedTime, + selectedPeriod, + setSelectedPeriod, +}) => { + const [localSelectedTime, setLocalSelectedTime] = useState(selectedTime); + const [localSelectedPeriod, setLocalSelectedPeriod] = + useState(selectedPeriod); + const [currentSheet, setCurrentSheet] = useState(null); + const [showTimeSelector, setShowTimeSelector] = useState( + localSelectedTime && localSelectedPeriod, + ); const styles = useStyleSheet(themedStyles); - const reasons = [ - '할 일들이 너무 크게 느껴져요', - '꾸준히 이어가기 어려워요', - '우선순위를 정하기 어려워요', - '동기 부여가 없어요', - '집중력이 부족해요', - ]; - - const toggleReason = index => { - setSelectedReasons(prev => { - const isSelected = prev.includes(index); - if (isSelected) { - return prev.filter(i => i !== index); - } else if (prev.length < 3) { - return [...prev, index]; - } - return prev; - }); + // refs + const timeSheetRef = useRef(null); + const periodSheetRef = useRef(null); + + // variables + const snapPoints = useMemo(() => ['50%'], []); + const timeOptions = Array.from({ length: 12 }, (_, i) => `${i + 1}시`); + const periodOptions = ['오전', '오후']; + + // callbacks + const handleSheetChanges = useCallback(index => { + if (index === -1) { + setCurrentSheet(null); + } + }, []); + + const handleClosePress = useCallback(() => { + if (currentSheet === 'time') { + timeSheetRef.current?.close(); + } else if (currentSheet === 'period') { + periodSheetRef.current?.close(); + } + setCurrentSheet(null); + }, [currentSheet]); + + const handlePeriodSelect = period => { + setLocalSelectedPeriod(period); + periodSheetRef.current?.close(); + setShowTimeSelector(true); + }; + + const handleTimeSelect = time => { + setLocalSelectedTime(time); + timeSheetRef.current?.close(); }; + const renderBackdrop = useCallback( + props => ( + + ), + [], + ); + const renderStepIndicators = () => ( {[...Array(totalSteps)].map((_, index) => ( @@ -52,47 +98,132 @@ const DelayReasonsScreen = ({ onNext, currentStep = 4, totalSteps = 5 }) => { {renderStepIndicators()} - 미루는 이유를 알려주세요 - 최대 3개까지 선택할 수 있어요 + 평소 몇시에 주무시나요? - - {reasons.map((reason, index) => ( - toggleReason(index)} + + { + setCurrentSheet('period'); + periodSheetRef.current?.expand(); + }} + > + - + + + + {showTimeSelector && ( + + { + setCurrentSheet('time'); + timeSheetRef.current?.expand(); + }} > - {reason} - - - ))} - - {selectedReasons.length > 0 && ( - + + {localSelectedTime || '시간'} + + + + + )} + + + {localSelectedTime && localSelectedPeriod && ( + )} + + {/* Time Bottom Sheet */} + + + + 시간 선택 + + × + + + + {timeOptions.map((time, index) => ( + handleTimeSelect(time)} + > + {time} + + ))} + + + + + {/* Period Bottom Sheet */} + + + + 시간대 선택 + + × + + + + {periodOptions.map((period, index) => ( + handlePeriodSelect(period)} + > + {period} + + ))} + + + ); }; @@ -103,17 +234,17 @@ const themedStyles = StyleService.create({ backgroundColor: 'background-basic-color-1', paddingHorizontal: scale(20), }, - contentContainer: { - flex: 1, - justifyContent: 'space-between', - paddingBottom: verticalScale(20), - }, stepContainer: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginBottom: verticalScale(40), }, + contentContainer: { + flex: 1, + justifyContent: 'space-between', + paddingBottom: verticalScale(20), // 하단 간격 + }, stepIndicator: { width: scale(8), height: scale(8), @@ -128,41 +259,77 @@ const themedStyles = StyleService.create({ }, title: { textAlign: 'left', + marginBottom: verticalScale(30), fontSize: moderateScale(28), fontWeight: 'bold', - marginBottom: verticalScale(8), }, - subtitle: { - textAlign: 'left', - fontSize: moderateScale(16), + selector: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: moderateScale(15), + borderRadius: moderateScale(8), + backgroundColor: 'background-basic-color-1', + borderWidth: 1, + borderColor: 'color-basic-400', + marginBottom: verticalScale(10), + }, + placeholderText: { color: 'text-hint-color', - marginBottom: verticalScale(30), }, - reasonsContainer: { - gap: verticalScale(10), + selectedText: { + color: 'text-basic-color', }, - reasonOption: { - padding: moderateScale(16), - borderRadius: moderateScale(8), - backgroundColor: 'background-basic-color-2', - borderWidth: 1, - borderColor: 'transparent', + arrow: { + color: 'color-basic-600', }, - selectedReason: { - backgroundColor: 'color-primary-100', - borderColor: 'color-primary-500', + bottomSheetContainer: { + flex: 1, + backgroundColor: 'background-basic-color-1', }, - reasonText: { + bottomSheetHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: moderateScale(15), + borderBottomWidth: 1, + borderBottomColor: 'color-basic-300', + }, + bottomSheetTitle: { fontSize: moderateScale(16), - color: 'text-basic-color', + fontWeight: 'bold', }, - selectedReasonText: { - color: 'color-primary-700', - fontWeight: '600', + closeButton: { + fontSize: moderateScale(24), + color: 'color-basic-600', + padding: moderateScale(5), }, + optionsContainer: { + padding: moderateScale(15), + }, + optionItem: { + padding: moderateScale(15), + }, + optionText: { + textAlign: 'center', + fontSize: moderateScale(16), + }, + buttonContainer: {}, button: { borderRadius: moderateScale(8), }, + timeGridContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + padding: moderateScale(15), + }, + timeGridItem: { + width: '33.33%', + padding: moderateScale(10), + alignItems: 'center', + justifyContent: 'center', + }, + selectorContainer: {}, }); -export default DelayReasonsScreen; +export default SleepTimeScreen; diff --git a/app/funnelView/stepLastView.tsx b/app/funnelView/stepLastView.tsx new file mode 100644 index 0000000..f686e18 --- /dev/null +++ b/app/funnelView/stepLastView.tsx @@ -0,0 +1,202 @@ +import React, { useEffect, useState } from 'react'; +import { + Layout, + Text, + Button, + StyleService, + useStyleSheet, +} from '@ui-kitten/components'; +import { View, Platform, Linking, TouchableOpacity } from 'react-native'; +import * as Animatable from 'react-native-animatable'; +import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; +import { router } from 'expo-router'; +import messaging from '@react-native-firebase/messaging'; +import ConfirmModal from '@/components/common/molecules/ConfirmModal'; +import useModal from '@/hooks/common/useModal'; +import { useStorage } from '@/hooks/auth/useStorage'; + +const requestNotificationPermission = async () => { + try { + const authStatus = await messaging().hasPermission(); + + const enabled = + authStatus === messaging.AuthorizationStatus.AUTHORIZED || + authStatus === messaging.AuthorizationStatus.PROVISIONAL; + + if (enabled) { + // 권한이 이미 허용됨 + return true; + } else { + if (Platform.OS === 'ios') { + // iOS에서 권한 요청 + const authStatusOnIOS = await messaging().requestPermission(); + const enabledOnIOS = + authStatusOnIOS === messaging.AuthorizationStatus.AUTHORIZED || + authStatusOnIOS === messaging.AuthorizationStatus.PROVISIONAL; + return enabledOnIOS; + } else if (Platform.OS === 'android') { + // Android에서 버전 확인 + const androidVersion = Platform.Version; + if (androidVersion >= 33) { + // Android 13 이상에서 권한 요청 + const result = await messaging().requestPermission(); + const enabledOnAndroid = + result === messaging.AuthorizationStatus.AUTHORIZED; + return enabledOnAndroid; + } else { + // Android 13 미만은 권한 요청 불필요 + return true; + } + } else { + // 기타 플랫폼 (web 등) + return false; + } + } + } catch (error) { + console.error('Notification permission error:', error); + return false; + } +}; + +const CompletionScreen = ({ name, userInfo }) => { + const [showNotificationScreen, setShowNotificationScreen] = useState(false); + const styles = useStyleSheet(themedStyles); + const { isVisible, setIsVisible } = useModal(); + const { setItem } = useStorage(); + + useEffect(() => { + // 3초 후에 알림 허용 화면으로 전환 + const timer = setTimeout(() => { + setShowNotificationScreen(true); + }, 3000); + + return () => clearTimeout(timer); + }, []); + + const handleAllowNotifications = async () => { + const granted = await requestNotificationPermission(); + if (granted) { + router.dismissAll(); + router.replace('/(tabs)'); + } else { + setIsVisible(true); + } + }; + + if (!showNotificationScreen) { + return ( + + + 환영합니다 {name}님 + 가입이 완료되었어요 + + + ); + } + + return ( + + + + 할 일을 잊지 않도록{'\n'}알려드릴게요 + + + { + setIsVisible(true); + setItem('userInfo', userInfo); + }} + > + 지금은 괜찮아요 + + + + + + { + setIsVisible(false); + Linking.openSettings(); + router.dismissAll(); + router.replace('/(tabs)'); + }} + onCancel={() => { + setIsVisible(false); + router.dismissAll(); + router.replace('/(tabs)'); + }} + titleKey="views.index.AlertModalTitle" + messageKey="views.index.AlertModalMessage" + confirmTextKey="common.yes" + cancelTextKey="common.no" + /> + + + ); +}; + +const themedStyles = StyleService.create({ + container: { + flex: 1, + backgroundColor: 'background-basic-color-1', + paddingHorizontal: scale(20), + }, + completionContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + welcomeText: { + fontSize: moderateScale(18), + marginBottom: verticalScale(8), + color: 'text-basic-color', + }, + completionText: { + fontSize: moderateScale(24), + fontWeight: 'bold', + color: 'text-basic-color', + }, + notificationContainer: { + flex: 1, + justifyContent: 'space-between', + paddingTop: verticalScale(88), + paddingBottom: verticalScale(30), + }, + notificationTitle: { + fontSize: moderateScale(28), + fontWeight: 'bold', + textAlign: 'left', + lineHeight: moderateScale(35), + }, + buttonContainer: { + gap: verticalScale(16), + }, + skipText: { + textAlign: 'center', + color: 'text-hint-color', + fontSize: moderateScale(14), + }, + button: { + borderRadius: moderateScale(8), + }, +}); + +export default CompletionScreen; diff --git a/app/funnelView/stepOneView.tsx b/app/funnelView/stepOneView.tsx index 57a4fa9..186698d 100644 --- a/app/funnelView/stepOneView.tsx +++ b/app/funnelView/stepOneView.tsx @@ -11,9 +11,15 @@ import * as Animatable from 'react-native-animatable'; import { View, KeyboardAvoidingView, Platform } from 'react-native'; import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; -const NameInputScreen = ({ onNext, currentStep = 0, totalSteps = 5 }) => { - const [name, setName] = React.useState(''); +const NameInputScreen = ({ + onNext, + currentStep = 0, + totalSteps = 5, + name, + setName, +}) => { const styles = useStyleSheet(themedStyles); + const [inputContent, setInputContent] = React.useState(name); const renderStepIndicators = () => { return ( @@ -50,15 +56,15 @@ const NameInputScreen = ({ onNext, currentStep = 0, totalSteps = 5 }) => { > - {name !== '' && ( + {inputContent !== '' && ( { diff --git a/app/funnelView/stepThreeView.tsx b/app/funnelView/stepThreeView.tsx index ca657ce..b1f0e0f 100644 --- a/app/funnelView/stepThreeView.tsx +++ b/app/funnelView/stepThreeView.tsx @@ -1,206 +1,93 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; +import React from 'react'; import { Layout, - Text, - Button, StyleService, useStyleSheet, + Text, + Button, } from '@ui-kitten/components'; -import { View, TouchableOpacity, Pressable } from 'react-native'; +import { View, TouchableOpacity } from 'react-native'; import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; -import BottomSheet, { - BottomSheetView, - BottomSheetBackdrop, -} from '@gorhom/bottom-sheet'; import * as Animatable from 'react-native-animatable'; -const SleepTimeScreen = ({ onNext, currentStep = 2, totalSteps = 5 }) => { - const [selectedTime, setSelectedTime] = useState(null); - const [selectedPeriod, setSelectedPeriod] = useState(null); - const [currentSheet, setCurrentSheet] = useState(null); - const [showTimeSelector, setShowTimeSelector] = useState(false); +const JobSelectionScreen = ({ + onNext, + currentStep = 1, + totalSteps = 5, + selectedJob, + setSelectedJob, +}) => { const styles = useStyleSheet(themedStyles); - - // refs - const timeSheetRef = useRef(null); - const periodSheetRef = useRef(null); - - // variables - const snapPoints = useMemo(() => ['50%'], []); - const timeOptions = Array.from({ length: 12 }, (_, i) => `${i + 1}시`); - const periodOptions = ['오전', '오후']; - - // callbacks - const handleSheetChanges = useCallback(index => { - if (index === -1) { - setCurrentSheet(null); - } - }, []); - - const handleClosePress = useCallback(() => { - if (currentSheet === 'time') { - timeSheetRef.current?.close(); - } else if (currentSheet === 'period') { - periodSheetRef.current?.close(); - } - setCurrentSheet(null); - }, [currentSheet]); - - const handlePeriodSelect = period => { - setSelectedPeriod(period); - periodSheetRef.current?.close(); - setShowTimeSelector(true); - }; - - const handleTimeSelect = time => { - setSelectedTime(time); - timeSheetRef.current?.close(); + const [selectedItem, setSelectedItem] = React.useState(selectedJob); + + const JobRanges = ['중·고등학생', '대학생', '직장인', '자영업자', '기타']; + + const renderStepIndicators = () => { + return ( + + {[...Array(totalSteps)].map((_, index) => ( + + ))} + + ); }; - const renderBackdrop = useCallback( - props => ( - - ), - [], - ); + const renderJobOption = (job, index) => { + const isSelected = selectedItem === index; - const renderStepIndicators = () => ( - - {[...Array(totalSteps)].map((_, index) => ( - - ))} - - ); + return ( + setSelectedItem(index)} + style={[styles.ageOption, isSelected && styles.selectedAgeOption]} + > + + {job} + + + ); + }; return ( {renderStepIndicators()} - 평소 몇시에 주무시나요? - - - { - setCurrentSheet('period'); - periodSheetRef.current?.expand(); - }} - > - - {selectedPeriod || '시간대'} - - - - - {showTimeSelector && ( - - { - setCurrentSheet('time'); - timeSheetRef.current?.expand(); - }} - > - - {selectedTime || '시간'} - - - - - )} - + 직업을 알려주세요 - {selectedTime && selectedPeriod && ( + + + {JobRanges.map((age, index) => renderJobOption(age, index))} + + + {selectedItem !== null && ( - )} - - {/* Time Bottom Sheet */} - - - - 시간 선택 - - × - - - - {timeOptions.map((time, index) => ( - handleTimeSelect(time)} - > - {time} - - ))} - - - - - {/* Period Bottom Sheet */} - - - - 시간대 선택 - - × - - - - {periodOptions.map((period, index) => ( - handlePeriodSelect(period)} - > - {period} - - ))} - - - ); }; @@ -217,11 +104,6 @@ const themedStyles = StyleService.create({ alignItems: 'center', marginBottom: verticalScale(40), }, - contentContainer: { - flex: 1, - justifyContent: 'space-between', - paddingBottom: verticalScale(20), // 하단 간격 - }, stepIndicator: { width: scale(8), height: scale(8), @@ -239,74 +121,35 @@ const themedStyles = StyleService.create({ marginBottom: verticalScale(30), fontSize: moderateScale(28), fontWeight: 'bold', + color: 'text-basic-color', }, - selector: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: moderateScale(15), + optionsContainer: {}, + ageOption: { + backgroundColor: 'background-basic-color-2', + padding: moderateScale(16), borderRadius: moderateScale(8), - backgroundColor: 'background-basic-color-1', - borderWidth: 1, - borderColor: 'color-basic-400', marginBottom: verticalScale(10), }, - placeholderText: { - color: 'text-hint-color', + selectedAgeOption: { + backgroundColor: 'color-primary-100', }, - selectedText: { + ageText: { + fontSize: moderateScale(16), color: 'text-basic-color', }, - arrow: { - color: 'color-basic-600', - }, - bottomSheetContainer: { - flex: 1, - backgroundColor: 'background-basic-color-1', - }, - bottomSheetHeader: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: moderateScale(15), - borderBottomWidth: 1, - borderBottomColor: 'color-basic-300', - }, - bottomSheetTitle: { - fontSize: moderateScale(16), + selectedAgeText: { + color: 'color-primary-700', fontWeight: 'bold', }, - closeButton: { - fontSize: moderateScale(24), - color: 'color-basic-600', - padding: moderateScale(5), - }, - optionsContainer: { - padding: moderateScale(15), - }, - optionItem: { - padding: moderateScale(15), - }, - optionText: { - textAlign: 'center', - fontSize: moderateScale(16), - }, - buttonContainer: {}, button: { borderRadius: moderateScale(8), }, - timeGridContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - padding: moderateScale(15), - }, - timeGridItem: { - width: '33.33%', - padding: moderateScale(10), - alignItems: 'center', - justifyContent: 'center', + buttonContainer: {}, + contentContainer: { + flex: 1, + justifyContent: 'space-between', + paddingBottom: verticalScale(20), // 하단 간격 }, - selectorContainer: {}, }); -export default SleepTimeScreen; +export default JobSelectionScreen; diff --git a/app/funnelView/stepTwoView.tsx b/app/funnelView/stepTwoView.tsx index 0e51a9f..a233096 100644 --- a/app/funnelView/stepTwoView.tsx +++ b/app/funnelView/stepTwoView.tsx @@ -10,9 +10,15 @@ import { View, TouchableOpacity } from 'react-native'; import { scale, verticalScale, moderateScale } from 'react-native-size-matters'; import * as Animatable from 'react-native-animatable'; -const AgeSelectionScreen = ({ onNext, currentStep = 1, totalSteps = 5 }) => { - const [selectedAge, setSelectedAge] = React.useState(null); +const AgeSelectionScreen = ({ + onNext, + currentStep = 1, + totalSteps = 5, + selectedAge, + setSelectedAge, +}) => { const styles = useStyleSheet(themedStyles); + const [selectedItem, setSelectedItem] = React.useState(selectedAge); const ageRanges = ['10대', '20대', '30대', '40대', '50대 이상']; @@ -33,12 +39,12 @@ const AgeSelectionScreen = ({ onNext, currentStep = 1, totalSteps = 5 }) => { }; const renderAgeOption = (age, index) => { - const isSelected = selectedAge === index; + const isSelected = selectedItem === index; return ( setSelectedAge(index)} + onPress={() => setSelectedItem(index)} style={[styles.ageOption, isSelected && styles.selectedAgeOption]} > @@ -63,13 +69,20 @@ const AgeSelectionScreen = ({ onNext, currentStep = 1, totalSteps = 5 }) => { {ageRanges.map((age, index) => renderAgeOption(age, index))} - {selectedAge !== null && ( + {selectedItem !== null && ( - From 969aba6ddd04613cb853bebabfd04edd32bb2c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:21:15 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[SZ-533]feat:=20=EC=8B=A0=EA=B7=9C=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EB=A7=8C=20=EC=98=A8=EB=B3=B4=EB=94=A9=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EA=B0=80=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/auth/useLogin.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hooks/auth/useLogin.js b/hooks/auth/useLogin.js index 8ad3c02..bc8be1e 100644 --- a/hooks/auth/useLogin.js +++ b/hooks/auth/useLogin.js @@ -32,12 +32,10 @@ const useLogin = () => { await setAsyncStorageLoginInfo(jwtTokenData, user); setUserId(jwtTokenData.userId); setIsLoggedIn(true); - - if (user.isNew) { + if (jwtTokenData.isNew) { router.push('/funnelView/funnelView'); } else { - router.push('/funnelView/funnelView'); - // router.push('/(tabs)'); + router.push('/(tabs)'); } } }; From 7e92f53c96fb7886427044f5e4b0dea80e962725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=B3=91=EC=B0=AC?= <70642609+byungchanKo99@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:33:15 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[SZ-533]fix:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=9C=EA=B2=AC=20=EB=B0=8F=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 미루는 이유가 하나 부족했음 - 마지막에 다음에 할래요 시 로컬에 스트링으로 저장하지 않았음 - 세번째 화면에 인디케이터가 두번째를 나타내고 있었음 --- app/funnelView/stepFiveView.tsx | 1 + app/funnelView/stepLastView.tsx | 2 +- app/funnelView/stepThreeView.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/funnelView/stepFiveView.tsx b/app/funnelView/stepFiveView.tsx index 5dd07e2..8a5145e 100644 --- a/app/funnelView/stepFiveView.tsx +++ b/app/funnelView/stepFiveView.tsx @@ -27,6 +27,7 @@ const DelayReasonsScreen = ({ '우선순위를 정하기 어려워요', '동기 부여가 없어요', '집중력이 부족해요', + '심리적으로 불안해요', ]; const toggleReason = index => { diff --git a/app/funnelView/stepLastView.tsx b/app/funnelView/stepLastView.tsx index f686e18..34fe839 100644 --- a/app/funnelView/stepLastView.tsx +++ b/app/funnelView/stepLastView.tsx @@ -112,7 +112,7 @@ const CompletionScreen = ({ name, userInfo }) => { { setIsVisible(true); - setItem('userInfo', userInfo); + setItem('userInfo', JSON.stringify(userInfo)); }} > 지금은 괜찮아요 diff --git a/app/funnelView/stepThreeView.tsx b/app/funnelView/stepThreeView.tsx index b1f0e0f..d4b2515 100644 --- a/app/funnelView/stepThreeView.tsx +++ b/app/funnelView/stepThreeView.tsx @@ -12,7 +12,7 @@ import * as Animatable from 'react-native-animatable'; const JobSelectionScreen = ({ onNext, - currentStep = 1, + currentStep = 2, totalSteps = 5, selectedJob, setSelectedJob,