diff --git a/src/app/navigation/RootNavigator.tsx b/src/app/navigation/RootNavigator.tsx index d80e042..5038f5d 100644 --- a/src/app/navigation/RootNavigator.tsx +++ b/src/app/navigation/RootNavigator.tsx @@ -3,6 +3,7 @@ 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 TermsAgreementScreen from '@screens/auth/terms/TermsAgreementScreen'; import { LinkAccountScreen } from '@screens/auth/LinkAccount/LinkAccountScreen'; import { GuardianStackNavigator } from './GuardianStackNavigator'; import { SeniorHomeScreen } from '@screens/senior/Home/SeniorHomeScreen'; @@ -31,7 +32,8 @@ export function RootNavigator() { + ); -} +} \ 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 ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* πŸ‘‡ 이 λΆ€λΆ„μ˜ href 속성 μ•ˆμ— 본인 이미지 데이터λ₯Ό λ„£μœΌμ‹œλ©΄ λ©λ‹ˆλ‹€ */} + + + + + ); +} + +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..7d9dba6 --- /dev/null +++ b/src/screens/auth/terms/TermsAgreement.styles.ts @@ -0,0 +1,112 @@ +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: 24, + position: 'relative', + }, + titleContainer: { + position: 'absolute', + left: 0, + right: 0, + alignItems: 'center', + zIndex: 1, + }, + /* πŸ‘‡ 둜고λ₯Ό 감싸기 μœ„ν•΄ μΆ”κ°€λœ μŠ€νƒ€μΌ */ + logoContainer: { + alignItems: 'center', + marginBottom: 24, + }, + 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, + }, + termItem: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 12, + // κ°œλ³„ λ°•μŠ€ ν…Œλ‘λ¦¬ 제거됨 + }, + termText: { + fontSize: 15, + color: '#333333', + marginLeft: 12, + }, + required: { + color: '#FF7E00', + }, + /* ν”Όκ·Έλ§ˆμ™€ λ™μΌν•œ λΉ„μœ¨/크기의 μ²΄ν¬λ°•μŠ€ μŠ€νƒ€μΌ */ + checkbox: { + width: 22, + height: 22, + borderRadius: 4, + borderWidth: 1, + borderColor: '#DDDDDD', + backgroundColor: '#FFFFFF', + justifyContent: 'center', + alignItems: 'center', + }, + checkboxChecked: { + 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, + }, + 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..ea13240 --- /dev/null +++ b/src/screens/auth/terms/TermsAgreementScreen.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, ScrollView } from 'react-native'; +import { useTermsAgreement, TERM_LIST } from './useTermsAgreement'; +import { styles } from './TermsAgreement.styles'; +import { CaringLogo } from './CaringLogo'; + +export default function TermsAgreementScreen({ navigation }: any) { + const { checkedItems, isAllChecked, isRequiredChecked, handleCheckItem, handleCheckAll } = useTermsAgreement(); + + return ( + + + + + + μ„œλΉ„μŠ€ μ΄μš©μ„ μœ„ν•΄{'\n'}약관에 λ™μ˜ν•΄ μ£Όμ„Έμš”. + + {/* 전체 λ™μ˜ λ²„νŠΌ (μŠ€νƒ€μΌ 이름을 allAgreeBox둜 μˆ˜μ •) */} + + λͺ¨λ“  약관에 λ™μ˜ν•©λ‹ˆλ‹€. + + + {/* κ°œλ³„ μ•½κ΄€ λͺ©λ‘ */} + {TERM_LIST.map(term => ( + handleCheckItem(term.id)} + > + + {term.required ? '[ν•„μˆ˜]' : '[선택]'} {term.title} {checkedItems[term.id] ? 'βœ“' : ''} + + + ))} + + {/* λ‹€μŒ λ²„νŠΌ */} + { + navigation.navigate('Signup'); + }} + > + λ‹€μŒ + + + ); +} \ 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