diff --git a/app/(tabs)/history.tsx b/app/(tabs)/history.tsx index 4e96714..2a0fef4 100644 --- a/app/(tabs)/history.tsx +++ b/app/(tabs)/history.tsx @@ -1,19 +1,19 @@ import { PageLayout } from '@/components/bestai/layout'; +import { getTemplateGenerations, TemplateGeneration } from '@/lib/api/template-generations'; +import { router } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { ActivityIndicator, Image, + Platform, + RefreshControl, ScrollView, StyleSheet, Text, - View, - RefreshControl, - Platform, - TouchableOpacity + TouchableOpacity, + View } from 'react-native'; -import { getTemplateGenerations, TemplateGeneration } from '@/lib/api/template-generations'; -import { router } from 'expo-router'; const LAYOUT_CONFIG = { VIDEO_HEIGHT: 280, @@ -392,8 +392,8 @@ export default function HistoryScreen() { const styles = StyleSheet.create({ scrollContent: { - paddingTop: 24, - paddingBottom: 48, + paddingTop: 14, + paddingBottom: 14, }, heading: { fontSize: 24, diff --git a/app/exchange.tsx b/app/exchange.tsx index a216ba3..e6693d9 100644 --- a/app/exchange.tsx +++ b/app/exchange.tsx @@ -5,7 +5,6 @@ import { useRouter } from 'expo-router'; import React, { useCallback, useMemo, useState } from 'react'; import { ActivityIndicator, - Alert, ScrollView, StyleSheet, Text, @@ -14,6 +13,7 @@ import { View } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Alert } from '@/utils/alert'; type PurchaseTab = 'subscription' | 'pack'; diff --git a/components/bestai/header.tsx b/components/bestai/header.tsx index 1855db4..21c70d7 100644 --- a/components/bestai/header.tsx +++ b/components/bestai/header.tsx @@ -19,7 +19,8 @@ export const Header = memo(function Header({ title = 'BESTAI' }: HeaderProps) { const styles = StyleSheet.create({ container: { alignItems: 'center', - marginBottom: 28, + marginBottom: 14, + marginTop: 14 }, logo: { width: 89, diff --git a/components/profile/profile-header.tsx b/components/profile/profile-header.tsx index c49b45d..ea52b7e 100644 --- a/components/profile/profile-header.tsx +++ b/components/profile/profile-header.tsx @@ -130,7 +130,8 @@ const styles = { flexDirection: 'row' as const, alignItems: 'center' as const, justifyContent: 'space-between' as const, - marginBottom: 28, + marginBottom: 14, + marginTop: 14 }, settingsButton: { width: 44, diff --git a/components/profile/profile-screen.tsx b/components/profile/profile-screen.tsx index 6ee8055..1ca82fd 100644 --- a/components/profile/profile-screen.tsx +++ b/components/profile/profile-screen.tsx @@ -1,13 +1,14 @@ -import React, { useState, useMemo } from 'react'; -import { View, useWindowDimensions, type ImageSourcePropType, StyleSheet } from 'react-native'; +import React, { useMemo, useState } from 'react'; +import { StyleSheet, View, useWindowDimensions, type ImageSourcePropType } from 'react-native'; import { PageLayout } from '@/components/bestai/layout'; import { useAuth } from '@/hooks/use-auth'; import { useBalance } from '@/hooks/use-balance'; import { useProfileData } from '@/hooks/use-profile-data'; -import { createStats, deriveAvatarSource, deriveDisplayName } from '@/utils/profile-data'; import { PROFILE_THEME } from '@/theme/profile'; +import { createStats, deriveAvatarSource, deriveDisplayName } from '@/utils/profile-data'; import { ContentGallery } from './content-gallery'; +import { ContentSkeleton } from './content-skeleton'; import { ContentTabs } from './content-tabs'; import { Divider } from './divider'; import { ProfileEditModal } from './profile-edit-modal'; @@ -16,7 +17,6 @@ import { ProfileErrorState } from './profile-error-state'; import { ProfileHeader } from './profile-header'; import { ProfileIdentity } from './profile-identity'; import { ProfileLoadingState } from './profile-loading-state'; -import { ContentSkeleton } from './content-skeleton'; type TabKey = 'all' | 'image' | 'video'; type BillingMode = 'monthly' | 'lifetime'; diff --git a/hooks/use-pricing.ts b/hooks/use-pricing.ts index 9c4100c..15b5ab8 100644 --- a/hooks/use-pricing.ts +++ b/hooks/use-pricing.ts @@ -1,8 +1,9 @@ import { useState, useEffect, useRef, useMemo } from 'react'; -import { Alert, Linking } from 'react-native'; +import { Linking } from 'react-native'; import { useRouter } from 'expo-router'; import { authClient, useSession } from '@/lib/auth/client'; import { getStripePlans, getPlanNames } from '@/lib/api/pricing'; +import { Alert } from '@/utils/alert'; import type { StripePricingTableResponse, CreditPlan, diff --git a/utils/alert.ts b/utils/alert.ts new file mode 100644 index 0000000..56e77e8 --- /dev/null +++ b/utils/alert.ts @@ -0,0 +1,44 @@ +import { Alert as RNAlert, Platform } from 'react-native'; + +interface AlertButton { + text: string; + onPress?: () => void; + style?: 'default' | 'cancel' | 'destructive'; +} + +class PlatformAlert { + alert(title: string, message?: string, buttons?: AlertButton[]) { + if (Platform.OS === 'web') { + this.webAlert(title, message, buttons); + } else { + RNAlert.alert(title, message, buttons); + } + } + + private webAlert(title: string, message?: string, buttons?: AlertButton[]) { + const fullMessage = message ? `${title}\n\n${message}` : title; + + if (!buttons || buttons.length === 0) { + window.alert(fullMessage); + return; + } + + if (buttons.length === 1) { + window.alert(fullMessage); + buttons[0].onPress?.(); + return; + } + + const confirmed = window.confirm(fullMessage); + + if (confirmed) { + const confirmButton = buttons.find(btn => btn.style !== 'cancel'); + confirmButton?.onPress?.(); + } else { + const cancelButton = buttons.find(btn => btn.style === 'cancel'); + cancelButton?.onPress?.(); + } + } +} + +export const Alert = new PlatformAlert();