by codex: feat. General Improvements
CodexBar Mobile Build / build-android (push) Successful in 21m9s
CodexBar Mobile Build / release (push) Successful in 11s

This commit is contained in:
2026-06-25 14:27:58 +02:00
parent 9dd17cf565
commit 2c31be11c4
33 changed files with 1217 additions and 296 deletions
+2 -1
View File
@@ -2,6 +2,7 @@ import { Tabs } from "expo-router";
import { useColorScheme } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { MaterialIcons } from "@expo/vector-icons";
import { COLORS } from "@/lib/constants";
export default function TabLayout() {
const colorScheme = useColorScheme();
@@ -12,7 +13,7 @@ export default function TabLayout() {
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: "#10a37f",
tabBarActiveTintColor: COLORS.codex,
tabBarInactiveTintColor: isDark ? "#52525b" : "#a1a1aa",
tabBarStyle: {
backgroundColor: isDark ? "#09090b" : "#ffffff",
+34 -12
View File
@@ -14,8 +14,11 @@ import { MaterialIcons } from "@expo/vector-icons";
import { useClaudeUsage } from "@/hooks/useClaudeUsage";
import { UsageStat } from "@/components/UsageStat";
import { ErrorMessage } from "@/components/ErrorMessage";
import { LastUpdated } from "@/components/LastUpdated";
import { ScreenErrorBoundary } from "@/components/ScreenErrorBoundary";
import { COLORS } from "@/lib/constants";
export default function ClaudeTab() {
function ClaudeTabContent() {
const {
auth,
pendingKey,
@@ -23,15 +26,16 @@ export default function ClaudeTab() {
pendingOrg,
setPendingOrg,
usage,
lastFetchedAt,
status,
error,
keyValidationError,
canSaveKey,
saveKey,
refresh,
clearKey,
} = useClaudeUsage();
const canSave = pendingKey.trim().startsWith("sk-ant-");
return (
<SafeAreaView
className="flex-1 bg-neutral-50 dark:bg-neutral-950"
@@ -49,7 +53,7 @@ export default function ClaudeTab() {
<RefreshControl
refreshing={status === "loading"}
onRefresh={refresh}
tintColor="#d97706"
tintColor={COLORS.claude}
/>
}
>
@@ -57,9 +61,9 @@ export default function ClaudeTab() {
<View className="flex-row items-center gap-x-3 py-5">
<View
className="w-9 h-9 rounded-xl items-center justify-center"
style={{ backgroundColor: "#d9770622" }}
style={{ backgroundColor: `${COLORS.claude}22` }}
>
<MaterialIcons name="psychology" size={18} color="#d97706" />
<MaterialIcons name="psychology" size={18} color={COLORS.claude} />
</View>
<View>
<Text className="text-xl font-bold text-neutral-900 dark:text-white tracking-tight">
@@ -111,6 +115,11 @@ export default function ClaudeTab() {
secureTextEntry
className="border border-neutral-200 dark:border-neutral-700 rounded-xl px-3 py-3 text-sm text-neutral-900 dark:text-white bg-white dark:bg-neutral-800 mb-2"
/>
{keyValidationError && (
<Text selectable className="mb-2 text-xs text-red-500">
{keyValidationError}
</Text>
)}
<TextInput
value={pendingOrg}
onChangeText={setPendingOrg}
@@ -122,13 +131,15 @@ export default function ClaudeTab() {
/>
<TouchableOpacity
onPress={saveKey}
disabled={!canSave}
disabled={!canSaveKey}
className="py-3.5 px-4 rounded-xl items-center"
style={{ backgroundColor: canSave ? "#d97706" : "#e5e5e5" }}
style={{
backgroundColor: canSaveKey ? COLORS.claude : COLORS.disabled,
}}
>
<Text
className="font-semibold text-sm"
style={{ color: canSave ? "white" : "#a3a3a3" }}
style={{ color: canSaveKey ? "white" : COLORS.muted }}
>
Save Session Key
</Text>
@@ -162,11 +173,11 @@ export default function ClaudeTab() {
</Text>
</View>
)}
{status === "loading" && (
<ActivityIndicator color="#d97706" style={{ marginVertical: 24 }} />
{status === "loading" && !usage && (
<ActivityIndicator color={COLORS.claude} style={{ marginVertical: 24 }} />
)}
{status === "error" && error && <ErrorMessage message={error} />}
{status === "success" && usage && (
{usage && (
<View>
<UsageStat
label="5-hour window"
@@ -198,6 +209,9 @@ export default function ClaudeTab() {
)}
</View>
)}
<View className="mt-4 border-t border-neutral-100 pt-3 dark:border-neutral-800">
<LastUpdated timestamp={lastFetchedAt} />
</View>
</View>
)}
</View>
@@ -206,3 +220,11 @@ export default function ClaudeTab() {
</SafeAreaView>
);
}
export default function ClaudeTab() {
return (
<ScreenErrorBoundary screenName="Claude">
<ClaudeTabContent />
</ScreenErrorBoundary>
);
}
+33 -10
View File
@@ -11,10 +11,22 @@ import { MaterialIcons } from "@expo/vector-icons";
import { useCodexUsage } from "@/hooks/useCodexUsage";
import { UsageStat } from "@/components/UsageStat";
import { ErrorMessage } from "@/components/ErrorMessage";
import { LastUpdated } from "@/components/LastUpdated";
import { ScreenErrorBoundary } from "@/components/ScreenErrorBoundary";
import { COLORS } from "@/lib/constants";
import { windowLabel } from "@/lib/timeUtils";
export default function CodexTab() {
const { auth, usage, status, error, importAuthFile, refresh, clearAuth } =
function CodexTabContent() {
const {
auth,
usage,
lastFetchedAt,
status,
error,
importAuthFile,
refresh,
clearAuth,
} =
useCodexUsage();
return (
@@ -29,7 +41,7 @@ export default function CodexTab() {
<RefreshControl
refreshing={status === "loading"}
onRefresh={refresh}
tintColor="#10a37f"
tintColor={COLORS.codex}
/>
}
>
@@ -37,9 +49,9 @@ export default function CodexTab() {
<View className="flex-row items-center gap-x-3 py-5">
<View
className="w-9 h-9 rounded-xl items-center justify-center"
style={{ backgroundColor: "#10a37f22" }}
style={{ backgroundColor: `${COLORS.codex}22` }}
>
<MaterialIcons name="auto-awesome" size={18} color="#10a37f" />
<MaterialIcons name="auto-awesome" size={18} color={COLORS.codex} />
</View>
<View>
<Text className="text-xl font-bold text-neutral-900 dark:text-white tracking-tight">
@@ -84,7 +96,7 @@ export default function CodexTab() {
<TouchableOpacity
onPress={importAuthFile}
className="flex-row items-center justify-center gap-x-2 py-3.5 px-4 rounded-xl"
style={{ backgroundColor: "#10a37f" }}
style={{ backgroundColor: COLORS.codex }}
>
<MaterialIcons name="upload-file" size={16} color="white" />
<Text className="text-white font-semibold text-sm">
@@ -112,11 +124,11 @@ export default function CodexTab() {
</Text>
</View>
)}
{status === "loading" && (
<ActivityIndicator color="#10a37f" style={{ marginVertical: 24 }} />
{status === "loading" && !usage && (
<ActivityIndicator color={COLORS.codex} style={{ marginVertical: 24 }} />
)}
{status === "error" && error && <ErrorMessage message={error} />}
{status === "success" && usage && (
{usage && (
<View>
{/* Plan badge */}
<View className="flex-row items-center mb-5">
@@ -145,7 +157,7 @@ export default function CodexTab() {
</Text>
{usage.credits.unlimited ? (
<View className="flex-row items-center gap-x-2">
<MaterialIcons name="all-inclusive" size={16} color="#10a37f" />
<MaterialIcons name="all-inclusive" size={16} color={COLORS.codex} />
<Text className="text-sm font-semibold text-green-600 dark:text-green-400">
Unlimited
</Text>
@@ -158,6 +170,9 @@ export default function CodexTab() {
<Text className="text-sm text-neutral-400">No credits</Text>
)}
</View>
<View className="mt-4 border-t border-neutral-100 pt-3 dark:border-neutral-800">
<LastUpdated timestamp={lastFetchedAt} />
</View>
</View>
)}
</View>
@@ -165,3 +180,11 @@ export default function CodexTab() {
</SafeAreaView>
);
}
export default function CodexTab() {
return (
<ScreenErrorBoundary screenName="Codex">
<CodexTabContent />
</ScreenErrorBoundary>
);
}
+43 -27
View File
@@ -15,6 +15,9 @@ import { useClaudeUsage } from "@/hooks/useClaudeUsage";
import { onUsageDataLoaded } from "@/lib/notifications";
import { ProgressBar } from "@/components/ProgressBar";
import { ResetCountdown } from "@/components/ResetCountdown";
import { LastUpdated } from "@/components/LastUpdated";
import { ScreenErrorBoundary } from "@/components/ScreenErrorBoundary";
import { COLORS, getUsageColor } from "@/lib/constants";
import { windowLabel } from "@/lib/timeUtils";
function UsageRow({
@@ -28,8 +31,7 @@ function UsageRow({
resetAtSeconds?: number;
resetAtISO?: string;
}) {
const color =
percent >= 90 ? "#ef4444" : percent >= 70 ? "#f59e0b" : "#10a37f";
const color = getUsageColor(percent);
return (
<View className="mb-4">
<View className="flex-row justify-between items-baseline mb-1.5">
@@ -65,33 +67,33 @@ function SetupPrompt({ label }: { label: string }) {
);
}
export default function DashboardTab() {
function DashboardTabContent() {
const codex = useCodexUsage();
const claude = useClaudeUsage();
// Fire daily digest reschedule + threshold check whenever fresh data arrives
useEffect(() => {
if (codex.status === "success" || claude.status === "success") {
if (codex.usage || claude.usage) {
void onUsageDataLoaded(
claude.status === "success" ? claude.usage : null,
codex.status === "success" ? codex.usage : null
claude.usage,
codex.usage
);
}
}, [codex.status, claude.status]);
}, [codex.lastFetchedAt, claude.lastFetchedAt]);
useFocusEffect(
useCallback(() => {
codex.reload();
claude.reload();
}, [])
void codex.reloadCredentials();
void claude.reloadCredentials();
}, [codex.reloadCredentials, claude.reloadCredentials])
);
const isRefreshing =
codex.status === "loading" || claude.status === "loading";
const handleRefresh = () => {
codex.refresh();
claude.refresh();
void codex.refresh();
void claude.refresh();
};
const connectedCount = [codex.auth, claude.auth].filter(Boolean).length;
@@ -108,7 +110,7 @@ export default function DashboardTab() {
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
tintColor="#10a37f"
tintColor={COLORS.codex}
/>
}
>
@@ -135,21 +137,21 @@ export default function DashboardTab() {
onPress={handleRefresh}
className="w-9 h-9 rounded-xl bg-neutral-100 dark:bg-neutral-800 items-center justify-center"
>
<MaterialIcons name="refresh" size={18} color="#10a37f" />
<MaterialIcons name="refresh" size={18} color={COLORS.codex} />
</TouchableOpacity>
</View>
{/* Codex card */}
<View className="rounded-2xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 mb-4 overflow-hidden">
<View style={{ height: 3, backgroundColor: "#10a37f" }} />
<View style={{ height: 3, backgroundColor: COLORS.codex }} />
<View className="p-4">
<View className="flex-row items-center justify-between mb-4">
<View className="flex-row items-center gap-x-2.5">
<View
className="w-8 h-8 rounded-xl items-center justify-center"
style={{ backgroundColor: "#10a37f18" }}
style={{ backgroundColor: `${COLORS.codex}18` }}
>
<MaterialIcons name="auto-awesome" size={16} color="#10a37f" />
<MaterialIcons name="auto-awesome" size={16} color={COLORS.codex} />
</View>
<Text className="text-sm font-semibold text-neutral-900 dark:text-white">
Codex CLI
@@ -175,9 +177,9 @@ export default function DashboardTab() {
{!codex.auth && (
<SetupPrompt label="Import auth.json to see your Codex rate limits." />
)}
{codex.auth && codex.status === "loading" && (
{codex.auth && codex.status === "loading" && !codex.usage && (
<ActivityIndicator
color="#10a37f"
color={COLORS.codex}
style={{ marginVertical: 16 }}
/>
)}
@@ -189,7 +191,7 @@ export default function DashboardTab() {
</Text>
</View>
)}
{codex.status === "success" && codex.usage && (
{codex.usage && (
<View>
<UsageRow
label={`${windowLabel(codex.usage.rate_limit.primary_window.limit_window_seconds)} window`}
@@ -220,13 +222,16 @@ export default function DashboardTab() {
<MaterialIcons
name="all-inclusive"
size={14}
color="#10a37f"
color={COLORS.codex}
/>
<Text className="text-xs text-green-600 dark:text-green-400">
Unlimited credits
</Text>
</View>
)}
<View className="mt-3 border-t border-neutral-100 pt-3 dark:border-neutral-800">
<LastUpdated timestamp={codex.lastFetchedAt} />
</View>
</View>
)}
</View>
@@ -234,15 +239,15 @@ export default function DashboardTab() {
{/* Claude card */}
<View className="rounded-2xl bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 mb-4 overflow-hidden">
<View style={{ height: 3, backgroundColor: "#d97706" }} />
<View style={{ height: 3, backgroundColor: COLORS.claude }} />
<View className="p-4">
<View className="flex-row items-center justify-between mb-4">
<View className="flex-row items-center gap-x-2.5">
<View
className="w-8 h-8 rounded-xl items-center justify-center"
style={{ backgroundColor: "#d9770618" }}
style={{ backgroundColor: `${COLORS.claude}18` }}
>
<MaterialIcons name="psychology" size={16} color="#d97706" />
<MaterialIcons name="psychology" size={16} color={COLORS.claude} />
</View>
<Text className="text-sm font-semibold text-neutral-900 dark:text-white">
Claude.ai
@@ -259,9 +264,9 @@ export default function DashboardTab() {
{!claude.auth && (
<SetupPrompt label="Add your session key to see Claude usage windows." />
)}
{claude.auth && claude.status === "loading" && (
{claude.auth && claude.status === "loading" && !claude.usage && (
<ActivityIndicator
color="#d97706"
color={COLORS.claude}
style={{ marginVertical: 16 }}
/>
)}
@@ -273,7 +278,7 @@ export default function DashboardTab() {
</Text>
</View>
)}
{claude.status === "success" && claude.usage && (
{claude.usage && (
<View>
<UsageRow
label="5-hour window"
@@ -305,6 +310,9 @@ export default function DashboardTab() {
)}
</View>
)}
<View className="mt-3 border-t border-neutral-100 pt-3 dark:border-neutral-800">
<LastUpdated timestamp={claude.lastFetchedAt} />
</View>
</View>
)}
</View>
@@ -313,3 +321,11 @@ export default function DashboardTab() {
</SafeAreaView>
);
}
export default function DashboardTab() {
return (
<ScreenErrorBoundary screenName="Dashboard">
<DashboardTabContent />
</ScreenErrorBoundary>
);
}
+64 -46
View File
@@ -8,7 +8,6 @@ import {
TextInput,
Switch,
KeyboardAvoidingView,
Platform,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { router, useFocusEffect } from "expo-router";
@@ -26,22 +25,16 @@ import {
import { pickAndReadCodexAuth } from "@/lib/fileReader";
import { resetOnboarding } from "@/lib/setupState";
import { useNotificationSettings } from "@/hooks/useNotificationSettings";
import {
isClaudeSessionKeyValid,
validateClaudeSessionKey,
} from "@/lib/claudeCredentials";
import { COLORS } from "@/lib/constants";
import { formatTime, parseTimeInput } from "@/lib/timeUtils";
import { ScreenErrorBoundary } from "@/components/ScreenErrorBoundary";
import type { CodexAuth } from "@/types/codex";
import Constants from "expo-constants";
function parseTimeInput(s: string): { hour: number; minute: number } | null {
const m = s.trim().match(/^(\d{1,2}):(\d{2})$/);
if (!m) return null;
const h = parseInt(m[1], 10);
const min = parseInt(m[2], 10);
if (h < 0 || h > 23 || min < 0 || min > 59) return null;
return { hour: h, minute: min };
}
function formatTime(hour: number, minute: number): string {
return `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
}
function SettingRow({
icon,
label,
@@ -120,7 +113,7 @@ function Divider() {
return <View className="ml-16 mr-0 h-px bg-neutral-100 dark:bg-neutral-800" />;
}
export default function SettingsTab() {
function SettingsTabContent() {
const [codexAuth, setCodexAuth] = useState<CodexAuth | null>(null);
const [claudeKey, setClaudeKey] = useState<string | null>(null);
const [pendingKey, setPendingKey] = useState("");
@@ -129,6 +122,7 @@ export default function SettingsTab() {
const notif = useNotificationSettings();
const [timeInput, setTimeInput] = useState("");
const [timeError, setTimeError] = useState<string | null>(null);
const [thresholdInput, setThresholdInput] = useState("");
// Sync local text inputs when settings load
@@ -173,8 +167,9 @@ export default function SettingsTab() {
const handleSaveClaude = async () => {
const trimmed = pendingKey.trim();
if (!trimmed.startsWith("sk-ant-")) {
setClaudeError("Key must start with sk-ant-");
const validationError = validateClaudeSessionKey(trimmed);
if (validationError) {
setClaudeError(validationError);
return;
}
const orgTrimmed = pendingOrg.trim() || undefined;
@@ -249,9 +244,10 @@ export default function SettingsTab() {
const parsed = parseTimeInput(timeInput);
if (parsed) {
void notif.update({ dailyHour: parsed.hour, dailyMinute: parsed.minute });
setTimeInput(formatTime(parsed.hour, parsed.minute));
setTimeError(null);
} else {
// Reset to last valid value
setTimeInput(formatTime(notif.settings.dailyHour, notif.settings.dailyMinute));
setTimeError("Use HH:MM format, from 00:00 to 23:59.");
}
}, [timeInput, notif]);
@@ -264,7 +260,9 @@ export default function SettingsTab() {
}
}, [thresholdInput, notif]);
const canSaveClaude = pendingKey.trim().startsWith("sk-ant-");
const canSaveClaude = isClaudeSessionKeyValid(pendingKey);
const inlineClaudeError =
claudeError ?? (pendingKey ? validateClaudeSessionKey(pendingKey) : null);
const appVersion = Constants.expoConfig?.version ?? "1.0.0";
return (
@@ -274,7 +272,7 @@ export default function SettingsTab() {
>
<KeyboardAvoidingView
className="flex-1"
behavior={Platform.OS === "ios" ? "padding" : "height"}
behavior={process.env.EXPO_OS === "ios" ? "padding" : "height"}
>
<ScrollView
className="flex-1"
@@ -341,9 +339,9 @@ export default function SettingsTab() {
</>
) : (
<View className="p-4">
{claudeError && (
<Text className="text-xs text-red-500 mb-2">
{claudeError}
{inlineClaudeError && (
<Text selectable className="text-xs text-red-500 mb-2">
{inlineClaudeError}
</Text>
)}
<TextInput
@@ -373,12 +371,14 @@ export default function SettingsTab() {
disabled={!canSaveClaude}
className="py-3 px-4 rounded-xl items-center"
style={{
backgroundColor: canSaveClaude ? "#d97706" : "#e5e5e5",
backgroundColor: canSaveClaude
? COLORS.claude
: COLORS.disabled,
}}
>
<Text
className="font-semibold text-sm"
style={{ color: canSaveClaude ? "white" : "#a3a3a3" }}
style={{ color: canSaveClaude ? "white" : COLORS.muted }}
>
Save Session Key
</Text>
@@ -446,7 +446,7 @@ export default function SettingsTab() {
<Switch
value={notif.settings.dailyEnabled}
onValueChange={(v) => void notif.update({ dailyEnabled: v })}
trackColor={{ false: "#e5e5e5", true: "#10a37f" }}
trackColor={{ false: COLORS.disabled, true: COLORS.codex }}
thumbColor="white"
/>
</View>
@@ -454,25 +454,35 @@ export default function SettingsTab() {
{notif.settings.dailyEnabled && (
<>
<Divider />
<View className="flex-row items-center gap-x-3 py-3 px-4">
<View className="w-8 items-center">
<MaterialIcons name="schedule" size={20} color="#737373" />
<View className="px-4 py-3">
<View className="flex-row items-center gap-x-3">
<View className="w-8 items-center">
<MaterialIcons name="schedule" size={20} color="#737373" />
</View>
<Text className="flex-1 text-sm font-medium text-neutral-800 dark:text-neutral-100">
Time
</Text>
<TextInput
value={timeInput}
onChangeText={(value) => {
setTimeInput(value);
setTimeError(null);
}}
onBlur={handleTimeBlur}
placeholder="09:00"
placeholderTextColor={COLORS.muted}
keyboardType="numbers-and-punctuation"
returnKeyType="done"
maxLength={5}
className="text-sm font-mono text-neutral-600 dark:text-neutral-300 text-right"
style={{ minWidth: 52 }}
/>
</View>
<Text className="flex-1 text-sm font-medium text-neutral-800 dark:text-neutral-100">
Time
</Text>
<TextInput
value={timeInput}
onChangeText={setTimeInput}
onBlur={handleTimeBlur}
placeholder="09:00"
placeholderTextColor="#a3a3a3"
keyboardType="numbers-and-punctuation"
returnKeyType="done"
maxLength={5}
className="text-sm font-mono text-neutral-600 dark:text-neutral-300 text-right"
style={{ minWidth: 52 }}
/>
{timeError && (
<Text selectable className="ml-11 mt-1.5 text-xs text-red-500">
{timeError}
</Text>
)}
</View>
</>
)}
@@ -495,7 +505,7 @@ export default function SettingsTab() {
<Switch
value={notif.settings.thresholdEnabled}
onValueChange={(v) => void notif.update({ thresholdEnabled: v })}
trackColor={{ false: "#e5e5e5", true: "#d97706" }}
trackColor={{ false: COLORS.disabled, true: COLORS.claude }}
thumbColor="white"
/>
</View>
@@ -554,3 +564,11 @@ export default function SettingsTab() {
</SafeAreaView>
);
}
export default function SettingsTab() {
return (
<ScreenErrorBoundary screenName="Settings">
<SettingsTabContent />
</ScreenErrorBoundary>
);
}
+2 -1
View File
@@ -2,6 +2,7 @@ import { useEffect } from "react";
import { View, ActivityIndicator } from "react-native";
import { router } from "expo-router";
import { hasCompletedOnboarding } from "@/lib/setupState";
import { COLORS } from "@/lib/constants";
export default function Index() {
useEffect(() => {
@@ -12,7 +13,7 @@ export default function Index() {
return (
<View className="flex-1 bg-neutral-950 items-center justify-center">
<ActivityIndicator color="#10a37f" size="large" />
<ActivityIndicator color={COLORS.codex} size="large" />
</View>
);
}
+27 -16
View File
@@ -5,7 +5,6 @@ import {
TouchableOpacity,
TextInput,
KeyboardAvoidingView,
Platform,
ScrollView,
} from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
@@ -18,6 +17,11 @@ import {
loadClaudeSessionKey,
loadClaudeLastActiveOrg,
} from "@/lib/storage";
import {
isClaudeSessionKeyValid,
validateClaudeSessionKey,
} from "@/lib/claudeCredentials";
import { COLORS } from "@/lib/constants";
export default function OnboardingClaudeScreen() {
const [savedKey, setSavedKey] = useState<string | null>(null);
@@ -35,12 +39,15 @@ export default function OnboardingClaudeScreen() {
);
}, []);
const canSave = pendingKey.trim().startsWith("sk-ant-");
const canSave = isClaudeSessionKeyValid(pendingKey);
const inlineError =
error ?? (pendingKey ? validateClaudeSessionKey(pendingKey) : null);
const handleSave = useCallback(async () => {
const trimmed = pendingKey.trim();
if (!trimmed.startsWith("sk-ant-")) {
setError("Session key must start with sk-ant-");
const validationError = validateClaudeSessionKey(trimmed);
if (validationError) {
setError(validationError);
return;
}
const orgTrimmed = pendingOrg.trim() || undefined;
@@ -58,7 +65,7 @@ export default function OnboardingClaudeScreen() {
<SafeAreaView className="flex-1 bg-neutral-950">
<KeyboardAvoidingView
className="flex-1"
behavior={Platform.OS === "ios" ? "padding" : "height"}
behavior={process.env.EXPO_OS === "ios" ? "padding" : "height"}
>
<ScrollView
className="flex-1"
@@ -77,14 +84,14 @@ export default function OnboardingClaudeScreen() {
<View className="flex-row items-center gap-x-2">
<View
className="w-6 h-6 rounded-full items-center justify-center"
style={{ backgroundColor: "#10a37f" }}
style={{ backgroundColor: COLORS.codex }}
>
<MaterialIcons name="check" size={14} color="white" />
</View>
<View className="w-12 h-0.5" style={{ backgroundColor: "#d97706" }} />
<View className="w-12 h-0.5" style={{ backgroundColor: COLORS.claude }} />
<View
className="w-6 h-6 rounded-full items-center justify-center"
style={{ backgroundColor: "#d97706" }}
style={{ backgroundColor: COLORS.claude }}
>
<Text className="text-white text-xs font-bold">2</Text>
</View>
@@ -96,9 +103,9 @@ export default function OnboardingClaudeScreen() {
<View>
<View
className="w-16 h-16 rounded-2xl items-center justify-center mb-6"
style={{ backgroundColor: "#d9770622" }}
style={{ backgroundColor: `${COLORS.claude}22` }}
>
<MaterialIcons name="psychology" size={32} color="#d97706" />
<MaterialIcons name="psychology" size={32} color={COLORS.claude} />
</View>
<Text className="text-3xl font-bold text-white mb-3">
Connect Claude.ai
@@ -112,9 +119,9 @@ export default function OnboardingClaudeScreen() {
<View className="flex-row items-center gap-x-3 p-4">
<View
className="w-10 h-10 rounded-xl items-center justify-center"
style={{ backgroundColor: "#d9770622" }}
style={{ backgroundColor: `${COLORS.claude}22` }}
>
<MaterialIcons name="check-circle" size={22} color="#d97706" />
<MaterialIcons name="check-circle" size={22} color={COLORS.claude} />
</View>
<View className="flex-1">
<Text className="text-white font-semibold">Connected</Text>
@@ -143,9 +150,9 @@ export default function OnboardingClaudeScreen() {
</View>
) : (
<View>
{error && (
{inlineError && (
<View className="bg-red-950 border border-red-900 rounded-xl px-4 py-3 mb-4">
<Text className="text-red-400 text-sm">{error}</Text>
<Text selectable className="text-red-400 text-sm">{inlineError}</Text>
</View>
)}
<TextInput
@@ -174,7 +181,9 @@ export default function OnboardingClaudeScreen() {
onPress={handleSave}
disabled={!canSave}
className="rounded-2xl py-4 items-center mb-4"
style={{ backgroundColor: canSave ? "#d97706" : "#1a1a1a" }}
style={{
backgroundColor: canSave ? COLORS.claude : "#1a1a1a",
}}
>
<Text
className="font-bold text-base"
@@ -201,7 +210,9 @@ export default function OnboardingClaudeScreen() {
<TouchableOpacity
onPress={() => router.push("/onboarding/done")}
className="rounded-2xl py-4 items-center"
style={{ backgroundColor: savedKey ? "#d97706" : "#1a1a1a" }}
style={{
backgroundColor: savedKey ? COLORS.claude : "#1a1a1a",
}}
>
<Text
className="font-bold text-base"
+9 -8
View File
@@ -6,6 +6,7 @@ import { MaterialIcons } from "@expo/vector-icons";
import { pickAndReadCodexAuth } from "@/lib/fileReader";
import { saveCodexAuth, loadCodexAuth } from "@/lib/storage";
import type { CodexAuth } from "@/types/codex";
import { COLORS } from "@/lib/constants";
export default function OnboardingCodexScreen() {
const [auth, setAuth] = useState<CodexAuth | null>(null);
@@ -45,7 +46,7 @@ export default function OnboardingCodexScreen() {
<MaterialIcons name="arrow-back" size={18} color="#a3a3a3" />
</TouchableOpacity>
<View className="flex-row items-center gap-x-2">
<View className="w-6 h-6 rounded-full items-center justify-center" style={{ backgroundColor: "#10a37f" }}>
<View className="w-6 h-6 rounded-full items-center justify-center" style={{ backgroundColor: COLORS.codex }}>
<Text className="text-white text-xs font-bold">1</Text>
</View>
<View className="w-12 h-0.5 bg-neutral-800" />
@@ -60,9 +61,9 @@ export default function OnboardingCodexScreen() {
<View>
<View
className="w-16 h-16 rounded-2xl items-center justify-center mb-6"
style={{ backgroundColor: "#10a37f22" }}
style={{ backgroundColor: `${COLORS.codex}22` }}
>
<MaterialIcons name="auto-awesome" size={32} color="#10a37f" />
<MaterialIcons name="auto-awesome" size={32} color={COLORS.codex} />
</View>
<Text className="text-3xl font-bold text-white mb-3">
Connect Codex CLI
@@ -80,9 +81,9 @@ export default function OnboardingCodexScreen() {
<View className="flex-row items-center gap-x-3 bg-neutral-900 rounded-2xl p-4 border border-neutral-800 mb-4">
<View
className="w-10 h-10 rounded-xl items-center justify-center"
style={{ backgroundColor: "#10a37f22" }}
style={{ backgroundColor: `${COLORS.codex}22` }}
>
<MaterialIcons name="check-circle" size={22} color="#10a37f" />
<MaterialIcons name="check-circle" size={22} color={COLORS.codex} />
</View>
<View className="flex-1">
<Text className="text-white font-semibold">Connected</Text>
@@ -107,10 +108,10 @@ export default function OnboardingCodexScreen() {
onPress={importFile}
disabled={loading}
className="rounded-2xl py-4 items-center mb-3"
style={{ backgroundColor: loading ? "#1a1a1a" : "#10a37f" }}
style={{ backgroundColor: loading ? "#1a1a1a" : COLORS.codex }}
>
{loading ? (
<ActivityIndicator color="#10a37f" />
<ActivityIndicator color={COLORS.codex} />
) : (
<View className="flex-row items-center gap-x-2">
<MaterialIcons name="upload-file" size={18} color="white" />
@@ -132,7 +133,7 @@ export default function OnboardingCodexScreen() {
<TouchableOpacity
onPress={() => router.push("/onboarding/claude")}
className="rounded-2xl py-4 items-center"
style={{ backgroundColor: auth ? "#10a37f" : "#1a1a1a" }}
style={{ backgroundColor: auth ? COLORS.codex : "#1a1a1a" }}
>
<Text
className="font-bold text-base"
+12 -7
View File
@@ -5,6 +5,7 @@ import { router } from "expo-router";
import { MaterialIcons } from "@expo/vector-icons";
import { markOnboardingComplete } from "@/lib/setupState";
import { loadCodexAuth, loadClaudeSessionKey } from "@/lib/storage";
import { COLORS } from "@/lib/constants";
export default function OnboardingDoneScreen() {
const [codexConfigured, setCodexConfigured] = useState(false);
@@ -32,12 +33,12 @@ export default function OnboardingDoneScreen() {
{/* Icon */}
<View
className="w-20 h-20 rounded-3xl items-center justify-center mb-8"
style={{ backgroundColor: "#10a37f22" }}
style={{ backgroundColor: `${COLORS.codex}22` }}
>
<MaterialIcons
name={noneConfigured ? "info-outline" : "check-circle"}
size={40}
color={noneConfigured ? "#a3a3a3" : "#10a37f"}
color={noneConfigured ? COLORS.muted : COLORS.codex}
/>
</View>
@@ -57,13 +58,15 @@ export default function OnboardingDoneScreen() {
<View
className="w-10 h-10 rounded-xl items-center justify-center"
style={{
backgroundColor: codexConfigured ? "#10a37f22" : "#27272a",
backgroundColor: codexConfigured
? `${COLORS.codex}22`
: "#27272a",
}}
>
<MaterialIcons
name={codexConfigured ? "check-circle" : "radio-button-unchecked"}
size={22}
color={codexConfigured ? "#10a37f" : "#525252"}
color={codexConfigured ? COLORS.codex : "#525252"}
/>
</View>
<View className="flex-1">
@@ -83,13 +86,15 @@ export default function OnboardingDoneScreen() {
<View
className="w-10 h-10 rounded-xl items-center justify-center"
style={{
backgroundColor: claudeConfigured ? "#d9770622" : "#27272a",
backgroundColor: claudeConfigured
? `${COLORS.claude}22`
: "#27272a",
}}
>
<MaterialIcons
name={claudeConfigured ? "check-circle" : "radio-button-unchecked"}
size={22}
color={claudeConfigured ? "#d97706" : "#525252"}
color={claudeConfigured ? COLORS.claude : "#525252"}
/>
</View>
<View className="flex-1">
@@ -110,7 +115,7 @@ export default function OnboardingDoneScreen() {
<TouchableOpacity
onPress={handleEnterApp}
className="w-full rounded-2xl py-4 items-center"
style={{ backgroundColor: "#10a37f" }}
style={{ backgroundColor: COLORS.codex }}
>
<Text className="text-white font-bold text-base">Open App</Text>
</TouchableOpacity>
+8 -7
View File
@@ -2,6 +2,7 @@ import { View, Text, TouchableOpacity } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { router } from "expo-router";
import { MaterialIcons } from "@expo/vector-icons";
import { COLORS } from "@/lib/constants";
export default function WelcomeScreen() {
return (
@@ -12,13 +13,13 @@ export default function WelcomeScreen() {
<View className="flex-row items-center justify-center mb-8">
<View
className="w-16 h-16 rounded-2xl items-center justify-center"
style={{ backgroundColor: "#10a37f" }}
style={{ backgroundColor: COLORS.codex }}
>
<MaterialIcons name="auto-awesome" size={28} color="white" />
</View>
<View
className="w-16 h-16 rounded-2xl items-center justify-center -ml-5"
style={{ backgroundColor: "#d97706" }}
style={{ backgroundColor: COLORS.claude }}
>
<MaterialIcons name="psychology" size={28} color="white" />
</View>
@@ -36,9 +37,9 @@ export default function WelcomeScreen() {
<View className="flex-row items-center gap-x-3 bg-neutral-900 rounded-2xl p-4 border border-neutral-800">
<View
className="w-10 h-10 rounded-xl items-center justify-center"
style={{ backgroundColor: "#10a37f22" }}
style={{ backgroundColor: `${COLORS.codex}22` }}
>
<MaterialIcons name="auto-awesome" size={20} color="#10a37f" />
<MaterialIcons name="auto-awesome" size={20} color={COLORS.codex} />
</View>
<View className="flex-1">
<Text className="text-white font-semibold mb-0.5">Codex CLI</Text>
@@ -50,9 +51,9 @@ export default function WelcomeScreen() {
<View className="flex-row items-center gap-x-3 bg-neutral-900 rounded-2xl p-4 border border-neutral-800">
<View
className="w-10 h-10 rounded-xl items-center justify-center"
style={{ backgroundColor: "#d9770622" }}
style={{ backgroundColor: `${COLORS.claude}22` }}
>
<MaterialIcons name="psychology" size={20} color="#d97706" />
<MaterialIcons name="psychology" size={20} color={COLORS.claude} />
</View>
<View className="flex-1">
<Text className="text-white font-semibold mb-0.5">Claude.ai</Text>
@@ -68,7 +69,7 @@ export default function WelcomeScreen() {
<TouchableOpacity
onPress={() => router.push("/onboarding/codex")}
className="rounded-2xl py-4 items-center"
style={{ backgroundColor: "#10a37f" }}
style={{ backgroundColor: COLORS.codex }}
>
<Text className="text-white font-bold text-base">Get Started</Text>
</TouchableOpacity>