Skip to content

refactor - QA반영#17

Merged
mark77234 merged 9 commits into
devfrom
refactor/14
Jan 11, 2026
Merged

refactor - QA반영#17
mark77234 merged 9 commits into
devfrom
refactor/14

Conversation

@mark77234

Copy link
Copy Markdown
Collaborator

작업내용

  • users/me/token 으로 서버에 푸시알림 토큰 전달
  • 2026 1월 1일 전 데이터 조회 막기
  • 알림 설정 5분단위로 받기
  • 로그인/회원가입 입력창 글자 잘림
  • 유저 정보 입력 페이지 사이즈 일정하게
  • 회원가입 시 users/me PATCH로 사용자 정보 업데이트 및 유저정보페이지 라우팅
  • 로그인 시 users/me GET으로 사용자 정보 조회 및 메인화면으로 라우팅
  • 메인화면 따옴표 제거

#14

Copilot AI review requested due to automatic review settings January 11, 2026 09:43
@mark77234 mark77234 linked an issue Jan 11, 2026 that may be closed by this pull request
8 tasks
@mark77234 mark77234 self-assigned this Jan 11, 2026
@mark77234 mark77234 merged commit d153289 into dev Jan 11, 2026
2 of 3 checks passed
@mark77234 mark77234 deleted the refactor/14 branch January 11, 2026 09:43
@mark77234 mark77234 changed the title Refactor/14 refactor - QA반영 Jan 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown

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 multiple UX improvements and refactoring changes focused on user profile management, notification settings, and authentication flow. The changes add push token synchronization, enforce 5-minute intervals for notification times, restrict fortune data access to dates after January 1, 2026, and improve the login/signup experience.

Changes:

  • Added push token service to sync device tokens with the backend via /users/me/token
  • Implemented 5-minute interval validation for notification times
  • Added minimum date restriction (2026-01-01) for fortune data access
  • Refactored authentication flow to use persistent login state and profile fetching
  • Fixed input text clipping issues and improved UI consistency

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/types/fortune.ts Updated comment to document 5-minute increment requirement for notification time
src/services/userProfileService.ts Added validation function to enforce 5-minute intervals on notification times; moved payload construction inside try block
src/services/pushTokenService.ts New service to handle push token synchronization with backend API
src/components/UserInfoForm.tsx Added 5-minute step validation for notification time picker; fixed input text sizing
src/components/SettingsSheet.tsx Refactored to use callback props for logout/unauthorized; added notification time picker with 5-minute intervals
src/components/LoginScreen.tsx Added INPUT_STYLE constant and fixed text alignment to prevent clipping
src/components/CalendarPage.tsx Added MIN_FORTUNE_DATE constant to restrict viewing dates before 2026-01-01
app/index.tsx Implemented persistent login state, automatic profile fetching on sign-in, and push token synchronization
app.config.ts Bumped version to 1.0.5

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +116 to +125
if (
!Number.isFinite(hour) ||
!Number.isFinite(minute) ||
hour < 0 ||
hour > 23 ||
minute < 0 ||
minute > 59
) {
throw new ProfileApiError('알림 시간 형식이 올바르지 않습니다.');
}

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

The validation logic checks if hour and minute are negative, but Number() conversion of strings like "00" or "01" will never be negative. The real issue is checking if hour/minute are less than 0 after they've been successfully parsed from a regex match of digits. Consider removing the unnecessary negative checks (hour < 0, minute < 0) since the regex pattern /^(\d{1,2}):(\d{1,2})$/ already ensures non-negative integers.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +56
if (
!Number.isFinite(hourNumber) ||
!Number.isFinite(minuteNumber) ||
hourNumber > 23 ||
minuteNumber > 59
) {
return { hour: fallbackHour, minute: fallbackMinute };
}
return { hour, minute };

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

The same validation logic issue exists here - checking if hour/minute are less than 0 after regex parsing is redundant since the regex /(\d{1,2}):(\d{1,2})/ already ensures non-negative digits. Consider removing the hour < 0 and minute < 0 checks.

Copilot uses AI. Check for mistakes.
Comment thread app/index.tsx
Comment on lines +134 to +138
const requireLogin = useCallback(() => {
setForceLogin(true);
setIsSettingsOpen(false);
void signOut();
}, [signOut]);

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

The requireLogin function sets forceLogin to true and calls signOut, but it doesn't clear the HAS_LOGGED_IN_KEY from AsyncStorage or reset hasLoggedIn state. This means after logout, the persisted login state remains true. Consider adding await AsyncStorage.removeItem(HAS_LOGGED_IN_KEY) and setHasLoggedIn(false) to properly clear the login state when requiring re-authentication.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +135
const ITEM_HEIGHT = 36;
const VISIBLE_ITEMS = 5;
const DEFAULT_NOTIFICATION_TIME = '08:00';
const NOTIFICATION_MINUTE_STEP = 5;

const pad2 = (value: number) => String(value).padStart(2, '0');

const parseTimeParts = (value: string, fallback: string) => {
const [fallbackHour, fallbackMinute] = fallback.split(':');
const match = value.match(/(\d{1,2}):(\d{1,2})/);
if (!match) return { hour: fallbackHour, minute: fallbackMinute };
const hourNumber = Number(match[1]);
const minuteNumber = Number(match[2]);
const hour = pad2(hourNumber);
const minute = pad2(minuteNumber);
if (
!Number.isFinite(hourNumber) ||
!Number.isFinite(minuteNumber) ||
hourNumber > 23 ||
minuteNumber > 59
) {
return { hour: fallbackHour, minute: fallbackMinute };
}
return { hour, minute };
};

const normalizeMinuteToStep = (minute: string, step: number) => {
const numeric = Number(minute);
if (!Number.isFinite(numeric)) return minute;
const normalized = Math.floor(numeric / step) * step;
const bounded = Math.min(Math.max(normalized, 0), 59);
return pad2(bounded);
};

const WheelPicker: React.FC<{
options: string[];
value: string;
onChange: (value: string) => void;
itemTextClassName?: string;
}> = ({ options, value, onChange, itemTextClassName = 'text-base' }) => {
const scrollRef = useRef<ScrollView | null>(null);
const padding = ((VISIBLE_ITEMS - 1) / 2) * ITEM_HEIGHT;

useEffect(() => {
if (!options.length) return;
const index = Math.max(0, options.indexOf(value));
scrollRef.current?.scrollTo({ y: index * ITEM_HEIGHT, animated: false });
}, [options, value]);

const handleScrollEnd = (event: { nativeEvent: { contentOffset: { y: number } } }) => {
if (!options.length) return;
const offsetY = event.nativeEvent.contentOffset.y;
const index = Math.round(offsetY / ITEM_HEIGHT);
const bounded = Math.max(0, Math.min(index, options.length - 1));
const nextValue = options[bounded];
if (nextValue !== value) onChange(nextValue);
};

return (
<View className="overflow-hidden" style={{ height: ITEM_HEIGHT * VISIBLE_ITEMS }}>
<ScrollView
ref={scrollRef}
showsVerticalScrollIndicator={false}
snapToInterval={ITEM_HEIGHT}
decelerationRate="fast"
onMomentumScrollEnd={handleScrollEnd}
onScrollEndDrag={handleScrollEnd}
nestedScrollEnabled
contentContainerStyle={{ paddingVertical: padding }}
>
{options.map((option) => {
const isSelected = option === value;
return (
<View
key={option}
style={{ height: ITEM_HEIGHT }}
className="items-center justify-center"
>
<Text
className={`${isSelected ? 'text-gray-900' : 'text-gray-400'} ${itemTextClassName} `}
>
{option}
</Text>
</View>
);
})}
</ScrollView>
<View
pointerEvents="none"
style={{
position: 'absolute',
left: 0,
right: 0,
top: padding,
height: ITEM_HEIGHT,
borderTopWidth: 1,
borderBottomWidth: 1,
borderColor: '#e5e7eb',
}}
/>
</View>
);
};

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

There is significant code duplication between UserInfoForm and SettingsSheet components. Both define identical helper functions (pad2, parseTimeParts, normalizeMinuteToStep), constants (ITEM_HEIGHT, VISIBLE_ITEMS, NOTIFICATION_MINUTE_STEP), and the WheelPicker component. Consider extracting these shared utilities and components into separate files to improve maintainability and reduce code duplication.

Copilot uses AI. Check for mistakes.
Comment on lines +279 to 281
const handleLogout = () => {
onLogout();
};

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

The handleLogout function has been changed from async to synchronous and now only calls onLogout(). However, the component still uses isLoading from useAuth to disable the logout button (line 379, not shown in diff). Since handleLogout no longer performs async operations directly, the isLoading state from useAuth may not accurately represent the logout operation anymore. Consider reviewing whether this isLoading check is still needed.

Copilot uses AI. Check for mistakes.
<Pressable className="rounded-xl bg-gray-100 px-4 py-4" onPress={openBirthDateModal}>
<View className="flex-row items-center justify-between">
<Text className="text-2xl font-semibold text-gray-900">{birthSummary}</Text>
<Text className="text-lg font-semibold text-gray-900">{birthSummary}</Text>

Copilot AI Jan 11, 2026

Copy link

Choose a reason for hiding this comment

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

The text size for the birth date summary was changed from text-2xl to text-lg, which is a reduction from 1.5rem (24px) to 1.125rem (18px). This is a significant visual change that affects consistency with other parts of the form. Consider verifying this matches the design requirements and is consistent with other similar fields in the form.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor - 푸시알림 토큰 전달 및 디테일 수정

2 participants