import { COMPUTER_UPDATE_STAGES, type ComputerUpdate } from "@rakazo/contracts"; import { computerUpdateNeedsAttention, computerUpdateStages } from "@rakazo/core"; import { usePathname } from "expo-router"; import { useEffect, useState, useSyncExternalStore } from "react"; import { ActivityIndicator, Alert, Modal, Pressable, StyleSheet, Text, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { computerUpdates } from "../lib/computer-updates"; import { useI18n } from "../lib/i18n"; import { useMobileTokens } from "../lib/native"; export function ComputerUpdateProgress() { const { t } = useI18n(); const tokens = useMobileTokens(); const insets = useSafeAreaInsets(); const pathname = usePathname(); const { updates, openId } = useSyncExternalStore( computerUpdates.subscribe, computerUpdates.getSnapshot, ); const [error, setError] = useState(false); const [busy, setBusy] = useState(false); const authenticated = !["/sign-in"].includes(pathname); useEffect(() => (authenticated ? computerUpdates.watch() : undefined), [authenticated]); const labels = [ t("Getting ready"), t("Saving your workspace"), t("Recreating the computer"), t("Restoring your workspace"), t("Reconnecting"), ]; const title = (update: ComputerUpdate) => computerUpdateNeedsAttention(update) ? update.action === "recover" ? t("Recovery failed") : t("Update failed") : update.action === "recover" ? update.mode === "team" ? t("Recovering Team Computer") : t("Recovering {name}’s Computer", { name: update.name }) : update.mode === "team" ? t("Updating Team Computer") : t("Updating {name}’s Computer", { name: update.name }); const selected = updates.find((item) => item.id === openId); if (!authenticated) return null; return ( <> {updates.map((update) => ( { setError(false); computerUpdates.open(update.id); }} style={[styles.pill, { backgroundColor: tokens.card, borderColor: tokens.border }]} > {!computerUpdateNeedsAttention(update) ? ( ) : null} {title(update)} {labels[COMPUTER_UPDATE_STAGES.indexOf(update.stage)]} ))} computerUpdates.open(null)} > {selected ? ( {title(selected)} {computerUpdateNeedsAttention(selected) ? ( {selected.status === "interrupted" ? t("Recovery is unavailable until the previous operation has stopped.") : t("Recovery restores the last saved workspace. Unsaved work may be lost.")} ) : ( computerUpdateStages(selected.action).map((stage, index) => { const current = computerUpdateStages(selected.action).indexOf(selected.stage); return ( {index === current ? ( ) : ( {index < current ? "✓" : "○"} )} {labels[COMPUTER_UPDATE_STAGES.indexOf(stage)]} ); }) )} {error ? ( {t("Could not complete action")} ) : null} {selected.status === "interrupted" && selected.canReleaseReservation ? ( Alert.alert( t("Release interrupted computer?"), t("Make sure nothing is still running on this computer."), [ { text: t("Cancel"), style: "cancel" }, { text: t("Nothing is still running"), onPress: () => { setBusy(true); setError(false); void computerUpdates .releaseInterrupted(selected.id) .catch(() => setError(true)) .finally(() => setBusy(false)); }, }, ], ) } > {t("Release computer")} ) : null} {selected.status === "failed" ? ( { setBusy(true); setError(false); void computerUpdates .start(selected.botId, "recover") .catch(() => setError(true)) .finally(() => setBusy(false)); }} style={styles.button} > {t("Recover computer")} ) : null} { if (selected.status === "failed") void computerUpdates.dismiss(selected.id).catch(() => setError(true)); else computerUpdates.open(null); }} > {selected.status === "failed" ? t("Dismiss") : t("Continue in Background")} ) : null} ); } const styles = StyleSheet.create({ pills: { position: "absolute", left: 16, right: 16, zIndex: 100, alignItems: "center", gap: 8 }, pill: { flexDirection: "row", alignItems: "center", gap: 12, borderWidth: 1, borderRadius: 14, padding: 12, }, secondary: { fontSize: 12, textAlign: "center", marginTop: 3 }, sheet: { flex: 1, padding: 24, gap: 24 }, title: { fontSize: 22, fontWeight: "600" }, step: { flexDirection: "row", alignItems: "center", gap: 16 }, button: { minHeight: 44, justifyContent: "center", alignItems: "center" }, });