Files
codexbar-mobile/app/onboarding/codex.tsx
T
space d5b0f9c833
CodexBar Mobile Build / build-android (push) Successful in 31m31s
CodexBar Mobile Build / release (push) Failing after 23s
feat: production-ready UI with onboarding wizard and dashboard
- Onboarding flow (welcome -> Codex setup -> Claude setup -> done) with
  dark-themed screens and step indicators; completion stored in SecureStore
- Smart root redirect checks onboarding state on launch
- Dashboard tab shows both services at a glance via ServiceStatusCard;
  useFocusEffect + reload() keeps it fresh when credentials change in tabs
- Settings tab manages credentials and exposes re-run setup wizard
- Polished Codex and Claude detail screens with service headers and empty states
- 4-tab layout (Home, Codex, Claude, Settings) with dark-mode tab bar
- Both hooks gain reload() for cross-tab credential refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 17:52:06 +02:00

155 lines
5.9 KiB
TypeScript

import { useState, useCallback, useEffect } from "react";
import { View, Text, TouchableOpacity, ActivityIndicator } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { router } from "expo-router";
import { MaterialIcons } from "@expo/vector-icons";
import { pickAndReadCodexAuth } from "@/lib/fileReader";
import { saveCodexAuth, loadCodexAuth } from "@/lib/storage";
import type { CodexAuth } from "@/types/codex";
export default function OnboardingCodexScreen() {
const [auth, setAuth] = useState<CodexAuth | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
loadCodexAuth().then((stored) => {
if (stored) setAuth(stored);
});
}, []);
const importFile = useCallback(async () => {
setLoading(true);
setError(null);
try {
const parsed = await pickAndReadCodexAuth();
await saveCodexAuth(parsed);
setAuth(parsed);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "UNKNOWN_ERROR";
if (msg !== "PICKER_CANCELLED") setError(msg);
} finally {
setLoading(false);
}
}, []);
return (
<SafeAreaView className="flex-1 bg-neutral-950">
<View className="flex-1 px-8 justify-between py-8">
{/* Top: back + step indicator */}
<View className="flex-row items-center justify-between">
<TouchableOpacity
onPress={() => router.back()}
className="w-9 h-9 rounded-xl bg-neutral-900 items-center justify-center"
>
<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" }}>
<Text className="text-white text-xs font-bold">1</Text>
</View>
<View className="w-12 h-0.5 bg-neutral-800" />
<View className="w-6 h-6 rounded-full bg-neutral-800 items-center justify-center">
<Text className="text-neutral-500 text-xs font-bold">2</Text>
</View>
</View>
<View className="w-9" />
</View>
{/* Content */}
<View>
<View
className="w-16 h-16 rounded-2xl items-center justify-center mb-6"
style={{ backgroundColor: "#10a37f22" }}
>
<MaterialIcons name="auto-awesome" size={32} color="#10a37f" />
</View>
<Text className="text-3xl font-bold text-white mb-3">
Connect Codex CLI
</Text>
<Text className="text-neutral-400 text-base leading-relaxed mb-8">
Import your{" "}
<Text className="text-neutral-200 font-mono text-sm">
~/.codex/auth.json
</Text>{" "}
file to track your rate limits and credits.
</Text>
{/* Status / action */}
{auth ? (
<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" }}
>
<MaterialIcons name="check-circle" size={22} color="#10a37f" />
</View>
<View className="flex-1">
<Text className="text-white font-semibold">Connected</Text>
{auth.accountId && (
<Text className="text-neutral-500 text-sm font-mono">
{auth.accountId.slice(0, 12)}
</Text>
)}
</View>
<TouchableOpacity onPress={importFile}>
<Text className="text-neutral-400 text-xs">Re-import</Text>
</TouchableOpacity>
</View>
) : (
<View>
{error && (
<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>
</View>
)}
<TouchableOpacity
onPress={importFile}
disabled={loading}
className="rounded-2xl py-4 items-center mb-3"
style={{ backgroundColor: loading ? "#1a1a1a" : "#10a37f" }}
>
{loading ? (
<ActivityIndicator color="#10a37f" />
) : (
<View className="flex-row items-center gap-x-2">
<MaterialIcons name="upload-file" size={18} color="white" />
<Text className="text-white font-bold text-base">
Import auth.json
</Text>
</View>
)}
</TouchableOpacity>
<Text className="text-neutral-600 text-xs text-center">
Located at ~/.codex/auth.json on your Mac
</Text>
</View>
)}
</View>
{/* Bottom actions */}
<View className="gap-y-3">
<TouchableOpacity
onPress={() => router.push("/onboarding/claude")}
className="rounded-2xl py-4 items-center"
style={{ backgroundColor: auth ? "#10a37f" : "#1a1a1a" }}
>
<Text
className="font-bold text-base"
style={{ color: auth ? "white" : "#a3a3a3" }}
>
Continue
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={() => router.push("/onboarding/claude")}
className="py-3 items-center"
>
<Text className="text-neutral-600 text-sm">Skip for now</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>
);
}