Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { forwardRef, useCallback, useMemo, useState } from 'react';
import { Text, View } from 'react-native';
import { Pressable, Text, View } from 'react-native';
import BottomSheet, {
BottomSheetBackdrop,
BottomSheetBackdropProps,
BottomSheetView,
} from '@gorhom/bottom-sheet';
import * as WebBrowser from 'expo-web-browser';
import { colors } from '@theme/tokens';
import { CheckIcon, ChevronRightIcon } from 'lucide-react-native';
import { AnimatedPressable, Container } from '@components/common';

const TERMS_URLS = {
service: 'https://www.notion.so/2b4fa6e6a8fe80119c13d84d794a63a3?source=copy_link',
privacy: 'https://www.notion.so/2b4fa6e6a8fe8031ac2aef3009552575?source=copy_link',
marketing: 'https://www.notion.so/2b4fa6e6a8fe80359e8bf33c870967d9?source=copy_link',
Comment on lines +14 to +16
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Consider moving the terms URLs to environment variables or a centralized configuration file. The app.config.ts file shows a pattern of using environment variables for URLs and configuration values (lines 134-145). Hardcoding Notion URLs directly in the component makes them harder to update across environments or when terms pages change. This would improve maintainability by centralizing configuration values.

Suggested change
service: 'https://www.notion.so/2b4fa6e6a8fe80119c13d84d794a63a3?source=copy_link',
privacy: 'https://www.notion.so/2b4fa6e6a8fe8031ac2aef3009552575?source=copy_link',
marketing: 'https://www.notion.so/2b4fa6e6a8fe80359e8bf33c870967d9?source=copy_link',
service:
process.env.EXPO_PUBLIC_TERMS_URL_SERVICE ??
'https://www.notion.so/2b4fa6e6a8fe80119c13d84d794a63a3?source=copy_link',
privacy:
process.env.EXPO_PUBLIC_TERMS_URL_PRIVACY ??
'https://www.notion.so/2b4fa6e6a8fe8031ac2aef3009552575?source=copy_link',
marketing:
process.env.EXPO_PUBLIC_TERMS_URL_MARKETING ??
'https://www.notion.so/2b4fa6e6a8fe80359e8bf33c870967d9?source=copy_link',

Copilot uses AI. Check for mistakes.
} as const;

type AgreementState = {
age: boolean;
service: boolean;
Expand Down Expand Up @@ -124,23 +131,27 @@ const TermsConsentSheet = forwardRef<BottomSheet, TermsConsentSheetProps>(
onToggle={() => toggleAgreement('service')}
label='(필수) 서비스 이용약관 동의'
withChevron
onChevronPress={() => WebBrowser.openBrowserAsync(TERMS_URLS.service)}
/>
<ConsentRow
checked={agreements.privacy}
onToggle={() => toggleAgreement('privacy')}
label='(필수) 개인정보 수집 및 이용 필수동의'
withChevron
onChevronPress={() => WebBrowser.openBrowserAsync(TERMS_URLS.privacy)}
/>
<ConsentRow
checked={agreements.marketing}
onToggle={() => toggleAgreement('marketing')}
label='(선택) 마케팅 정보 수신 동의'
withChevron
onChevronPress={() => WebBrowser.openBrowserAsync(TERMS_URLS.marketing)}
Comment on lines +134 to +148
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The WebBrowser.openBrowserAsync calls should include error handling. This API can fail for various reasons (network issues, browser not available, user cancellation, etc.) and currently there's no handling for these failures. Consider wrapping these calls in try-catch blocks or using .catch() to handle potential errors gracefully. This is important for production reliability as seen in other parts of the codebase where async operations are wrapped in try-catch blocks.

Suggested change
onChevronPress={() => WebBrowser.openBrowserAsync(TERMS_URLS.service)}
/>
<ConsentRow
checked={agreements.privacy}
onToggle={() => toggleAgreement('privacy')}
label='(필수) 개인정보 수집 및 이용 필수동의'
withChevron
onChevronPress={() => WebBrowser.openBrowserAsync(TERMS_URLS.privacy)}
/>
<ConsentRow
checked={agreements.marketing}
onToggle={() => toggleAgreement('marketing')}
label='(선택) 마케팅 정보 수신 동의'
withChevron
onChevronPress={() => WebBrowser.openBrowserAsync(TERMS_URLS.marketing)}
onChevronPress={() => {
WebBrowser.openBrowserAsync(TERMS_URLS.service).catch(error => {
console.error('Failed to open service terms URL:', error);
});
}}
/>
<ConsentRow
checked={agreements.privacy}
onToggle={() => toggleAgreement('privacy')}
label='(필수) 개인정보 수집 및 이용 필수동의'
withChevron
onChevronPress={() => {
WebBrowser.openBrowserAsync(TERMS_URLS.privacy).catch(error => {
console.error('Failed to open privacy terms URL:', error);
});
}}
/>
<ConsentRow
checked={agreements.marketing}
onToggle={() => toggleAgreement('marketing')}
label='(선택) 마케팅 정보 수신 동의'
withChevron
onChevronPress={() => {
WebBrowser.openBrowserAsync(TERMS_URLS.marketing).catch(error => {
console.error('Failed to open marketing terms URL:', error);
});
}}

Copilot uses AI. Check for mistakes.
className='mb-[12px]'
/>
<AnimatedPressable
className={`my-[10px] items-center justify-center rounded-[12px] px-[12px] py-[10px] ${isRequiredChecked ? 'bg-primary-500' : 'bg-primary-200'
}`}
className={`my-[10px] items-center justify-center rounded-[12px] px-[12px] py-[10px] ${
isRequiredChecked ? 'bg-primary-500' : 'bg-primary-200'
}`}
disabled={!isRequiredChecked}
onPress={handleConfirm}>
<Text className='text-16m text-white'>다음</Text>
Expand All @@ -159,6 +170,7 @@ type ConsentRowProps = {
withChevron?: boolean;
isBold?: boolean;
onToggle: () => void;
onChevronPress?: () => void;
className?: string;
};

Expand All @@ -169,6 +181,7 @@ const ConsentRow = ({
withChevron,
isBold,
onToggle,
onChevronPress,
className,
}: ConsentRowProps) => {
return (
Expand All @@ -180,16 +193,26 @@ const ConsentRow = ({
disableScale>
<View className='flex-1 flex-row gap-[10px]'>
<View
className={`h-[24px] w-[24px] items-center justify-center rounded-[6px] border ${checked ? 'border-blue-500 bg-blue-500' : 'border-gray-600 bg-white'
}`}>
className={`h-[24px] w-[24px] items-center justify-center rounded-[6px] border ${
checked ? 'border-blue-500 bg-blue-500' : 'border-gray-600 bg-white'
}`}>
{checked ? <CheckIcon size={20} strokeWidth={2} color='white' /> : null}
</View>
<View className='flex-1 gap-[2px]'>
<Text className={`${isBold ? 'text-16sb' : 'text-16m'} text-gray-900`}>{label}</Text>
{description ? <Text className='text-14r text-gray-600'>{description}</Text> : null}
</View>
</View>
{withChevron ? <ChevronRightIcon size={18} color={colors['gray-600']} /> : null}
{withChevron ? (
<Pressable
onPress={(e) => {
e.stopPropagation();
onChevronPress?.();
}}
hitSlop={8}>
<ChevronRightIcon size={18} color={colors['gray-600']} />
</Pressable>
) : null}
</AnimatedPressable>
);
};
Expand Down