fix: error

This commit is contained in:
imeepos
2025-10-29 19:38:04 +08:00
parent 3a99ff96d5
commit 30ea4fb13c
34 changed files with 2185 additions and 870 deletions

View File

@@ -1,8 +1,7 @@
import { ThemedText } from "@/components/themed-text";
import { ThemedView } from "@/components/themed-view";
import { useAuth } from "@/hooks/use-auth";
import { authClient } from "@/lib/auth/client";
import { Feather, Ionicons } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
import { router } from "expo-router";
import { StatusBar } from "expo-status-bar";
import { useEffect, useState } from "react";
import {
ActivityIndicator,
@@ -10,15 +9,21 @@ import {
KeyboardAvoidingView,
Platform,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import { storage } from '../../lib/storage';
import { SafeAreaView } from "react-native-safe-area-context";
import { useAuth } from "@/hooks/use-auth";
import { authClient } from "@/lib/auth/client";
import { storage } from "@/lib/storage";
export default function LoginScreen() {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isPasswordVisible, setPasswordVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { isAuthenticated, isLoading: authLoading } = useAuth();
@@ -32,37 +37,42 @@ export default function LoginScreen() {
const handleLogin = async () => {
console.log("[Login] handleLogin called");
if (!username.trim() || !password.trim()) {
console.log("[Login] Validation failed: empty username or password");
Alert.alert("错误", "请输入用户名和密码");
if (!email.trim() || !password.trim()) {
console.log("[Login] Validation failed: empty email or password");
Alert.alert("错误", "请输入邮箱和密码");
return;
}
console.log("[Login] Starting login with username:", username.trim());
console.log("[Login] Starting login with email:", email.trim());
setIsLoading(true);
try {
console.log("[Login] Calling authClient.signIn.username...");
await new Promise<void>(async (resolve, reject) => {
await authClient.signIn.username({
username: username.trim(),
password,
}, {
onSuccess: async (ctx) => {
const authToken = ctx.response.headers.get("set-auth-token")
if (authToken) {
await storage.setItem(`bestaibest.better-auth.session_token`, authToken);
resolve()
} else {
console.error("Bearer token not set");
reject()
}
await authClient.signIn.username(
{
username: email.trim(),
password,
},
{
onSuccess: async (ctx) => {
const authToken = ctx.response.headers.get("set-auth-token");
if (authToken) {
await storage.setItem(
`bestaibest.better-auth.session_token`,
authToken
);
resolve();
} else {
console.error("Bearer token not set");
reject();
}
},
}
});
})
);
});
} catch (error: any) {
Alert.alert("登录失败", error?.message || "用户名或密码错误");
Alert.alert("登录失败", error?.message || "邮箱或密码错误");
} finally {
setIsLoading(false);
console.log("[Login] handleLogin completed");
@@ -70,111 +80,248 @@ export default function LoginScreen() {
};
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={styles.container}
>
<ThemedView style={styles.content}>
<ThemedText type="title" style={styles.title}>
</ThemedText>
<ThemedText style={styles.subtitle}></ThemedText>
<LinearGradient colors={["#050506", "#0b0c0f"]} style={styles.gradient}>
<SafeAreaView style={styles.safeArea}>
<StatusBar style="light" />
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : undefined}
style={styles.flex}
>
<View style={styles.screen}>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => router.back()}
style={styles.backButton}
>
<Feather name="arrow-left" size={20} color="#f2f4f8" />
</TouchableOpacity>
<View style={styles.form}>
<TextInput
style={styles.input}
placeholder="用户名"
value={username}
onChangeText={setUsername}
autoCapitalize="none"
autoCorrect={false}
editable={!isLoading}
/>
<View style={styles.sheet}>
<View style={styles.sheetHeader}>
<View style={styles.sheetTitleGroup}>
<View style={styles.iconBadge}>
<Ionicons name="mail-open" size={16} color="#ffffff" />
</View>
<Text style={styles.sheetTitle}>Email Login</Text>
</View>
<TextInput
style={styles.input}
placeholder="密码"
value={password}
onChangeText={setPassword}
secureTextEntry
editable={!isLoading}
/>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => router.push("/(auth)/register")}
disabled={isLoading}
>
<Text style={[styles.registerLabel, isLoading && styles.disabledText]}>
Register
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
style={[styles.button, isLoading && styles.buttonDisabled]}
onPress={handleLogin}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText style={styles.buttonText}></ThemedText>
)}
</TouchableOpacity>
<View style={styles.form}>
<View style={styles.inputWrapper}>
<TextInput
style={styles.input}
placeholder="yours@example.com"
placeholderTextColor="#70737c"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
autoCorrect={false}
keyboardType="email-address"
editable={!isLoading}
keyboardAppearance="dark"
/>
</View>
<TouchableOpacity
style={styles.registerLink}
onPress={() => router.push("/(auth)/register")}
disabled={isLoading}
>
<ThemedText style={styles.registerText}></ThemedText>
</TouchableOpacity>
</View>
</ThemedView>
</KeyboardAvoidingView>
<View style={styles.inputWrapper}>
<TextInput
style={styles.input}
placeholder="email password"
placeholderTextColor="#70737c"
value={password}
onChangeText={setPassword}
secureTextEntry={!isPasswordVisible}
autoCorrect={false}
editable={!isLoading}
keyboardAppearance="dark"
/>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => setPasswordVisible((visible) => !visible)}
style={styles.visibilityToggle}
>
<Feather
name={isPasswordVisible ? "eye-off" : "eye"}
size={18}
color="#8a8e97"
/>
</TouchableOpacity>
</View>
<TouchableOpacity
style={[styles.loginButton, isLoading && styles.loginButtonDisabled]}
onPress={handleLogin}
disabled={isLoading}
activeOpacity={0.85}
>
{isLoading ? (
<ActivityIndicator color="#0f100f" />
) : (
<Text style={styles.loginButtonText}>Log in</Text>
)}
</TouchableOpacity>
<View style={styles.termsRow}>
<Ionicons name="checkmark-circle" size={16} color="#d7ff1f" />
<Text style={styles.termsText}>
<Text style={styles.linkText}> </Text>
<Text style={styles.linkText}> </Text>
</Text>
</View>
</View>
</View>
</View>
</KeyboardAvoidingView>
</SafeAreaView>
</LinearGradient>
);
}
const styles = StyleSheet.create({
container: {
gradient: {
flex: 1,
},
content: {
safeArea: {
flex: 1,
padding: 24,
},
flex: {
flex: 1,
},
screen: {
flex: 1,
justifyContent: "flex-end",
paddingHorizontal: 20,
paddingBottom: 24,
},
backButton: {
width: 44,
height: 44,
borderRadius: 22,
alignItems: "center",
justifyContent: "center",
backgroundColor: "rgba(255, 255, 255, 0.05)",
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.06)",
marginBottom: 28,
},
title: {
marginBottom: 8,
textAlign: "center",
sheet: {
backgroundColor: "#14161c",
borderRadius: 28,
paddingHorizontal: 24,
paddingVertical: 28,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.08)",
shadowColor: "#000",
shadowOpacity: 0.25,
shadowRadius: 18,
shadowOffset: { width: 0, height: -8 },
elevation: 24,
},
subtitle: {
marginBottom: 32,
textAlign: "center",
opacity: 0.7,
sheetHeader: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 28,
},
sheetTitleGroup: {
flexDirection: "row",
alignItems: "center",
},
iconBadge: {
width: 40,
height: 40,
borderRadius: 16,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#f6474d",
marginRight: 12,
},
sheetTitle: {
color: "#f5f6f8",
fontSize: 22,
fontWeight: "700",
letterSpacing: 0.2,
},
registerLabel: {
color: "#b7bac2",
fontSize: 14,
fontWeight: "600",
letterSpacing: 0.3,
},
disabledText: {
opacity: 0.5,
},
form: {
gap: 16,
marginTop: 4,
},
inputWrapper: {
position: "relative",
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 18,
backgroundColor: "#1a1d23",
borderRadius: 18,
borderWidth: 1,
borderColor: "rgba(255, 255, 255, 0.05)",
marginBottom: 18,
},
input: {
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 8,
padding: 12,
flex: 1,
paddingVertical: 16,
fontSize: 16,
backgroundColor: "#fff",
color: "#f5f6f8",
letterSpacing: 0.3,
},
button: {
backgroundColor: "#007AFF",
padding: 16,
borderRadius: 8,
visibilityToggle: {
marginLeft: 12,
},
loginButton: {
height: 56,
borderRadius: 18,
alignItems: "center",
marginTop: 8,
justifyContent: "center",
backgroundColor: "#d7ff1f",
shadowColor: "#d7ff1f",
shadowOpacity: 0.35,
shadowRadius: 16,
shadowOffset: { width: 0, height: 10 },
elevation: 10,
marginTop: 4,
},
buttonDisabled: {
opacity: 0.6,
loginButtonDisabled: {
opacity: 0.7,
},
buttonText: {
color: "#fff",
fontSize: 16,
loginButtonText: {
color: "#10120d",
fontSize: 17,
fontWeight: "700",
letterSpacing: 0.5,
},
termsRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
marginTop: 18,
},
termsText: {
color: "#a7abb5",
fontSize: 13,
lineHeight: 18,
marginLeft: 8,
},
linkText: {
color: "#f5f6f8",
fontWeight: "600",
},
registerLink: {
alignItems: "center",
marginTop: 8,
},
registerText: {
color: "#007AFF",
fontSize: 14,
},
});

View File

@@ -1,112 +1,122 @@
import { Image } from 'expo-image';
import { Platform, StyleSheet } from 'react-native';
import { useMemo, useState } from 'react';
import { ScrollView, StyleSheet, View } from 'react-native';
import { Collapsible } from '@/components/ui/collapsible';
import { ExternalLink } from '@/components/external-link';
import ParallaxScrollView from '@/components/parallax-scroll-view';
import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Fonts } from '@/constants/theme';
import {
CategoryTabs,
CommunityGrid,
FeatureCarousel,
Header,
PageLayout,
SectionHeader,
StatusBarSpacer,
type CommunityItem,
type FeatureItem,
} from '@/components/bestai';
const categories = [
{ id: 'visual-effects', label: 'Visual Effects' },
{ id: 'higgsfield-soul', label: 'Higgsfield Soul' },
{ id: 'higgsfield-apps', label: 'Higgsfield Apps' },
{ id: 'king', label: 'King' },
];
const baseFeatureItems: FeatureItem[] = [
{
id: 'sketch-video',
title: 'UNLIMITED SKETCH TO VIDEO',
subtitle: 'Bring your imagination to life with Sora 2',
image: 'https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=800&q=80',
},
{
id: 'portrait-video',
title: 'UNLIMITED PORTRAIT TO VIDEO',
subtitle: 'Transform portraits into motion-driven stories',
image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
},
{
id: 'water-simulation',
title: 'FLUID SIMULATION PACK',
subtitle: 'Realistic water physics for cinematic scenes',
image: 'https://images.unsplash.com/photo-1505744386214-51dba16a26fc?auto=format&fit=crop&w=800&q=80',
},
];
const baseCommunityItems: CommunityItem[] = [
{
id: 'community-1',
chip: '64/10',
title: 'OBJECTS AROUND',
image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
actionLabel: 'Generate',
},
{
id: 'community-2',
chip: '64/10',
title: 'OBJECTS AROUND',
image: 'https://images.unsplash.com/photo-1544723795-3fb6469f5b39?auto=format&fit=crop&w=800&q=80',
actionLabel: 'Generate',
},
{
id: 'community-3',
chip: '64/10',
title: 'OBJECTS AROUND',
image: 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=800&q=80',
actionLabel: 'Generate',
},
{
id: 'community-4',
chip: '64/10',
title: 'OBJECTS AROUND',
image: 'https://images.unsplash.com/photo-1531259683007-016a7b628fc4?auto=format&fit=crop&w=800&q=80',
actionLabel: 'Generate',
},
];
export default function ExploreScreen() {
const [activeCategory, setActiveCategory] = useState(categories[0]?.id ?? 'visual-effects');
const featureItems = useMemo(() => {
return baseFeatureItems.map((item, index) => ({
...item,
id: `${activeCategory}-${index}`,
}));
}, [activeCategory]);
const communityItems = useMemo(() => {
return baseCommunityItems.map((item, index) => ({
...item,
id: `${activeCategory}-${index}`,
}));
}, [activeCategory]);
export default function TabTwoScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
headerImage={
<IconSymbol
size={310}
color="#808080"
name="chevron.left.forwardslash.chevron.right"
style={styles.headerImage}
/>
}>
<ThemedView style={styles.titleContainer}>
<ThemedText
type="title"
style={{
fontFamily: Fonts.rounded,
}}>
Explore 222
</ThemedText>
</ThemedView>
<ThemedText>This app includes example code to help you get started.</ThemedText>
<Collapsible title="File-based routing">
<ThemedText>
This app has two screens:{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
</ThemedText>
<ThemedText>
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
sets up the tab navigator.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/router/introduction">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Android, iOS, and web support">
<ThemedText>
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
</ThemedText>
</Collapsible>
<Collapsible title="Images">
<ThemedText>
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
different screen densities
</ThemedText>
<Image
source={require('@/assets/images/react-logo.png')}
style={{ width: 100, height: 100, alignSelf: 'center' }}
/>
<ExternalLink href="https://reactnative.dev/docs/images">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Light and dark mode components">
<ThemedText>
This template has light and dark mode support. The{' '}
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
what the user&apos;s current color scheme is, and so you can adjust UI colors accordingly.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Animations">
<ThemedText>
This template includes an example of an animated component. The{' '}
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
the powerful{' '}
<ThemedText type="defaultSemiBold" style={{ fontFamily: Fonts.mono }}>
react-native-reanimated
</ThemedText>{' '}
library to create a waving hand animation.
</ThemedText>
{Platform.select({
ios: (
<ThemedText>
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
component provides a parallax effect for the header image.
</ThemedText>
),
})}
</Collapsible>
</ParallaxScrollView>
<PageLayout backgroundColor="#050505">
<ScrollView
style={styles.scroll}
contentContainerStyle={styles.contentContainer}
showsVerticalScrollIndicator={false}
>
<StatusBarSpacer />
<Header />
<CategoryTabs categories={categories} activeId={activeCategory} onChange={setActiveCategory} />
<FeatureCarousel items={featureItems} />
<SectionHeader title="WAN 2.5 COMMUNITY" />
<CommunityGrid items={communityItems} />
<View style={styles.bottomSpacer} />
</ScrollView>
</PageLayout>
);
}
const styles = StyleSheet.create({
headerImage: {
color: '#808080',
bottom: -90,
left: -35,
position: 'absolute',
scroll: {
flex: 1,
},
titleContainer: {
flexDirection: 'row',
gap: 8,
contentContainer: {
paddingTop: 16,
paddingBottom: 48,
},
bottomSpacer: {
height: 32,
},
});

File diff suppressed because it is too large Load Diff

287
app/points-details.tsx Normal file
View File

@@ -0,0 +1,287 @@
import Ionicons from '@expo/vector-icons/Ionicons';
import { useRouter } from 'expo-router';
import React, { useMemo, useState } from 'react';
import {
FlatList,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
type LedgerKind = 'obtained' | 'consumed';
type LedgerEntry = {
id: string;
title: string;
happenedAt: string;
amount: number;
kind: LedgerKind;
};
type FilterKey = 'all' | LedgerKind;
const screenPalette = {
background: '#080808',
surface: '#111111',
surfaceActive: '#1C1C1C',
divider: '#1A1A1A',
primaryText: '#F5F5F5',
secondaryText: '#6C6C6C',
accent: '#FEB840',
accentHalo: 'rgba(254, 184, 64, 0.18)',
negative: '#B3B3B3',
};
const FILTERS: { key: FilterKey; label: string }[] = [
{ key: 'all', label: 'All records' },
{ key: 'consumed', label: 'Consumed' },
{ key: 'obtained', label: 'Obtained' },
];
const LEDGER: LedgerEntry[] = [
{
id: 'obtained-1',
title: 'Income Name',
happenedAt: '2025-10-22 11:11',
amount: 60,
kind: 'obtained',
},
{
id: 'consumed-1',
title: 'Expenditure Name',
happenedAt: '2025-10-22 11:11',
amount: -60,
kind: 'consumed',
},
{
id: 'consumed-2',
title: 'Expenditure Name',
happenedAt: '2025-10-22 11:11',
amount: -60,
kind: 'consumed',
},
{
id: 'obtained-2',
title: 'Income Name',
happenedAt: '2025-10-22 11:11',
amount: 60,
kind: 'obtained',
},
{
id: 'obtained-3',
title: 'Income Name',
happenedAt: '2025-10-22 11:11',
amount: 60,
kind: 'obtained',
},
{
id: 'obtained-4',
title: 'Income Name',
happenedAt: '2025-10-22 11:11',
amount: 60,
kind: 'obtained',
},
{
id: 'consumed-3',
title: 'Expenditure Name',
happenedAt: '2025-10-22 11:11',
amount: -60,
kind: 'consumed',
},
];
export default function PointsDetailsScreen() {
const insets = useSafeAreaInsets();
const router = useRouter();
const [activeFilter, setActiveFilter] = useState<FilterKey>('all');
const filteredEntries = useMemo(() => {
if (activeFilter === 'all') {
return LEDGER;
}
return LEDGER.filter(entry => entry.kind === activeFilter);
}, [activeFilter]);
return (
<View style={[styles.screen, { paddingTop: insets.top + 12 }]}>
<StatusBar barStyle="light-content" />
<View style={styles.headerRow}>
<TouchableOpacity
accessibilityRole="button"
onPress={() => router.back()}
style={styles.iconButton}
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
>
<Ionicons name="chevron-back" size={24} color={screenPalette.primaryText} />
</TouchableOpacity>
<Text style={styles.headerTitle}>Points Details</Text>
<View style={styles.iconButton} />
</View>
<View style={styles.balanceCluster}>
<View style={styles.energyOrb}>
<Ionicons name="flash" size={20} color={screenPalette.accent} />
</View>
<Text style={styles.balanceValue}>60</Text>
</View>
<View style={styles.segmentRail}>
{FILTERS.map(filter => {
const isActive = filter.key === activeFilter;
return (
<TouchableOpacity
key={filter.key}
style={[styles.segmentChip, isActive && styles.segmentChipActive]}
onPress={() => setActiveFilter(filter.key)}
accessibilityRole="button"
>
<Text style={[styles.segmentLabel, isActive && styles.segmentLabelActive]}>
{filter.label}
</Text>
</TouchableOpacity>
);
})}
</View>
<FlatList
data={filteredEntries}
keyExtractor={item => item.id}
style={styles.list}
contentContainerStyle={{ paddingBottom: Math.max(insets.bottom + 24, 64) }}
showsVerticalScrollIndicator={false}
ItemSeparatorComponent={() => <View style={styles.listDivider} />}
renderItem={({ item }) => {
const amount = Math.abs(item.amount);
const isPositive = item.amount > 0;
return (
<View style={styles.recordRow}>
<View>
<Text style={styles.recordTitle}>{item.title}</Text>
<Text style={styles.recordTimestamp}>{item.happenedAt}</Text>
</View>
<Text
style={[
styles.recordValue,
isPositive ? styles.valuePositive : styles.valueNegative,
]}
>
{isPositive ? `+ ${amount}` : `- ${amount}`}
</Text>
</View>
);
}}
/>
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: screenPalette.background,
paddingHorizontal: 20,
},
headerRow: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
iconButton: {
width: 44,
height: 44,
borderRadius: 22,
alignItems: 'center',
justifyContent: 'center',
},
headerTitle: {
fontSize: 18,
fontWeight: '600',
color: screenPalette.primaryText,
letterSpacing: 0.3,
},
balanceCluster: {
alignItems: 'center',
marginTop: 28,
marginBottom: 32,
gap: 12,
},
energyOrb: {
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: screenPalette.accentHalo,
alignItems: 'center',
justifyContent: 'center',
},
balanceValue: {
fontSize: 44,
fontWeight: '700',
color: screenPalette.primaryText,
letterSpacing: 1,
},
segmentRail: {
flexDirection: 'row',
backgroundColor: screenPalette.surface,
padding: 6,
borderRadius: 28,
},
segmentChip: {
flex: 1,
borderRadius: 22,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 10,
},
segmentChipActive: {
backgroundColor: screenPalette.surfaceActive,
},
segmentLabel: {
fontSize: 14,
color: screenPalette.secondaryText,
fontWeight: '500',
},
segmentLabelActive: {
color: screenPalette.primaryText,
},
list: {
flex: 1,
marginTop: 32,
},
listDivider: {
height: 1,
backgroundColor: screenPalette.divider,
marginVertical: 6,
},
recordRow: {
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
paddingVertical: 10,
},
recordTitle: {
fontSize: 16,
fontWeight: '600',
color: screenPalette.primaryText,
},
recordTimestamp: {
marginTop: 4,
fontSize: 13,
color: screenPalette.secondaryText,
},
recordValue: {
fontSize: 16,
fontWeight: '600',
},
valuePositive: {
color: screenPalette.accent,
},
valueNegative: {
color: screenPalette.negative,
},
});

View File

@@ -1,11 +1,9 @@
import React from 'react';
import { StyleSheet, ScrollView } from 'react-native';
import { router } from 'expo-router';
import { ThemedView } from '@/components/themed-view';
import { ThemedText } from '@/components/themed-text';
import { useThemeColor } from '@/hooks/use-theme-color';
import { IconSymbol } from '@/components/ui/icon-symbol';
export default function RechargeScreen() {
const backgroundColor = useThemeColor({}, 'background');
@@ -30,4 +28,4 @@ const styles = StyleSheet.create({
alignItems: 'center',
padding: 20,
},
});
});

View File

@@ -65,13 +65,10 @@ export default function TemplateRunScreen() {
const [formSchema, setFormSchema] = useState<RunFormSchema>({ fields: [] });
const {
state: runState,
progress,
result,
error,
isLoading,
isCompleted,
isError,
executeTemplate,
cancelRun,
reset,
@@ -85,13 +82,7 @@ export default function TemplateRunScreen() {
},
});
const {
formData,
errors,
updateField,
validateForm,
isValid,
} = useTemplateFormData();
const { formData, errors, updateField } = useTemplateFormData();
// 获取模板信息
useEffect(() => {
@@ -235,42 +226,56 @@ export default function TemplateRunScreen() {
}
const handleConfirm = async () => {
// 转换表单数据为 API 期望的格式
const transformedData = transformFormData(formData);
console.log('转换后的数据:', transformedData);
// 这个我后端取 获取月
const list = await subscription.list()
const listData = list.data;
if (listData && listData.length > 0) {
const item = listData[0]
const subscriptionId = item?.stripeSubscriptionId!;
const summary = await subscription.credit.summary({
subscriptionId: subscriptionId,
filter: {
type: `applicability_scope`,
applicability_scope: {
price_type: `metered`
}
}
});
const identify = await subscription.meterEvent({
event_name: `token_usage`,
payload: {
value: `100`
}
})
// error
const orid = identify.data?.identifier
if (orid) {
try {
setCurrentStep('progress');
executeTemplate(template!.id, transformedData);
} catch (e) {
// 退款orid
}
try {
const transformedData = transformFormData(formData);
console.log('转换后的数据:', transformedData);
const list = await subscription.list();
const listData = list.data ?? [];
if (listData.length === 0) {
Alert.alert('未检测到订阅', '请先完成订阅后再尝试生成内容。');
return;
}
} else {
// 跳转到订阅
const subscriptionId = listData[0]?.stripeSubscriptionId;
if (!subscriptionId) {
Alert.alert('订阅信息缺失', '当前订阅缺少 Stripe 订阅标识,无法记录用量。');
return;
}
await subscription.credit.summary({
subscriptionId,
filter: {
type: 'applicability_scope',
applicability_scope: {
price_type: 'metered',
},
},
});
const identify = await subscription.meterEvent({
event_name: 'token_usage',
payload: {
value: '100',
},
});
const identifier = identify.data?.identifier;
if (!identifier) {
Alert.alert('用量记录失败', '无法生成用量凭证,请稍后重试。');
return;
}
try {
setCurrentStep('progress');
await executeTemplate(template!.id, transformedData);
} catch (error) {
console.error('执行模板失败:', error);
Alert.alert('执行失败', '运行模板时出现异常,请稍后重试。');
}
} catch (error) {
console.error('提交表单失败:', error);
Alert.alert('提交失败', '提交生成请求时出现问题,请稍后重试。');
}
};
@@ -542,4 +547,4 @@ const styles = StyleSheet.create({
fontWeight: '600',
color: '#fff',
},
});
});