diff --git a/app/FunnelScreen.jsx b/app/FunnelScreen.jsx
deleted file mode 100644
index f484ccd..0000000
--- a/app/FunnelScreen.jsx
+++ /dev/null
@@ -1,269 +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,
- 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';
-
-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 */}
-
-
-
- 직업과 나이를 선택해주세요
-
-
- {/* 직업 드롭다운 */}
-
-
- {/* 직업이 선택되면 나이 드롭다운 표시 */}
- {selectedJobIndex !== null && (
-
-
-
- )}
-
- {/* 나이가 선택되면 다음 버튼 표시 */}
- {selectedAgeIndex !== null && (
-
-
-
- )}
-
-
-
- {/* 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/_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/funnelView/funnelView.jsx b/app/funnelView/funnelView.jsx
new file mode 100644
index 0000000..6bf35ca
--- /dev/null
+++ b/app/funnelView/funnelView.jsx
@@ -0,0 +1,115 @@
+/* eslint-disable react/no-unstable-nested-components */
+import React, { useState } 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 './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 => ;
+
+ const BackAction = () => (
+
+ );
+
+ const handleBackAction = () => {
+ if (currentStep === 'Step1') {
+ route.back();
+ } else {
+ goBack();
+ }
+ };
+
+ return (
+
+
+
+
+
+ {
+ setStep('Step2');
+ }}
+ name={name}
+ setName={setName}
+ />
+
+
+ {
+ setStep('Step3');
+ }}
+ selectedAge={selectedAge}
+ setSelectedAge={setSelectedAge}
+ />
+
+
+ setStep('Step4')}
+ selectedJob={selectedJob}
+ setSelectedJob={setSelectedJob}
+ />
+
+
+ {
+ setStep('Step5');
+ }}
+ selectedTime={selectedTime}
+ setSelectedTime={setSelectedTime}
+ selectedPeriod={selectedPeriod}
+ setSelectedPeriod={setSelectedPeriod}
+ />
+
+
+ {
+ setStep('StepLast');
+ }}
+ selectedReasons={selectedReasons}
+ setSelectedReasons={setSelectedReasons}
+ />
+
+
+
+
+
+
+ );
+};
+
+export default FunnelScreen;
diff --git a/app/funnelView/stepFiveView.tsx b/app/funnelView/stepFiveView.tsx
new file mode 100644
index 0000000..8a5145e
--- /dev/null
+++ b/app/funnelView/stepFiveView.tsx
@@ -0,0 +1,180 @@
+import React 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,
+ selectedReasons,
+ setSelectedReasons,
+}) => {
+ const styles = useStyleSheet(themedStyles);
+ const [localSelectedReasons, setLocalSelectedReasons] =
+ React.useState(selectedReasons);
+
+ const reasons = [
+ '할 일들이 너무 크게 느껴져요',
+ '꾸준히 이어가기 어려워요',
+ '우선순위를 정하기 어려워요',
+ '동기 부여가 없어요',
+ '집중력이 부족해요',
+ '심리적으로 불안해요',
+ ];
+
+ 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];
+ }
+ return prev;
+ });
+ };
+
+ const renderStepIndicators = () => (
+
+ {[...Array(totalSteps)].map((_, index) => (
+
+ ))}
+
+ );
+
+ return (
+
+ {renderStepIndicators()}
+
+ 미루는 이유를 알려주세요
+ 최대 3개까지 선택할 수 있어요
+
+
+ {reasons.map((reason, index) => (
+ toggleReason(index)}
+ >
+
+ {reason}
+
+
+ ))}
+
+ {localSelectedReasons.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/stepFourView.tsx b/app/funnelView/stepFourView.tsx
new file mode 100644
index 0000000..a489cc8
--- /dev/null
+++ b/app/funnelView/stepFourView.tsx
@@ -0,0 +1,335 @@
+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 = 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);
+
+ // 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) => (
+
+ ))}
+
+ );
+
+ return (
+
+ {renderStepIndicators()}
+
+ 평소 몇시에 주무시나요?
+
+
+ {
+ setCurrentSheet('period');
+ periodSheetRef.current?.expand();
+ }}
+ >
+
+ {localSelectedPeriod || '시간대'}
+
+ ∨
+
+
+ {showTimeSelector && (
+
+ {
+ setCurrentSheet('time');
+ timeSheetRef.current?.expand();
+ }}
+ >
+
+ {localSelectedTime || '시간'}
+
+ ∨
+
+
+ )}
+
+
+ {localSelectedTime && localSelectedPeriod && (
+
+
+
+ )}
+
+
+ {/* 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/stepLastView.tsx b/app/funnelView/stepLastView.tsx
new file mode 100644
index 0000000..34fe839
--- /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', JSON.stringify(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
new file mode 100644
index 0000000..186698d
--- /dev/null
+++ b/app/funnelView/stepOneView.tsx
@@ -0,0 +1,147 @@
+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,
+ name,
+ setName,
+}) => {
+ const styles = useStyleSheet(themedStyles);
+ const [inputContent, setInputContent] = React.useState(name);
+
+ const renderStepIndicators = () => {
+ return (
+
+ {[...Array(totalSteps)].map((_, index) => (
+
+ ))}
+
+ );
+ };
+
+ return (
+
+
+ {renderStepIndicators()}
+
+ 이름을 입력해주세요
+
+
+
+
+
+
+ {inputContent !== '' && (
+
+
+
+ )}
+
+
+
+ );
+};
+
+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..d4b2515
--- /dev/null
+++ b/app/funnelView/stepThreeView.tsx
@@ -0,0 +1,155 @@
+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 JobSelectionScreen = ({
+ onNext,
+ currentStep = 2,
+ totalSteps = 5,
+ selectedJob,
+ setSelectedJob,
+}) => {
+ const styles = useStyleSheet(themedStyles);
+ const [selectedItem, setSelectedItem] = React.useState(selectedJob);
+
+ const JobRanges = ['중·고등학생', '대학생', '직장인', '자영업자', '기타'];
+
+ const renderStepIndicators = () => {
+ return (
+
+ {[...Array(totalSteps)].map((_, index) => (
+
+ ))}
+
+ );
+ };
+
+ const renderJobOption = (job, index) => {
+ const isSelected = selectedItem === index;
+
+ return (
+ setSelectedItem(index)}
+ style={[styles.ageOption, isSelected && styles.selectedAgeOption]}
+ >
+
+ {job}
+
+
+ );
+ };
+
+ return (
+
+ {renderStepIndicators()}
+
+ 직업을 알려주세요
+
+
+
+ {JobRanges.map((age, index) => renderJobOption(age, index))}
+
+
+ {selectedItem !== 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 JobSelectionScreen;
diff --git a/app/funnelView/stepTwoView.tsx b/app/funnelView/stepTwoView.tsx
new file mode 100644
index 0000000..a233096
--- /dev/null
+++ b/app/funnelView/stepTwoView.tsx
@@ -0,0 +1,155 @@
+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,
+ selectedAge,
+ setSelectedAge,
+}) => {
+ const styles = useStyleSheet(themedStyles);
+ const [selectedItem, setSelectedItem] = React.useState(selectedAge);
+
+ const ageRanges = ['10대', '20대', '30대', '40대', '50대 이상'];
+
+ const renderStepIndicators = () => {
+ return (
+
+ {[...Array(totalSteps)].map((_, index) => (
+
+ ))}
+
+ );
+ };
+
+ const renderAgeOption = (age, index) => {
+ const isSelected = selectedItem === index;
+
+ return (
+ setSelectedItem(index)}
+ style={[styles.ageOption, isSelected && styles.selectedAgeOption]}
+ >
+
+ {age}
+
+
+ );
+ };
+
+ return (
+
+ {renderStepIndicators()}
+
+ 연령대를 알려주세요
+
+
+
+ {ageRanges.map((age, index) => renderAgeOption(age, index))}
+
+
+ {selectedItem !== 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..bc8be1e 100644
--- a/hooks/auth/useLogin.js
+++ b/hooks/auth/useLogin.js
@@ -32,8 +32,11 @@ const useLogin = () => {
await setAsyncStorageLoginInfo(jwtTokenData, user);
setUserId(jwtTokenData.userId);
setIsLoggedIn(true);
-
- router.push('/(tabs)');
+ if (jwtTokenData.isNew) {
+ router.push('/funnelView/funnelView');
+ } else {
+ 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",