Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions frontend/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
// ===== App 기본 정보 =====
name: '운세한장',
slug: 'dailyfate',
version: '1.0.2',
version: '1.0.3',

// ===== 환경 변수 =====
extra: {
Expand Down Expand Up @@ -55,7 +55,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
...config.android,
package: 'com.dailyfate.frontend',

// @ts-ignore — Expo에서 실제 지원하는 속성
// @ts-expect-error - Expo config type does not include `label` yet.
label: '운세한장',

adaptiveIcon: {
Expand Down
26 changes: 19 additions & 7 deletions frontend/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@ export default function Home() {
const [userSettings, setUserSettings] = useState<UserSettings | null>(null);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [needsProfileSetup, setNeedsProfileSetup] = useState(false);
const [needsOnboarding, setNeedsOnboarding] = useState(false);
const [profileSaving, setProfileSaving] = useState(false);

const { isBootstrapping: authBootstrapping, isSignedIn, signOut } = useAuth();

const canLoadFortune =
isSignedIn && !!userSettings && !needsProfileSetup && (!needsOnboarding || hasOnboarded);

const {
fortune,
loading: loadingFortune,
error,
} = useFortune(currentDate, hasOnboarded && !!userSettings && isSignedIn);
} = useFortune(currentDate, canLoadFortune);

// Load persisted state
useEffect(() => {
Expand All @@ -61,6 +65,7 @@ export default function Home() {
if (isSignedIn) return;
setIsSettingsOpen(false);
setNeedsProfileSetup(false);
setNeedsOnboarding(false);
}, [isSignedIn]);

// Header: show only when main screen is active
Expand All @@ -78,15 +83,22 @@ export default function Home() {
await AsyncStorage.setItem(USER_SETTINGS_KEY, JSON.stringify(settings));
};

const handleSignUpSuccess = useCallback(() => {
setNeedsProfileSetup(true);
setNeedsOnboarding(true);
}, []);

const handleOnboardingComplete = () => {
setNeedsOnboarding(false);
void persistHasOnboarded();
};

const handleUserInfoSubmit = async (settings: UserSettings) => {
if (profileSaving) return;
setProfileSaving(true);
try {
const updatedSettings = await updateUserProfile(settings);
await persistUserSettings(updatedSettings);
if (!hasOnboarded) {
await persistHasOnboarded();
}
setNeedsProfileSetup(false);
} catch (error) {
if (error instanceof ProfileApiError && error.status === 401) {
Expand Down Expand Up @@ -136,15 +148,15 @@ export default function Home() {
}

if (!isSignedIn) {
return <LoginScreen />;
return <LoginScreen onSignUpSuccess={handleSignUpSuccess} />;
}

const showOnboarding = !hasOnboarded && !needsProfileSetup;
const showOnboarding = needsOnboarding && !needsProfileSetup;
const showUserForm = needsProfileSetup;

return (
<View className="flex-1 bg-white">
{showOnboarding && <Onboarding onComplete={persistHasOnboarded} />}
{showOnboarding && <Onboarding onComplete={handleOnboardingComplete} />}
{showUserForm && !showOnboarding && (
<UserInfoForm
initialValues={userSettings || undefined}
Expand Down
5 changes: 5 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ const prettierConfig = require('eslint-config-prettier');
const runtimeGlobals = {
console: 'readonly',
process: 'readonly',
fetch: 'readonly',
Headers: 'readonly',
Request: 'readonly',
Response: 'readonly',
AbortController: 'readonly',
setTimeout: 'readonly',
clearTimeout: 'readonly',
setInterval: 'readonly',
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CalendarPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ const CalendarPage: React.FC<Props> = ({
</View>
) : fortune ? (
<Text className="text-center font-serif text-lg font-semibold leading-7 text-gray-600 font-noto">
" {fortune.overview} "
&quot; {fortune.overview} &quot;
</Text>
) : (
<Text className="text-center font-serif text-sm text-gray-300">
Expand Down
81 changes: 42 additions & 39 deletions frontend/src/components/Onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
import React from 'react';
import { Pressable, Text, View } from 'react-native';
import { Feather } from '@expo/vector-icons';
import { SafeAreaView } from 'react-native-safe-area-context';

interface Props {
onComplete: () => void;
}

const Onboarding: React.FC<Props> = ({ onComplete }) => {
return (
<View className="absolute inset-0 z-10 items-center justify-center bg-stone-200 px-5 py-6">
<View className="w-full max-w-xl rounded-2xl bg-white p-5 shadow-2xl shadow-black/10">
<View className="items-center space-y-3.5">
<View className="h-14 w-14 items-center justify-center rounded-full bg-gray-100">
<Feather name="star" size={26} color="#111827" />
</View>
<Text className="text-2xl font-bold text-gray-900">하루 일력</Text>
<Text className="text-center text-sm leading-5 text-gray-500">
매일 아침, 종이 일력을 뜯듯 당신의 하루를 확인해보세요.
</Text>
<SafeAreaView edges={['top']} className="flex-1 bg-white p-4">
<View>
<View className="w-full rounded-2xl bg-white gap-8">
<View className="items-center gap-6">
<View className="h-14 w-14 items-center justify-center rounded-full bg-gray-100">
<Feather name="star" size={26} color="#111827" />
</View>
<Text className="text-4xl font-bold text-gray-900">하루 일력</Text>
<Text className="text-center text-lg leading-5 text-gray-500">
매일 아침, 종이 일력을 뜯듯 당신의 하루를 확인해보세요.
</Text>

<View className="w-full space-y-3 rounded-xl border border-gray-200 bg-gray-50 p-4">
<GuideItem
icon="scissors"
title="다음 날로 넘기기"
text="상단을 눌러 종이를 뜯듯 내일로 넘어가요."
/>
<GuideItem
icon="calendar"
title="오늘의 총평"
text="큰 날짜 아래 핵심 운세를 바로 볼 수 있어요."
/>
<GuideItem
icon="chevron-down"
title="상세 운세"
text="스크롤로 재물·애정·성공운을 확인하세요."
/>
<View className="w-full rounded-xl border border-gray-200 bg-gray-50 p-4 gap-8">
<GuideItem
icon="scissors"
title="다음 날로 넘기기"
text="상단을 눌러 종이를 뜯듯 내일로 넘어가요."
/>
<GuideItem
icon="calendar"
title="오늘의 총평"
text="큰 날짜 아래 핵심 운세를 바로 볼 수 있어요."
/>
<GuideItem
icon="chevron-down"
title="상세 운세"
text="스크롤로 재물·애정·성공운을 확인하세요."
/>
</View>
</View>
</View>

<Pressable
className="mt-5 rounded-xl bg-gray-900 py-3.5 active:opacity-90"
onPress={onComplete}
accessibilityLabel="온보딩 완료"
>
<Text className="text-center text-lg font-bold text-white">시작하기</Text>
</Pressable>
<Pressable
className="mt-5 rounded-xl bg-gray-900 py-3.5 active:opacity-90"
onPress={onComplete}
accessibilityLabel="온보딩 완료"
>
<Text className="text-center text-lg font-bold text-white">시작하기</Text>
</Pressable>
</View>
</View>
Comment on lines +13 to 51
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an unnecessary wrapper View element that doesn't add any styling or functionality. The SafeAreaView could directly contain the inner View with the rounded-2xl styling, removing one level of nesting and simplifying the component structure.

Copilot uses AI. Check for mistakes.
</View>
</SafeAreaView>
);
};

Expand All @@ -59,11 +62,11 @@ const GuideItem = ({
title: string;
text: string;
}) => (
<View className="flex-row space-x-3">
<Feather name={icon} size={16} color="#6b7280" style={{ marginTop: 2 }} />
<View className="flex-row gap-4 items-center">
<Feather name={icon} size={25} color="#6b7280" style={{ marginTop: 2 }} />
<View className="flex-1">
<Text className="text-sm font-bold text-gray-900">{title}</Text>
<Text className="mt-0.5 text-xs leading-5 text-gray-500">{text}</Text>
<Text className="text-xl font-bold text-gray-900">{title}</Text>
<Text className="mt-0.5 text-lg leading-5 text-gray-500">{text}</Text>
</View>
</View>
);
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/SettingsSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Modal,
Platform,
Pressable,
Switch,
Text,
TextInput,
View,
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/UserInfoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ interface Props {
const genderOptions: { val: Gender; label: string }[] = [
{ val: 'male', label: '남성' },
{ val: 'female', label: '여성' },
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Gender type definition still includes 'other' as a valid option, but this option has been removed from the UI. This creates a type-safety inconsistency where the type allows values that cannot be selected by users. The type definition should be updated to match the available options.

Additionally, the backend mapping functions mapGenderFromApi and mapGenderToApi still handle 'other' (mapped to 'O'), which could cause issues if existing user data contains this value or if the backend still accepts it.

Suggested change
{ val: 'female', label: '여성' },
{ val: 'female', label: '여성' },
{ val: 'other', label: '기타' },

Copilot uses AI. Check for mistakes.
{ val: 'other', label: '기타' },
];

const ITEM_HEIGHT = 36;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/providers/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
try {
const token = await getAuthToken();
if (active) setIsSignedIn(Boolean(token));
} catch (_err) {
} catch {
if (active) setIsSignedIn(false);
} finally {
if (active) setIsBootstrapping(false);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/services/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const loadCache = async () => {
if (tokenRaw) {
try {
cachedTokens = JSON.parse(tokenRaw) as AuthTokens;
} catch (_err) {
} catch {
cachedTokens = null;
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/services/fortuneService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ const readPayload = async (response: Response) => {
if (!rawText) return { payload: null, rawText: null };
try {
return { payload: JSON.parse(rawText) as FortuneApiResponse, rawText };
} catch (_error) {
} catch {
return { payload: null, rawText };
}
} catch (_error) {
} catch {
return { payload: null, rawText: null };
}
};
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/services/userProfileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ const readPayload = async <T>(response: Response) => {
if (!rawText) return { payload: null, rawText: null };
try {
return { payload: JSON.parse(rawText) as T, rawText };
} catch (_error) {
} catch {
return { payload: null, rawText };
}
} catch (_error) {
} catch {
return { payload: null, rawText: null };
}
};
Expand Down