Skip to content

Commit e5c26e7

Browse files
authored
feat - 서버 api 연동 및 로그인,회원가입 추가 (#6)
* feat: api 세팅작업 * feat: 응답 로그 추가 * feat: 상단 헤더 여백 대응 * feat: cognito 로그인 및 회원가입 추가 * feat: 회원가입 로직 개선 * feat: 로그아웃 추가 * feat: 앱 아이콘 변경 * fix: 회원가입 이메일 인증 제거 * feat: 홈화면 기능 추가 * feat: icon 및 앱 이름 업데이트 및 햅틱 제거 * feat: 프로필 정보 및 수정 api 연동 * fix: 알림 필드 제거 * feat: 키보드 대응 * feat: 아이콘 업데이트 및 회원가입 시 정보입력 페이지 라우팅 추가
1 parent 70a7384 commit e5c26e7

23 files changed

+1647
-182
lines changed

frontend/.env.example

Lines changed: 0 additions & 8 deletions
This file was deleted.

frontend/app.config.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,77 @@
11
import { ConfigContext, ExpoConfig } from 'expo/config';
22

3+
const processEnv =
4+
(globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {};
5+
36
export default ({ config }: ConfigContext): ExpoConfig => ({
47
...config,
5-
name: 'dailyfate',
8+
9+
// ===== App 기본 정보 =====
10+
name: '운세한장',
611
slug: 'dailyfate',
712
version: '1.0.0',
13+
14+
// ===== 환경 변수 =====
815
extra: {
916
eas: {
1017
projectId: '7eaf22a0-8a86-4d38-96f4-5c8eb183393b',
1118
},
19+
EXPO_BASE_URL: processEnv.EXPO_BASE_URL,
20+
EXPO_AUTH_TOKEN: processEnv.EXPO_AUTH_TOKEN,
21+
EXPO_COGNITO_REGION: processEnv.EXPO_COGNITO_REGION,
22+
EXPO_COGNITO_USER_POOL_ID: processEnv.EXPO_COGNITO_USER_POOL_ID,
23+
EXPO_COGNITO_CLIENT_ID: processEnv.EXPO_COGNITO_CLIENT_ID,
24+
EXPO_COGNITO_TOKEN_USE: processEnv.EXPO_COGNITO_TOKEN_USE,
25+
EXPO_COGNITO_CODE_TTL_MINUTES: processEnv.EXPO_COGNITO_CODE_TTL_MINUTES,
1226
},
27+
28+
// ===== UI 기본 설정 =====
1329
orientation: 'portrait',
14-
icon: './assets/icon.png',
1530
userInterfaceStyle: 'light',
31+
icon: './assets/icon.png',
32+
1633
splash: {
1734
image: './assets/splash.png',
1835
resizeMode: 'contain',
1936
backgroundColor: '#ffffff',
2037
},
38+
2139
assetBundlePatterns: ['**/*'],
40+
41+
// ===== iOS 설정 =====
2242
ios: {
2343
...config.ios,
2444
supportsTablet: true,
2545
bundleIdentifier: 'com.dailyfate.frontend',
2646
infoPlist: {
2747
...config.ios?.infoPlist,
48+
CFBundleDisplayName: '운세한장', // 👈 iOS 홈 화면 앱 이름
2849
ITSAppUsesNonExemptEncryption: false,
2950
},
3051
},
52+
53+
// ===== Android 설정 =====
3154
android: {
3255
...config.android,
56+
package: 'com.dailyfate.frontend',
57+
58+
// @ts-ignore — Expo에서 실제 지원하는 속성
59+
label: '운세한장',
60+
3361
adaptiveIcon: {
3462
...(config.android?.adaptiveIcon ?? {}),
3563
foregroundImage: './assets/adaptive-icon.png',
3664
backgroundColor: '#ffffff',
3765
},
38-
package: 'com.dailyfate.frontend',
3966
},
67+
68+
// ===== Web 설정 =====
4069
web: {
4170
...config.web,
4271
favicon: './assets/favicon.png',
4372
},
73+
74+
// ===== 기타 =====
4475
scheme: 'frontend',
4576
plugins: ['expo-router'],
4677
});

frontend/app/_layout.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@ import { Stack } from 'expo-router';
22
import { GestureHandlerRootView } from 'react-native-gesture-handler';
33
import { SafeAreaProvider } from 'react-native-safe-area-context';
44
import '../src/styles/global.css';
5+
import { AuthProvider } from '@/providers/AuthProvider';
56

67
export default function RootLayout() {
78
return (
89
<GestureHandlerRootView style={{ flex: 1 }}>
910
<SafeAreaProvider>
10-
<Stack screenOptions={{ headerBackVisible: false }}>
11-
<Stack.Screen
12-
name="index"
13-
options={{
14-
headerTitle: '',
15-
headerShown: true,
16-
}}
17-
/>
18-
</Stack>
11+
<AuthProvider>
12+
<Stack screenOptions={{ headerBackVisible: false }}>
13+
<Stack.Screen
14+
name="index"
15+
options={{
16+
headerTitle: '',
17+
headerShown: true,
18+
}}
19+
/>
20+
</Stack>
21+
</AuthProvider>
1922
</SafeAreaProvider>
2023
</GestureHandlerRootView>
2124
);

frontend/app/index.tsx

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
2-
import { ActivityIndicator, Alert, Pressable, View } from 'react-native';
2+
import { ActivityIndicator, Alert, View } from 'react-native';
33
import AsyncStorage from '@react-native-async-storage/async-storage';
44

55
import { useNavigation } from 'expo-router';
66

7-
import { fetchDailyFortune } from '../src/services/geminiService';
8-
import { FortuneData, UserSettings } from '@/types';
7+
import { useFortune } from '@/hooks/useFortune';
8+
import { UserSettings } from '@/types';
99
import Onboarding from '@/components/Onboarding';
1010
import UserInfoForm from '@/components/UserInfoForm';
1111
import CalendarPage from '@/components/CalendarPage';
1212
import SettingsSheet from '@/components/SettingsSheet';
13-
import { Feather } from '@expo/vector-icons';
13+
import LoginScreen from '@/components/LoginScreen';
14+
import { useAuth } from '@/providers/AuthProvider';
15+
import { ProfileApiError, updateUserProfile } from '@/services/userProfileService';
1416

1517
const HAS_ONBOARDED_KEY = 'hasOnboarded';
1618
const USER_SETTINGS_KEY = 'userSettings';
@@ -22,9 +24,16 @@ export default function Home() {
2224
const [currentDate, setCurrentDate] = useState(new Date());
2325
const [hasOnboarded, setHasOnboarded] = useState(false);
2426
const [userSettings, setUserSettings] = useState<UserSettings | null>(null);
25-
const [fortune, setFortune] = useState<FortuneData | null>(null);
26-
const [loadingFortune, setLoadingFortune] = useState(false);
2727
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
28+
const [needsProfileSetup, setNeedsProfileSetup] = useState(false);
29+
const [profileSaving, setProfileSaving] = useState(false);
30+
31+
const { isBootstrapping: authBootstrapping, isSignedIn, signOut } = useAuth();
32+
33+
const { fortune, loading: loadingFortune, error } = useFortune(
34+
currentDate,
35+
hasOnboarded && !!userSettings && isSignedIn,
36+
);
2837

2938
// Load persisted state
3039
useEffect(() => {
@@ -62,33 +71,39 @@ export default function Home() {
6271
await AsyncStorage.setItem(USER_SETTINGS_KEY, JSON.stringify(settings));
6372
};
6473

65-
// Fortune fetch on date/settings change
66-
useEffect(() => {
67-
let isMounted = true;
68-
69-
const loadFortune = async () => {
70-
if (!hasOnboarded || !userSettings) return;
71-
setLoadingFortune(true);
72-
setFortune(null);
73-
try {
74-
const data = await fetchDailyFortune(currentDate, userSettings);
75-
if (isMounted) setFortune(data);
76-
} catch (error) {
77-
console.warn('Failed to fetch fortune', error);
78-
if (isMounted) {
79-
Alert.alert('운세를 불러오지 못했어요', '잠시 후 다시 시도해주세요.');
80-
}
81-
} finally {
82-
if (isMounted) setLoadingFortune(false);
74+
const handleUserInfoSubmit = async (settings: UserSettings) => {
75+
if (profileSaving) return;
76+
setProfileSaving(true);
77+
try {
78+
const updatedSettings = await updateUserProfile(settings);
79+
await persistUserSettings(updatedSettings);
80+
if (!hasOnboarded) {
81+
await persistHasOnboarded();
8382
}
84-
};
85-
86-
loadFortune();
83+
setNeedsProfileSetup(false);
84+
} catch (error) {
85+
if (error instanceof ProfileApiError && error.status === 401) {
86+
Alert.alert('로그인이 필요합니다', '다시 로그인해주세요.');
87+
await signOut();
88+
return;
89+
}
90+
const message =
91+
error instanceof Error ? error.message : '프로필을 저장하지 못했어요.';
92+
Alert.alert('프로필 저장 실패', message);
93+
} finally {
94+
setProfileSaving(false);
95+
}
96+
};
8797

88-
return () => {
89-
isMounted = false;
90-
};
91-
}, [currentDate, hasOnboarded, userSettings]);
98+
useEffect(() => {
99+
if (!error) return;
100+
if (error.status === 401) {
101+
signOut();
102+
Alert.alert('로그인이 필요합니다', '다시 로그인해주세요.');
103+
return;
104+
}
105+
Alert.alert('운세를 불러오지 못했어요', error.message);
106+
}, [error, signOut]);
92107

93108
const handleNextDay = useCallback(() => {
94109
setCurrentDate((prev) => {
@@ -106,22 +121,30 @@ export default function Home() {
106121
});
107122
}, []);
108123

109-
if (bootLoading) {
124+
if (bootLoading || authBootstrapping) {
110125
return (
111126
<View className="flex-1 items-center justify-center bg-stone-200">
112127
<ActivityIndicator size="large" color="#191F28" />
113128
</View>
114129
);
115130
}
116131

117-
const showOnboarding = !hasOnboarded;
118-
const showUserForm = hasOnboarded && !userSettings;
132+
if (!isSignedIn) {
133+
return <LoginScreen onSignUpSuccess={() => setNeedsProfileSetup(true)} />;
134+
}
135+
136+
const showOnboarding = !hasOnboarded && !needsProfileSetup;
137+
const showUserForm = needsProfileSetup || (hasOnboarded && !userSettings);
119138

120139
return (
121140
<View className="flex-1 bg-stone-200">
122141
{showOnboarding && <Onboarding onComplete={persistHasOnboarded} />}
123142
{showUserForm && !showOnboarding && (
124-
<UserInfoForm initialValues={userSettings || undefined} onSubmit={persistUserSettings} />
143+
<UserInfoForm
144+
initialValues={userSettings || undefined}
145+
onSubmit={handleUserInfoSubmit}
146+
isSubmitting={profileSaving}
147+
/>
125148
)}
126149

127150
{!showOnboarding && !showUserForm && (

frontend/assets/adaptive-icon.png

71.4 KB
Loading

frontend/assets/icon.png

66.7 KB
Loading

frontend/assets/splash-icon.png

71.4 KB
Loading

frontend/assets/splash.png

71.4 KB
Loading

frontend/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
import './src/utils/polyfills';
12
import 'expo-router/entry';

frontend/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@
1717
"@expo/vector-icons": "^14.0.2",
1818
"@react-native-async-storage/async-storage": "^1.23.1",
1919
"@react-navigation/native": "^7.1.25",
20+
"amazon-cognito-identity-js": "^6.3.16",
21+
"buffer": "^6.0.3",
2022
"expo": "~54.0.29",
23+
"expo-constants": "^18.0.12",
2124
"expo-router": "^6.0.19",
2225
"expo-status-bar": "~3.0.9",
2326
"nativewind": "^4.2.1",
2427
"react": "19.1.0",
25-
"react-native-css-interop": "^0.2.1",
2628
"react-native": "0.81.5",
29+
"react-native-css-interop": "^0.2.1",
2730
"react-native-gesture-handler": "^2.29.1",
31+
"react-native-get-random-values": "^2.0.0",
2832
"react-native-reanimated": "^4.2.0",
2933
"react-native-safe-area-context": "^5.6.2",
3034
"react-native-screens": "^4.18.0",
35+
"react-native-url-polyfill": "^3.0.0",
3136
"react-native-worklets": "^0.7.1",
3237
"tailwindcss": "^3.4.19"
3338
},

0 commit comments

Comments
 (0)