-
Notifications
You must be signed in to change notification settings - Fork 0
refactor - 프로젝트 초기 세팅 작업 #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements the initial project setup for a daily fortune calendar app ("하루 일력") with an interactive calendar interface, fortune display, and unique paper-tearing navigation using a draggable scissors icon.
Key Changes
- Refactored from tab-based navigation to a single-screen fortune calendar app with onboarding flow
- Integrated NativeWind (Tailwind CSS v3) for styling with proper configuration
- Implemented interactive calendar page with animated scissors-drag navigation and dual-page scroll (calendar + fortune details)
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/package.json | Added AsyncStorage and vector icons dependencies; downgraded Tailwind to v3.4.19 for NativeWind compatibility |
| frontend/tailwind.config.js | Added NativeWind preset for React Native Tailwind support |
| frontend/metro.config.js | Integrated NativeWind with global CSS configuration |
| frontend/eslint.config.js | Added runtime and Node globals configuration for config files |
| frontend/src/types/fortune.ts | Defined TypeScript types for user settings and fortune data structure |
| frontend/src/services/geminiService.ts | Created placeholder service for fetching daily fortune data |
| frontend/src/constants/config.ts | Added safe environment variable access for React Native |
| frontend/src/components/UserInfoForm.tsx | Implemented user information form for initial setup |
| frontend/src/components/SettingsSheet.tsx | Created settings modal for updating user preferences |
| frontend/src/components/Onboarding.tsx | Added onboarding screen with app feature guide |
| frontend/src/components/CalendarPage.tsx | Built main calendar page with animated scissors navigation and fortune display |
| frontend/src/components/Button.tsx | Refactored to use Pressable instead of View for proper touch handling |
| frontend/app/index.tsx | Implemented main app logic with state management and AsyncStorage persistence |
| frontend/app/_layout.tsx | Updated layout to single-screen navigation structure |
| frontend/.github/workflows/lint-test.yml | Added CI workflow for linting on push and pull requests |
Files not reviewed (1)
- frontend/pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)
frontend/app/index.tsx:2
- Unused import Pressable.
import { ActivityIndicator, Alert, Pressable, View } from 'react-native';
frontend/app/index.tsx:13
- Unused import Feather.
import { Feather } from '@expo/vector-icons';
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| await AsyncStorage.setItem(HAS_ONBOARDED_KEY, 'true'); | ||
| }; | ||
|
|
||
| const persistUserSettings = async (settings: UserSettings) => { | ||
| setUserSettings(settings); | ||
| await AsyncStorage.setItem(USER_SETTINGS_KEY, JSON.stringify(settings)); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error handling for AsyncStorage.setItem is missing. If persisting the onboarding state fails, the UI state and stored state will be out of sync. Consider wrapping the AsyncStorage call in a try-catch block and handling errors appropriately.
| await AsyncStorage.setItem(HAS_ONBOARDED_KEY, 'true'); | |
| }; | |
| const persistUserSettings = async (settings: UserSettings) => { | |
| setUserSettings(settings); | |
| await AsyncStorage.setItem(USER_SETTINGS_KEY, JSON.stringify(settings)); | |
| try { | |
| await AsyncStorage.setItem(HAS_ONBOARDED_KEY, 'true'); | |
| } catch (error) { | |
| console.warn('Failed to persist onboarding state', error); | |
| Alert.alert( | |
| '저장에 실패했어요', | |
| '온보딩 상태를 저장하지 못했습니다. 앱을 다시 시작하면 온보딩을 다시 진행해야 할 수 있어요.', | |
| ); | |
| } | |
| }; | |
| const persistUserSettings = async (settings: UserSettings) => { | |
| setUserSettings(settings); | |
| try { | |
| await AsyncStorage.setItem(USER_SETTINGS_KEY, JSON.stringify(settings)); | |
| } catch (error) { | |
| console.warn('Failed to persist user settings', error); | |
| Alert.alert( | |
| '설정을 저장하지 못했어요', | |
| '다시 시도해 주세요. 문제가 계속되면 앱을 재시작해 보세요.', | |
| ); | |
| } |
|
|
||
| <View | ||
| className="z-10 bg-white px-4" | ||
| style={{ paddingTop: insets.top + 8 }} // ✅ 노치 / 다이나믹 아일랜드 대응 |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is in Korean and includes emojis. While this may be intentional for a Korean-language project, it's inconsistent with the inline comment style in other parts of the codebase. Consider using a consistent comment style throughout the project.
| style={{ paddingTop: insets.top + 8 }} // ✅ 노치 / 다이나믹 아일랜드 대응 | |
| style={{ paddingTop: insets.top + 8 }} // Add extra top padding for notch / dynamic island |
| <TextInput | ||
| value={form.birthdate} | ||
| onChangeText={(text) => update({ birthdate: text })} | ||
| placeholder="YYYY-MM-DD" | ||
| placeholderTextColor="#9ca3af" | ||
| className="rounded-xl bg-gray-100 px-3.5 py-3.5 text-base text-gray-900" | ||
| /> |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The birthdate input accepts free-form text without validation. Users could enter invalid dates like "2025-13-45" or malformed strings. Consider adding input validation, using a date picker component, or at least validating the format before allowing form submission.
| <Row label="생년월일" divider> | ||
| <TextInput | ||
| value={form.birthdate} | ||
| onChangeText={(text) => update({ birthdate: text })} | ||
| placeholder="YYYY-MM-DD" | ||
| placeholderTextColor="#d1d5db" | ||
| className="min-w-[120] text-right text-[15px] text-gray-900" | ||
| /> | ||
| </Row> |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The birthdate input in settings accepts free-form text without validation. Users could enter invalid dates. This is the same issue as in UserInfoForm - consider adding input validation or using a date picker component.
| type EnvRecord = Record<string, string | undefined>; | ||
|
|
||
| const env: EnvRecord = | ||
| typeof process !== 'undefined' && process.env ? (process.env as EnvRecord) : {}; |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The environment variable check typeof process !== 'undefined' && process.env may not be sufficient in all React Native environments. Consider using a more robust environment configuration library like react-native-config or expo-constants for better cross-platform support.
| type EnvRecord = Record<string, string | undefined>; | |
| const env: EnvRecord = | |
| typeof process !== 'undefined' && process.env ? (process.env as EnvRecord) : {}; | |
| import Config from 'react-native-config'; | |
| type EnvRecord = Record<string, string | undefined>; | |
| const env: EnvRecord = | |
| (Config as unknown as EnvRecord) || | |
| (typeof process !== 'undefined' && process.env ? (process.env as EnvRecord) : {}); |
| > | ||
| {/* 상단 커스텀 헤더 */} | ||
| <View className="flex-row items-center justify-between mb-2"> | ||
| {/* 왼쪽 더미 공간 (에러 방지) */} |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Korean comment should be in English or use a consistent commenting style across the codebase. The "에러 방지" (error prevention) comment isn't descriptive enough about what error it's preventing.
| {/* 왼쪽 더미 공간 (에러 방지) */} | |
| {/* Left spacer to keep header content aligned and prevent layout overlap issues */} |
| <TextInput | ||
| value={form.notificationTime} | ||
| onChangeText={(text) => update({ notificationTime: text })} | ||
| placeholder="08:00" | ||
| placeholderTextColor="#9ca3af" | ||
| className="min-w-[80] text-right text-base text-gray-900" | ||
| /> |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The notification time input accepts free-form text without validation. Users could enter invalid time formats. Consider adding input validation to ensure the time is in the correct HH:mm format, or use a time picker component.
| <TextInput | ||
| value={form.notificationTime} | ||
| onChangeText={(text) => update({ notificationTime: text })} | ||
| placeholder="08:00" | ||
| placeholderTextColor="#d1d5db" | ||
| className="min-w-[120] text-right text-[15px] text-gray-900" | ||
| /> |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The notification time input accepts free-form text without validation. Users could enter invalid time formats. Consider adding input validation to ensure the time is in the correct HH:mm format, or use a time picker component.
| cutProgress.setValue(0); | ||
| scissorX.setValue(0); | ||
| scissorStartX.current = 0; | ||
| setTimeout(() => setIsTearing(false), 100); |
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using setTimeout without cleanup could lead to memory leaks or state updates on unmounted components. If the component unmounts before the 100ms timeout completes, the setIsTearing(false) call will attempt to update state on an unmounted component. Consider using a ref to track mount status or cleanup the timeout in a useEffect return function.
| @@ -0,0 +1,151 @@ | |||
| import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react'; | |||
| import { ActivityIndicator, Alert, Pressable, View } from 'react-native'; | |||
Copilot
AI
Dec 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unused import Pressable should be removed as it's not used anywhere in this file.
| import { ActivityIndicator, Alert, Pressable, View } from 'react-native'; | |
| import { ActivityIndicator, Alert, View } from 'react-native'; |

작업내용
설명
스크린샷