diff --git a/assets/icons/section/caring_dog.svg b/assets/icons/section/caring_dog.svg
new file mode 100644
index 0000000..5208356
--- /dev/null
+++ b/assets/icons/section/caring_dog.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/app/navigation/RootNavigator.tsx b/src/app/navigation/RootNavigator.tsx
index 17453a7..fd06fc1 100644
--- a/src/app/navigation/RootNavigator.tsx
+++ b/src/app/navigation/RootNavigator.tsx
@@ -1,37 +1,48 @@
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
-import { useSessionStore } from '@shared/store/useSessionStore';
-import LoginScreen from '@screens/auth/Login/LoginScreen';
-import SignupScreen from '@screens/auth/Signup/SignupScreen';
-import { LinkAccountScreen } from '@screens/auth/LinkAccount/LinkAccountScreen';
+
+import { useSessionStore } from '../../shared/store/useSessionStore';
+import LoginScreen from '../../screens/auth/Login/LoginScreen';
+import SignupScreen from '../../screens/auth/Signup/component/SignupScreen';
+import { SignupTypeSelectScreen } from '../../screens/auth/Signup/component/SignupTypeSelectScreen';
+import TermsAgreementScreen from '../../screens/auth/terms/TermsAgreementScreen';
+import { LinkAccountScreen } from '../../screens/auth/LinkAccount/LinkAccountScreen';
import { GuardianStackNavigator } from './GuardianStackNavigator';
-// 👇 어르신 메인 화면 컴포넌트 경로를 올바른 메인 화면으로 지정해주세요 (예: SeniorHomeTabs 또는 SeniorMainScreen 등)
-import SeniorHomeScreen from '@screens/senior/Home/SeniorHomeScreen';
+import SeniorHomeScreen from '../../screens/senior/Home/SeniorHomeScreen';
+import { SeniorScheduleScreen } from '../../screens/senior/Schedule/SeniorScheduleScreen';
const Stack = createNativeStackNavigator();
-export function RootNavigator() {
+export const RootNavigator = () => {
const isLoggedIn = useSessionStore(state => state.isLoggedIn);
const role = useSessionStore(state => state.role);
+ // 1. 보호자(PROTECTOR)로 로그인된 경우
if (isLoggedIn && role === 'PROTECTOR') {
return ;
}
+ // 2. 피보호자/어르신(WARD)으로 로그인된 경우
if (isLoggedIn && role === 'WARD') {
return (
- {/* 어르신으로 진입 시 보여질 메인 화면 컴포넌트 연결 */}
+ {/* 필요한 경우 어르신용 추가 스크린을 여기에 배치할 수 있습니다 */}
);
}
+ // 3. 비로그인 상태 (인증 및 회원가입 관련 스크린 제공)
return (
-
+
+
+
+
+
+
);
-}
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/screens/auth/Login/LoginScreen.tsx b/src/screens/auth/Login/LoginScreen.tsx
index d1b5fe3..10d4233 100644
--- a/src/screens/auth/Login/LoginScreen.tsx
+++ b/src/screens/auth/Login/LoginScreen.tsx
@@ -1,174 +1,214 @@
import React, { useState } from 'react';
-import { StyleSheet, Text, View, TextInput, TouchableOpacity } from 'react-native';
-import { useSessionStore } from '@shared/store/useSessionStore';
+import { SafeAreaView, View, Text, TextInput, TouchableOpacity, ScrollView, StyleSheet } from 'react-native';
+import { useSessionStore } from '../../../shared/store/useSessionStore';
-export default function LoginScreen() {
+export default function LoginScreen({ navigation }: { navigation: any }) {
const [id, setId] = useState('');
const [password, setPassword] = useState('');
- return (
-
-
+ const handleLogin = () => {
+ console.log('로그인 시도:', id, password);
+ };
+
+ // [DEV] 보호자 진입 핸들러
+ const handleDevProtector = () => {
+ useSessionStore.setState({ isLoggedIn: true, role: 'PROTECTOR' });
+ };
- {__DEV__ && (
-
- useSessionStore.getState().login('PROTECTOR')}>
- [DEV] 보호자로 바로 진입
+ // [DEV] 어르신 진입 핸들러
+ const handleDevSenior = () => {
+ useSessionStore.setState({ isLoggedIn: true, role: 'WARD' });
+ };
+
+ return (
+
+
+
+ {/* 상단 [DEV] 버튼 영역 */}
+
+
+ [DEV] 보호자로 바로 진입
- useSessionStore.getState().login('WARD')}>
- [DEV] 어르신으로 바로 진입
+
+ [DEV] 어르신으로 바로 진입
- )}
- 돌봄의 시작, 케어링
- Caring
+ {/* 타이틀 영역 */}
+
+ 돌봄의 시작, 케어링
+ Caring
+
+
+ {/* 로그인 입력 폼 */}
+
+
-
-
-
-
+
-
- 로그인
-
+
+ 로그인
+
-
- 아이디 찾기
- |
- 비밀번호 찾기
- |
- 회원가입
-
+ {/* 하단 링크 영역 */}
+
+
+ 아이디 찾기
+
+
+ |
+
+
+ 비밀번호 찾기
+
+
+ |
+
+ navigation.navigate('SignupTypeSelect')}>
+ 회원가입
+
+
+
-
- 간편 로그인
-
-
-
-
+ {/* 간편 로그인 원형 버튼 영역 */}
+
+ 간편 로그인
+
+
+
+
+
-
-
+
+
+
);
}
-const styles = StyleSheet.create({
- container: {
+const loginStyles = StyleSheet.create({
+ safeArea: {
flex: 1,
backgroundColor: '#FFFFFF',
- paddingHorizontal: 24,
+ },
+ scrollContainer: {
+ padding: 20,
+ flexGrow: 1,
+ justifyContent: 'center',
+ },
+ devButtonContainer: {
+ marginBottom: 20,
+ gap: 8,
+ },
+ devButton: {
+ borderWidth: 1,
+ borderColor: '#FF7F00',
+ borderRadius: 8,
+ paddingVertical: 10,
alignItems: 'center',
},
- spacer: {
- flex: 0.5,
+ devButtonText: {
+ color: '#FF7F00',
+ fontSize: 13,
+ fontWeight: '600',
+ },
+ titleContainer: {
+ alignItems: 'center',
+ marginBottom: 30,
},
subTitle: {
- fontSize: 26,
- fontWeight: 'bold',
- color: '#333333',
- marginBottom: 8,
+ fontSize: 16,
+ color: '#666',
+ marginBottom: 5,
},
mainTitle: {
- fontSize: 56,
+ fontSize: 32,
fontWeight: 'bold',
- color: '#FD7E14',
- marginBottom: 40,
+ color: '#FF7F00',
},
- inputContainer: {
+ formContainer: {
width: '100%',
- marginBottom: 20,
+ marginBottom: 30,
},
input: {
width: '100%',
height: 50,
borderWidth: 1,
- borderColor: '#E0E0E0',
+ borderColor: '#DDD',
borderRadius: 8,
paddingHorizontal: 15,
- marginBottom: 12,
+ fontSize: 15,
backgroundColor: '#FAFAFA',
+ marginBottom: 12,
+ color: '#000',
},
loginButton: {
width: '100%',
height: 50,
- backgroundColor: '#FD7E14',
+ backgroundColor: '#FF7F00',
+ borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
- borderRadius: 8,
- marginBottom: 20,
+ marginTop: 8,
},
loginButtonText: {
- color: '#FFFFFF',
+ color: '#FFF',
fontSize: 16,
fontWeight: 'bold',
},
- findMenuContainer: {
+ linkRow: {
flexDirection: 'row',
+ justifyContent: 'center',
alignItems: 'center',
- marginBottom: 40,
+ marginTop: 15,
},
- findMenuText: {
- fontSize: 12,
- color: '#666666',
+ linkText: {
+ color: '#666',
+ fontSize: 13,
},
- divider: {
+ barText: {
+ color: '#DDD',
marginHorizontal: 10,
- color: '#CCCCCC',
- fontSize: 12,
},
socialContainer: {
- width: '100%',
alignItems: 'center',
},
socialTitle: {
- fontSize: 12,
- color: '#999999',
- marginBottom: 15,
- },
- socialButtonsRow: {
- flexDirection: 'row',
- justifyContent: 'center',
- gap: 20,
- },
- circleButton: {
- width: 44,
- height: 44,
- borderRadius: 22,
- },
- devRow: {
- width: '100%',
- gap: 8,
+ fontSize: 13,
+ color: '#888',
marginBottom: 12,
},
- devButton: {
- width: '100%',
- paddingVertical: 10,
- borderRadius: 8,
- borderWidth: 1,
- borderColor: '#FD7E14',
- alignItems: 'center',
+ socialIconRow: {
+ flexDirection: 'row',
+ gap: 15,
},
- devButtonText: {
- color: '#FD7E14',
- fontSize: 13,
- fontWeight: '600',
+ socialCircle: {
+ width: 40,
+ height: 40,
+ borderRadius: 20,
},
-});
+});
\ No newline at end of file
diff --git a/src/screens/auth/Signup/component/BirthDatePicker.tsx b/src/screens/auth/Signup/component/BirthDatePicker.tsx
new file mode 100644
index 0000000..1839150
--- /dev/null
+++ b/src/screens/auth/Signup/component/BirthDatePicker.tsx
@@ -0,0 +1,167 @@
+import React, { useState, useEffect } from 'react';
+import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
+import { Calendar, LocaleConfig } from 'react-native-calendars';
+import { BirthDatePickerProps } from '../types/birthDatePicker.types';
+
+// 달력 한국어 설정
+LocaleConfig.locales['kr'] = {
+ monthNames: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
+ monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
+ dayNames: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'],
+ dayNamesShort: ['일', '월', '화', '수', '목', '금', '토'],
+ today: '오늘',
+};
+LocaleConfig.defaultLocale = 'kr';
+
+// YYYY-MM-DD 포맷 변환 함수
+const formatDate = (date: Date) => {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+};
+
+export default function BirthDatePicker({ value, onChange }: BirthDatePickerProps) {
+ // 선택된 날짜 문자열 관리
+ const [selectedDate, setSelectedDate] = useState(value || '');
+
+ // 외부 props(value) 변경 시 내부 상태 동기화
+ useEffect(() => {
+ if (value) {
+ setSelectedDate(value);
+ }
+ }, [value]);
+
+ // 달력 표시 기준 Date 객체
+ const [currentDate, setCurrentDate] = useState(() => {
+ if (value) {
+ const parsed = new Date(value);
+ if (!isNaN(parsed.getTime())) return parsed;
+ }
+ return new Date();
+ });
+
+ // 연도 변경 (-1년 / +1년)
+ const changeYear = (amount: number) => {
+ const nextDate = new Date(currentDate);
+ nextDate.setFullYear(nextDate.getFullYear() + amount);
+ setCurrentDate(nextDate);
+ };
+
+ // 월 변경 (-1달 / +1달)
+ const changeMonth = (amount: number) => {
+ const nextDate = new Date(currentDate);
+ nextDate.setMonth(nextDate.getMonth() + amount);
+ setCurrentDate(nextDate);
+ };
+
+ const formattedDateString = formatDate(currentDate);
+ const currentYear = currentDate.getFullYear();
+ const currentMonth = currentDate.getMonth() + 1;
+
+ // 날짜 클릭 이벤트
+ const handleDayPress = (day: { dateString: string }) => {
+ const newDateStr = day.dateString;
+ setSelectedDate(newDateStr);
+ if (onChange) {
+ onChange(newDateStr);
+ }
+ };
+
+ return (
+
+ {/* 상단 고정 커스텀 헤더 (년도 및 월 조작) */}
+
+ {/* 이전 연도 / 이전 월 버튼 */}
+
+ changeYear(-1)} style={customStyles.arrowBtn}>
+ «
+
+ changeMonth(-1)} style={customStyles.arrowBtn}>
+ ‹
+
+
+
+ {/* 현재 년도 및 월 표시 */}
+ {`${currentYear}년 ${currentMonth}월`}
+
+ {/* 다음 월 / 다음 연도 버튼 */}
+
+ changeMonth(1)} style={customStyles.arrowBtn}>
+ ›
+
+ changeYear(1)} style={customStyles.arrowBtn}>
+ »
+
+
+
+
+ {/* 고정 달력 */}
+ null}
+ onDayPress={handleDayPress}
+ theme={{
+ todayTextColor: '#FF7A00',
+ }}
+ />
+
+ );
+}
+
+const customStyles = StyleSheet.create({
+ container: {
+ borderWidth: 1,
+ borderColor: '#E2E8F0',
+ borderRadius: 12,
+ overflow: 'hidden',
+ backgroundColor: '#fff',
+ marginTop: 4,
+ },
+ headerContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ paddingHorizontal: 12,
+ paddingVertical: 10,
+ backgroundColor: '#fff',
+ borderBottomWidth: 1,
+ borderBottomColor: '#eee',
+ },
+ btnGroup: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ headerTitle: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ color: '#333',
+ },
+ arrowBtn: {
+ paddingHorizontal: 6,
+ paddingVertical: 2,
+ },
+ yearBtnText: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#FF7A00',
+ },
+ monthBtnText: {
+ fontSize: 22,
+ fontWeight: 'bold',
+ color: '#555',
+ },
+});
\ No newline at end of file
diff --git a/src/screens/auth/Signup/component/CaringDogIcon.tsx b/src/screens/auth/Signup/component/CaringDogIcon.tsx
new file mode 100644
index 0000000..1a771b5
--- /dev/null
+++ b/src/screens/auth/Signup/component/CaringDogIcon.tsx
@@ -0,0 +1,25 @@
+import React from 'react';
+import { SvgXml } from 'react-native-svg';
+
+// 피그마에서 [Copy as SVG]로 복사한 전체 코드 붙여넣기
+const dogXml = `
+`;
+
+interface CaringDogIconProps {
+ width?: number;
+ height?: number;
+}
+
+export const CaringDogIcon = ({ width = 180, height = 180 }: CaringDogIconProps) => {
+ return ;
+};
+
+export default CaringDogIcon;
\ No newline at end of file
diff --git a/src/screens/auth/Signup/component/CaringLogo.tsx b/src/screens/auth/Signup/component/CaringLogo.tsx
new file mode 100644
index 0000000..b90072b
--- /dev/null
+++ b/src/screens/auth/Signup/component/CaringLogo.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { SvgXml } from 'react-native-svg';
+
+// 피그마에서 케어링 로고 [Copy as SVG]한 코드 또는 로고 SVG 코드
+const logoXml = `
+`;
+
+interface CaringLogoProps {
+ width?: number;
+ height?: number;
+}
+
+export const CaringLogo = ({ width = 44, height = 44 }: CaringLogoProps) => {
+ return ;
+};
+
+export default CaringLogo;
\ No newline at end of file
diff --git a/src/screens/auth/Signup/component/RoleSelectButton.tsx b/src/screens/auth/Signup/component/RoleSelectButton.tsx
new file mode 100644
index 0000000..7392862
--- /dev/null
+++ b/src/screens/auth/Signup/component/RoleSelectButton.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { TouchableOpacity, Text } from 'react-native';
+import { signupTypeSelectStyles } from '../styles/signupTypeSelect.styles';
+
+interface RoleSelectButtonProps {
+ title: string;
+ onPress: () => void;
+}
+
+export default function RoleSelectButton({ title, onPress }: RoleSelectButtonProps) {
+ return (
+
+ {title}
+
+ );
+}
\ No newline at end of file
diff --git a/src/screens/auth/Signup/component/SignupScreen.tsx b/src/screens/auth/Signup/component/SignupScreen.tsx
new file mode 100644
index 0000000..af8734d
--- /dev/null
+++ b/src/screens/auth/Signup/component/SignupScreen.tsx
@@ -0,0 +1,235 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ TextInput,
+ TouchableOpacity,
+ ScrollView,
+ SafeAreaView,
+} from 'react-native';
+
+import { signupStyles } from '../styles/signup.styles';
+import useSignUp from '../hooks/useSignUp';
+import BirthDatePicker from './BirthDatePicker';
+
+// 로고 SVG import
+import LogoIcon from '@assets/icons/header/caring_logo.svg';
+
+export const SignupScreen = () => {
+ const signUpHook = useSignUp() as any;
+
+ // 훅 상태 및 핸들러 추출
+ const form = signUpHook?.form || {};
+ const setName = signUpHook?.setName || signUpHook?.handleNameChange;
+ const setPhone = signUpHook?.setPhone || signUpHook?.handlePhoneChange;
+ const setAuthCode = signUpHook?.setAuthCode || signUpHook?.handleAuthCodeChange;
+ const setPassword = signUpHook?.setPassword || signUpHook?.handlePasswordChange;
+ const setConfirmPassword = signUpHook?.setConfirmPassword || signUpHook?.handleConfirmPasswordChange;
+
+ const handleSendAuthCode = signUpHook?.handleSendAuthCode || (() => {});
+ const handleVerifyAuthCode = signUpHook?.handleVerifyAuthCode || (() => {});
+
+ const setAddress = signUpHook?.setAddress;
+ const setDetailAddress = signUpHook?.setDetailAddress;
+ const handleSelectAddress = signUpHook?.handleSelectAddress;
+ const handleBirthDateChange = signUpHook?.setBirthDate || signUpHook?.handleBirthDateChange;
+
+ const toggleDisease = signUpHook?.toggleDisease || signUpHook?.handleToggleDisease || (() => {});
+ const handleSubmit = signUpHook?.handleSubmit || signUpHook?.handleSignUp || signUpHook?.onSubmit || (() => {});
+ const isFormValid = signUpHook?.isFormValid ?? true;
+
+ // 기저질환 목록
+ const diseaseList: string[] = signUpHook?.diseaseList || signUpHook?.diseases || [
+ '고혈압',
+ '당뇨병',
+ '치매',
+ '골다공증',
+ '고지혈증',
+ '관절염',
+ '심혈관 질환',
+ '만성 신부전',
+ '파킨슨병',
+ '기타',
+ ];
+
+ const selectedDiseases: string[] = form?.selectedDiseases || signUpHook?.selectedDiseases || [];
+
+ return (
+
+
+
+ {/* 상단 헤더 영역: 로고와 타이틀을 독립된 구조로 분리 */}
+
+
+
+
+ 회원가입
+
+
+ {/* 1. 이름 입력 */}
+ {form?.name !== undefined && (
+
+ 이름
+
+
+ )}
+
+ {/* 2. 전화번호 입력 & 인증번호 요청 */}
+ {form?.phone !== undefined && (
+
+ 전화번호
+
+
+
+ 인증요청
+
+
+
+ )}
+
+ {/* 3. 인증번호 입력 & 확인 */}
+
+ 인증번호
+
+
+
+ 인증확인
+
+
+
+
+ {/* 4. 비밀번호 입력 */}
+
+ 비밀번호
+
+
+
+ {/* 5. 비밀번호 확인 입력 */}
+
+ 비밀번호 확인
+
+ {form?.password && form?.confirmPassword && form.password !== form.confirmPassword && (
+
+ 비밀번호가 일치하지 않습니다.
+
+ )}
+
+
+ {/* 6. 생년월일 선택 */}
+
+ 생년월일
+ {
+ if (handleBirthDateChange) {
+ handleBirthDateChange(date);
+ }
+ }}
+ />
+
+
+ {/* 7. 주소 입력 */}
+
+ 주소
+
+
+
+
+
+
+ {/* 8. 기저 질환 선택 */}
+
+ 기저 질환 선택
+
+
+ {diseaseList.map((disease: string) => {
+ const isSelected = selectedDiseases.includes(disease);
+ return (
+
+ toggleDisease(disease)}
+ activeOpacity={0.7}
+ >
+
+ {isSelected && ✓}
+
+ {disease}
+
+
+ );
+ })}
+
+
+
+ {/* 9. 제출 버튼 */}
+
+ 가입하기
+
+
+
+
+ );
+};
+
+export default SignupScreen;
\ No newline at end of file
diff --git a/src/screens/auth/Signup/component/SignupTypeSelectScreen.tsx b/src/screens/auth/Signup/component/SignupTypeSelectScreen.tsx
new file mode 100644
index 0000000..1b8ba60
--- /dev/null
+++ b/src/screens/auth/Signup/component/SignupTypeSelectScreen.tsx
@@ -0,0 +1,69 @@
+import React from 'react';
+import {
+ View,
+ Text,
+ TouchableOpacity,
+ SafeAreaView,
+} from 'react-native';
+import { useNavigation } from '@react-navigation/native';
+
+import { signupTypeSelectStyles } from '../styles/signupTypeSelect.styles';
+import { useSignupTypeSelect } from '../hooks/useSignupTypeSelect';
+
+import { CaringLogo } from './CaringLogo';
+import { CaringDogIcon } from './CaringDogIcon';
+
+export const SignupTypeSelectScreen = () => {
+ const navigation = useNavigation();
+ const typeSelectHook = useSignupTypeSelect(navigation) as any;
+
+ const handleSelectType =
+ typeSelectHook?.handleRoleSelect ||
+ typeSelectHook?.handleSelectType ||
+ typeSelectHook?.onSelectType;
+
+ return (
+
+ {/* 1. 상단 케어링 로고 */}
+
+
+
+
+
+ {/* 2. 중앙 컨텐츠 영역 (강아지 + 텍스트 + 버튼) */}
+
+ {/* 강아지 캐릭터 */}
+
+
+
+
+ {/* 메인 안내 문구 (강아지 발 바로 아래) */}
+ 안녕하세요!
+
+ 어떤 서비스를 이용하시나요?
+
+
+ {/* 3. 역할 선택 버튼 영역 */}
+
+ handleSelectType && handleSelectType('GUARDIAN')}
+ activeOpacity={0.7}
+ >
+ 보호자
+
+
+ handleSelectType && handleSelectType('SENIOR')}
+ activeOpacity={0.7}
+ >
+ 돌봄대상자
+
+
+
+
+ );
+};
+
+export default SignupTypeSelectScreen;
\ No newline at end of file
diff --git a/src/screens/auth/Signup/hooks/useBirthDatePicker.ts b/src/screens/auth/Signup/hooks/useBirthDatePicker.ts
new file mode 100644
index 0000000..004ce0e
--- /dev/null
+++ b/src/screens/auth/Signup/hooks/useBirthDatePicker.ts
@@ -0,0 +1,21 @@
+import { useState } from 'react';
+
+export function useBirthDatePicker(initialDate: string, onChange: (date: string) => void) {
+ const [isOpen, setIsOpen] = useState(true);
+
+ const handleDayPress = (day: { dateString: string }) => {
+ onChange(day.dateString);
+ // 선택 후 닫고 싶다면 아래 주석을 해제하세요
+ // setIsOpen(false);
+ };
+
+ const toggleOpen = () => setIsOpen(prev => !prev);
+ const closeCalendar = () => setIsOpen(false);
+
+ return {
+ isOpen,
+ toggleOpen,
+ closeCalendar,
+ handleDayPress,
+ };
+}
\ No newline at end of file
diff --git a/src/screens/auth/Signup/hooks/useSignUp.ts b/src/screens/auth/Signup/hooks/useSignUp.ts
new file mode 100644
index 0000000..695b6f4
--- /dev/null
+++ b/src/screens/auth/Signup/hooks/useSignUp.ts
@@ -0,0 +1,58 @@
+import { useState } from 'react';
+
+export default function useSignUp() {
+ // 입력 폼 상태 관리
+ const [name, setName] = useState('');
+ const [phone, setPhone] = useState('');
+ const [authCode, setAuthCode] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordConfirm, setPasswordConfirm] = useState('');
+ const [birthDate, setBirthDate] = useState('');
+ const [address, setAddress] = useState('');
+ const [selectedDiseases, setSelectedDiseases] = useState([]);
+
+ // 주소 모달 상태
+ const [isAddressModalOpen, setIsAddressModalOpen] = useState(false);
+
+ // 기저 질환 체크박스 토글 함수
+ const toggleDisease = (disease: string) => {
+ if (selectedDiseases.includes(disease)) {
+ setSelectedDiseases(selectedDiseases.filter((item) => item !== disease));
+ } else {
+ setSelectedDiseases([...selectedDiseases, disease]);
+ }
+ };
+
+ // 다음 주소 검색 선택 핸들러
+ const handleSelectAddress = (data: any) => {
+ setAddress(data.address);
+ setIsAddressModalOpen(false);
+ };
+
+ return {
+ // 상태값 모음
+ form: {
+ name,
+ phone,
+ authCode,
+ password,
+ passwordConfirm,
+ birthDate,
+ address,
+ selectedDiseases,
+ },
+ // Setters
+ setName,
+ setPhone,
+ setAuthCode,
+ setPassword,
+ setPasswordConfirm,
+ setBirthDate,
+ setAddress,
+ // 모달 및 핸들러
+ isAddressModalOpen,
+ setIsAddressModalOpen,
+ toggleDisease,
+ handleSelectAddress,
+ };
+}
\ No newline at end of file
diff --git a/src/screens/auth/Signup/hooks/useSignupTypeSelect.ts b/src/screens/auth/Signup/hooks/useSignupTypeSelect.ts
new file mode 100644
index 0000000..80822c9
--- /dev/null
+++ b/src/screens/auth/Signup/hooks/useSignupTypeSelect.ts
@@ -0,0 +1,12 @@
+import { UserRole } from '../types/signuptype.types';
+
+export function useSignupTypeSelect(navigation: any) {
+ const handleRoleSelect = (role: UserRole) => {
+ // 선택된 역할(role)을 담아서 약관 동의 화면으로 이동
+ navigation.navigate('TermsAgreement', { role });
+ };
+
+ return {
+ handleRoleSelect,
+ };
+}
\ No newline at end of file
diff --git a/src/screens/auth/Signup/styles/birthDatePicker.styles.ts b/src/screens/auth/Signup/styles/birthDatePicker.styles.ts
new file mode 100644
index 0000000..db4ffdf
--- /dev/null
+++ b/src/screens/auth/Signup/styles/birthDatePicker.styles.ts
@@ -0,0 +1,42 @@
+import { StyleSheet } from 'react-native';
+
+export const birthDatePickerStyles = StyleSheet.create({
+ wrapper: {
+ width: '100%',
+ },
+ mainInput: {
+ backgroundColor: '#FAFAFA',
+ borderWidth: 1,
+ borderColor: '#DDD',
+ borderRadius: 8,
+ paddingHorizontal: 12,
+ height: 48,
+ fontSize: 14,
+ color: '#000',
+ },
+ calendarBox: {
+ marginTop: 8,
+ backgroundColor: '#fff',
+ borderWidth: 1,
+ borderColor: '#dee2e6',
+ borderRadius: 12,
+ padding: 8,
+ elevation: 5,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ },
+ closeBtn: {
+ marginTop: 6,
+ backgroundColor: '#f1f3f5',
+ borderRadius: 6,
+ paddingVertical: 8,
+ alignItems: 'center',
+ },
+ closeText: {
+ color: '#495057',
+ fontSize: 12,
+ fontWeight: '600',
+ },
+});
\ No newline at end of file
diff --git a/src/screens/auth/Signup/styles/signup.styles.ts b/src/screens/auth/Signup/styles/signup.styles.ts
new file mode 100644
index 0000000..6196b4c
--- /dev/null
+++ b/src/screens/auth/Signup/styles/signup.styles.ts
@@ -0,0 +1,116 @@
+import { StyleSheet, Platform, StatusBar } from 'react-native';
+
+const PRIMARY_COLOR = '#FF7F00';
+
+export const signupStyles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#FFF',
+ paddingTop: Platform.OS === 'android' ? (StatusBar.currentHeight || 0) + 24 : 60,
+ },
+ scrollContent: {
+ paddingHorizontal: 20,
+ paddingTop: 20,
+ paddingBottom: 40,
+ },
+
+ // ----------------------------------------------------
+ // 헤더: 로고와 타이틀을 세로로 완벽히 분리하고 간격 확보
+ // ----------------------------------------------------
+ headerContainer: {
+ flexDirection: 'column',
+ alignItems: 'flex-start',
+ marginBottom: 32,
+ marginTop: 10,
+ },
+ logoWrapper: {
+ width: 48,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginBottom: 10, // 로고와 '회원가입' 글자 사이 여백
+ },
+ title: {
+ fontSize: 26,
+ fontWeight: 'bold',
+ color: '#111827',
+ includeFontPadding: false,
+ textAlign: 'left',
+ lineHeight: 32,
+ },
+
+ inputGroup: {
+ marginBottom: 16,
+ },
+ label: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: '#333',
+ marginBottom: 8,
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#E2E8F0',
+ borderRadius: 8,
+ paddingHorizontal: 12,
+ paddingVertical: 12,
+ fontSize: 14,
+ color: '#111',
+ backgroundColor: '#FFF',
+ },
+ submitButton: {
+ backgroundColor: PRIMARY_COLOR,
+ borderRadius: 8,
+ paddingVertical: 14,
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginTop: 24,
+ },
+ submitButtonDisabled: {
+ backgroundColor: '#CBD5E1',
+ },
+ submitButtonText: {
+ color: '#FFF',
+ fontSize: 16,
+ fontWeight: 'bold',
+ },
+
+ // 기저 질환 선택 3열 정렬 스타일
+ diseaseGrid: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ marginTop: 8,
+ rowGap: 14,
+ },
+ diseaseItem: {
+ width: '33.33%',
+ },
+ checkboxRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ checkbox: {
+ width: 18,
+ height: 18,
+ borderWidth: 1.5,
+ borderColor: '#CED4DA',
+ borderRadius: 4,
+ marginRight: 6,
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: '#FFFFFF',
+ },
+ checkboxActive: {
+ backgroundColor: PRIMARY_COLOR,
+ borderColor: PRIMARY_COLOR,
+ },
+ checkmark: {
+ color: '#FFFFFF',
+ fontSize: 11,
+ fontWeight: 'bold',
+ },
+ diseaseText: {
+ fontSize: 13,
+ color: '#333333',
+ },
+});
\ No newline at end of file
diff --git a/src/screens/auth/Signup/styles/signupTypeSelect.styles.ts b/src/screens/auth/Signup/styles/signupTypeSelect.styles.ts
new file mode 100644
index 0000000..ea93fd8
--- /dev/null
+++ b/src/screens/auth/Signup/styles/signupTypeSelect.styles.ts
@@ -0,0 +1,74 @@
+import { StyleSheet } from 'react-native';
+
+export const signupTypeSelectStyles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#FFFFFF',
+ },
+ // 1. 상단 좌측 로고 영역
+ headerContainer: {
+ paddingHorizontal: 24,
+ paddingTop: 16,
+ paddingBottom: 10,
+ alignItems: 'flex-start',
+ },
+ logoWrapper: {
+ width: 48,
+ height: 48,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ // 2. 중앙 컨텐츠 전체 (위쪽 정렬로 변경해서 전체를 위로 올림!)
+ contentContainer: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'flex-start', // ⭐ center에서 flex-start로 변경 ⭐
+ paddingTop: 10, // ⭐ 로고 밑에서의 위치 (원하는 만큼 숫자로 조절 가능) ⭐
+ paddingHorizontal: 24,
+ },
+ // 강아지 캐릭터 영역
+ characterContainer: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginTop: -70,
+ },
+ // 메인 타이틀 문구
+ title: {
+ fontSize: 26,
+ fontWeight: 'bold',
+ color: '#111111',
+ textAlign: 'center',
+ marginTop: -100, // 강아지 발 바로 밑에 붙이기
+ marginBottom: 6,
+ },
+ subtitle: {
+ fontSize: 26,
+ fontWeight: 'bold',
+ color: '#111111',
+ textAlign: 'center',
+ marginBottom: 32,
+ },
+ // 3. 하단 역할 선택 버튼 영역
+ buttonRow: {
+ flexDirection: 'row',
+ justifyContent: 'center',
+ gap: 16,
+ width: '100%',
+ paddingHorizontal: 8,
+ },
+ typeButton: {
+ flex: 1,
+ height: 58,
+ backgroundColor: '#FFFFFF',
+ borderWidth: 2,
+ borderColor: '#FF7F00',
+ borderRadius: 12,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ buttonText: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#111111',
+ },
+});
\ No newline at end of file
diff --git a/src/screens/auth/Signup/types/birthDatePicker.types.ts b/src/screens/auth/Signup/types/birthDatePicker.types.ts
new file mode 100644
index 0000000..742fd39
--- /dev/null
+++ b/src/screens/auth/Signup/types/birthDatePicker.types.ts
@@ -0,0 +1,4 @@
+export interface BirthDatePickerProps {
+ value: string;
+ onChange: (date: string) => void;
+}
\ No newline at end of file
diff --git a/src/screens/auth/Signup/types/signup.types.ts b/src/screens/auth/Signup/types/signup.types.ts
new file mode 100644
index 0000000..6a64ee1
--- /dev/null
+++ b/src/screens/auth/Signup/types/signup.types.ts
@@ -0,0 +1,10 @@
+export interface SignupFormState {
+ name: string;
+ phone: string;
+ authCode: string;
+ password: string;
+ passwordConfirm: string;
+ birthDate: string;
+ address: string;
+ diseases: string[];
+}
\ No newline at end of file
diff --git a/src/screens/auth/Signup/types/signupType.types.ts b/src/screens/auth/Signup/types/signupType.types.ts
new file mode 100644
index 0000000..09d308f
--- /dev/null
+++ b/src/screens/auth/Signup/types/signupType.types.ts
@@ -0,0 +1,5 @@
+export type UserRole = 'PROTECTOR' | 'WARD';
+
+export interface RoleSelectHandler {
+ onSelectRole: (role: UserRole) => void;
+}
\ No newline at end of file
diff --git a/src/screens/auth/terms/CaringLogo.tsx b/src/screens/auth/terms/CaringLogo.tsx
new file mode 100644
index 0000000..4f3f8a4
--- /dev/null
+++ b/src/screens/auth/terms/CaringLogo.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import { View, StyleSheet } from 'react-native';
+import Svg, { Circle, Path, Rect, G, Defs, Pattern, Use, Filter, FeFlood, FeColorMatrix, FeOffset, FeGaussianBlur, FeComposite, FeBlend, LinearGradient, Stop, Image as SvgImage } from 'react-native-svg';
+
+export function CaringLogo() {
+ return (
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ marginBottom: 8,
+ },
+});
\ No newline at end of file
diff --git a/src/screens/auth/terms/TermsAgreement.styles.ts b/src/screens/auth/terms/TermsAgreement.styles.ts
new file mode 100644
index 0000000..70a8b2f
--- /dev/null
+++ b/src/screens/auth/terms/TermsAgreement.styles.ts
@@ -0,0 +1,127 @@
+import { StyleSheet } from 'react-native';
+
+export const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#FFFFFF',
+ paddingHorizontal: 24,
+ paddingTop: 60,
+ },
+ headerContainer: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ width: '100%',
+ marginBottom: 20,
+ position: 'relative',
+ height: 48,
+ },
+ titleContainer: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ alignItems: 'center',
+ zIndex: 1,
+ },
+ logoContainer: {
+ justifyContent: 'center',
+ zIndex: 2,
+ },
+ title: {
+ fontSize: 20,
+ fontWeight: 'bold',
+ color: '#000000',
+ },
+ subtitle: {
+ fontSize: 14,
+ color: '#666666',
+ marginBottom: 24,
+ },
+ allAgreeBox: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: '#F5F6F8',
+ padding: 16,
+ borderRadius: 12,
+ marginBottom: 24,
+ },
+ allAgreeText: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ color: '#000000',
+ marginLeft: 12,
+ },
+ sectionTitle: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: '#888888',
+ marginBottom: 12,
+ marginTop: 12,
+ },
+ sectionHeader: {
+ fontSize: 14,
+ fontWeight: 'bold',
+ color: '#666666',
+ marginTop: 16,
+ marginBottom: 8,
+ paddingHorizontal: 4,
+ },
+ termItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingVertical: 12,
+ },
+ termText: {
+ fontSize: 15,
+ color: '#333333',
+ marginLeft: 12,
+ },
+ required: {
+ color: '#FF7E00',
+ },
+ requiredText: {
+ color: '#FF7E00',
+ },
+ checkbox: {
+ width: 22,
+ height: 22,
+ borderRadius: 4,
+ borderWidth: 1,
+ borderColor: '#DDDDDD',
+ backgroundColor: '#FFFFFF',
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ checkboxChecked: {
+ borderColor: '#FF7E00',
+ backgroundColor: '#FFF9F5',
+ },
+ checkedBox: {
+ borderColor: '#FF7E00',
+ backgroundColor: '#FFF9F5',
+ },
+ checkmark: {
+ color: '#FF7E00',
+ fontSize: 12,
+ fontWeight: 'bold',
+ },
+ nextButton: {
+ backgroundColor: '#FF7E00',
+ height: 56,
+ borderRadius: 12,
+ justifyContent: 'center',
+ alignItems: 'center',
+ marginTop: 20,
+ marginBottom: 30,
+ },
+ disabledButton: {
+ backgroundColor: '#DDDDDD',
+ },
+ nextButtonDisabled: {
+ backgroundColor: '#DDDDDD',
+ },
+ nextButtonText: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ color: '#FFFFFF',
+ },
+});
\ No newline at end of file
diff --git a/src/screens/auth/terms/TermsAgreementScreen.tsx b/src/screens/auth/terms/TermsAgreementScreen.tsx
new file mode 100644
index 0000000..0118c2b
--- /dev/null
+++ b/src/screens/auth/terms/TermsAgreementScreen.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { View, Text, TouchableOpacity, ScrollView, SafeAreaView } from 'react-native';
+import { useTermsAgreement, TERM_LIST } from './useTermsAgreement';
+import { styles } from './TermsAgreement.styles';
+import LogoIcon from '@assets/icons/header/caring_logo.svg';
+
+export default function TermsAgreementScreen({ navigation }: any) {
+ const { checkedItems, isAllChecked, isRequiredChecked, handleCheckItem, handleCheckAll } = useTermsAgreement();
+
+ const handleNextPress = () => {
+ if (isRequiredChecked) {
+ navigation.navigate('Signup');
+ }
+ };
+
+ return (
+
+
+
+ {/* 상단 헤더 (로고 왼쪽, 타이틀 가운데 정렬 구조) */}
+
+
+
+
+
+ 약관 동의
+
+
+
+ 서비스 이용을 위해 약관에 동의해 주세요.
+
+ {/* 전체 동의 박스 */}
+
+
+ {isAllChecked && ✓}
+
+ 전체 동의
+
+
+ {/* 필수 약관 타이틀 */}
+ 필수 약관
+
+ {/* 필수 약관 목록 */}
+ {TERM_LIST.filter(term => term.required).map(term => (
+ handleCheckItem(term.id)}
+ activeOpacity={0.8}
+ >
+
+ {checkedItems[term.id] && ✓}
+
+
+ {term.title} (필수)
+
+
+ ))}
+
+ {/* 선택 약관 타이틀 */}
+ 선택 약관
+
+ {/* 선택 약관 목록 */}
+ {TERM_LIST.filter(term => !term.required).map(term => (
+ handleCheckItem(term.id)}
+ activeOpacity={0.8}
+ >
+
+ {checkedItems[term.id] && ✓}
+
+ {term.title}
+
+ ))}
+
+ {/* 다음 버튼 */}
+
+ 다음
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/screens/auth/terms/types.ts b/src/screens/auth/terms/types.ts
new file mode 100644
index 0000000..092f793
--- /dev/null
+++ b/src/screens/auth/terms/types.ts
@@ -0,0 +1,5 @@
+export interface TermItem {
+ id: string;
+ title: string;
+ required: boolean;
+}
\ No newline at end of file
diff --git a/src/screens/auth/terms/useTermsAgreement.ts b/src/screens/auth/terms/useTermsAgreement.ts
new file mode 100644
index 0000000..59ecc77
--- /dev/null
+++ b/src/screens/auth/terms/useTermsAgreement.ts
@@ -0,0 +1,45 @@
+import { useState } from 'react';
+import { TermItem } from './types';
+
+export const TERM_LIST: TermItem[] = [
+ { id: 'service', title: '서비스 이용약관 동의', required: true },
+ { id: 'privacy', title: '개인정보 수집 및 이용 동의', required: true },
+ { id: 'location', title: '위치기반 서비스 이용약관 동의', required: true },
+ { id: 'thirdParty', title: '개인정보 제3자 제공 동의', required: true },
+ { id: 'marketing', title: '마케팅 정보 수신 동의', required: false },
+ { id: 'optionalPrivacy', title: '개인정보 선택 수집 동의', required: false },
+ { id: 'voice', title: '음성 데이터 수집 및 이용 동의', required: false },
+];
+
+export const useTermsAgreement = () => {
+ const [checkedItems, setCheckedItems] = useState<{ [key: string]: boolean }>({});
+
+ const isAllChecked = TERM_LIST.every((item) => checkedItems[item.id]);
+ const isRequiredChecked = TERM_LIST
+ .filter((item) => item.required)
+ .every((item) => checkedItems[item.id]);
+
+ const handleCheckItem = (id: string) => {
+ setCheckedItems((prev) => ({
+ ...prev,
+ [id]: !prev[id],
+ }));
+ };
+
+ const handleCheckAll = () => {
+ const nextValue = !isAllChecked;
+ const newCheckedItems: { [key: string]: boolean } = {};
+ TERM_LIST.forEach((item) => {
+ newCheckedItems[item.id] = nextValue;
+ });
+ setCheckedItems(newCheckedItems);
+ };
+
+ return {
+ checkedItems,
+ isAllChecked,
+ isRequiredChecked,
+ handleCheckItem,
+ handleCheckAll,
+ };
+};
\ No newline at end of file
diff --git a/src/screens/senior/Schedule/SeniorScheduleScreen.tsx b/src/screens/senior/Schedule/SeniorScheduleScreen.tsx
new file mode 100644
index 0000000..a4b2456
--- /dev/null
+++ b/src/screens/senior/Schedule/SeniorScheduleScreen.tsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import { SafeAreaView, Text, View } from 'react-native';
+
+export function SeniorScheduleScreen() {
+ return (
+
+
+ 오늘 일정
+
+
+ );
+}
\ No newline at end of file