import { useCallback, useState } from "react"; import { Image, Pressable, Text, View, Platform, ScrollView } from "react-native"; import { useRouter } from "expo-router"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { QrCode, Link2, ClipboardPaste } from "lucide-react-native"; import type { HostProfile } from "@/contexts/daemon-registry-context"; import { useDaemonRegistry } from "@/contexts/daemon-registry-context"; import { useSessionStore } from "@/stores/session-store"; import { AddHostModal } from "./add-host-modal"; import { PairLinkModal } from "./pair-link-modal"; import { NameHostModal } from "./name-host-modal"; import { resolveAppVersion } from "@/utils/app-version"; import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates"; const styles = StyleSheet.create((theme) => ({ container: { flexGrow: 1, backgroundColor: theme.colors.surface0, padding: theme.spacing[6], alignItems: "center", }, content: { width: "100%", flexGrow: 1, justifyContent: "center", alignItems: "center", }, logo: { width: 96, height: 96, marginBottom: theme.spacing[6], }, title: { color: theme.colors.foreground, fontSize: theme.fontSize.xl, fontWeight: theme.fontWeight.medium, marginBottom: theme.spacing[3], textAlign: "center", }, subtitle: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.base, textAlign: "center", marginBottom: theme.spacing[8], }, actions: { width: "100%", maxWidth: 420, gap: theme.spacing[3], }, actionButton: { flexDirection: "row", alignItems: "center", justifyContent: "center", gap: theme.spacing[3], paddingVertical: theme.spacing[4], borderRadius: theme.borderRadius.xl, backgroundColor: theme.colors.surface2, borderWidth: 1, borderColor: theme.colors.border, }, actionButtonPrimary: { backgroundColor: theme.colors.palette.blue[500], borderColor: theme.colors.palette.blue[500], }, actionText: { color: theme.colors.foreground, fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.medium, }, actionTextPrimary: { color: theme.colors.palette.white, }, versionLabel: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.xs, textAlign: "center", marginTop: theme.spacing[6], }, })); export interface WelcomeScreenProps { onHostAdded?: (profile: HostProfile) => void; } export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) { const { theme } = useUnistyles(); const router = useRouter(); const { updateHost } = useDaemonRegistry(); const appVersion = resolveAppVersion(); const appVersionText = formatVersionWithPrefix(appVersion); const [isDirectOpen, setIsDirectOpen] = useState(false); const [isPasteLinkOpen, setIsPasteLinkOpen] = useState(false); const [pendingNameHost, setPendingNameHost] = useState<{ serverId: string; hostname: string | null } | null>(null); const [pendingRedirectServerId, setPendingRedirectServerId] = useState(null); const pendingNameHostname = useSessionStore( useCallback( (state) => { if (!pendingNameHost) return null; return state.sessions[pendingNameHost.serverId]?.serverInfo?.hostname ?? pendingNameHost.hostname ?? null; }, [pendingNameHost] ) ); const finishOnboarding = useCallback( (serverId: string) => { router.replace(`/h/${encodeURIComponent(serverId)}` as any); }, [router] ); return ( Welcome to Paseo Add a host to start. setIsDirectOpen(true)} testID="welcome-direct-connection" > Direct connection setIsPasteLinkOpen(true)} testID="welcome-paste-pairing-link" > Paste pairing link {Platform.OS !== "web" ? ( router.push("/pair-scan?source=onboarding")} testID="welcome-scan-qr" > Scan QR code ) : null} {appVersionText} setIsDirectOpen(false)} onSaved={({ profile, serverId, hostname, isNewHost }) => { onHostAdded?.(profile); setPendingRedirectServerId(serverId); if (isNewHost) { setPendingNameHost({ serverId, hostname }); return; } finishOnboarding(serverId); }} /> setIsPasteLinkOpen(false)} onSaved={({ profile, serverId, hostname, isNewHost }) => { onHostAdded?.(profile); setPendingRedirectServerId(serverId); if (isNewHost) { setPendingNameHost({ serverId, hostname }); return; } finishOnboarding(serverId); }} /> {pendingNameHost && pendingRedirectServerId ? ( { const serverId = pendingRedirectServerId; setPendingNameHost(null); setPendingRedirectServerId(null); finishOnboarding(serverId); }} onSave={(label) => { const serverId = pendingRedirectServerId; void updateHost(pendingNameHost.serverId, { label }).finally(() => { setPendingNameHost(null); setPendingRedirectServerId(null); finishOnboarding(serverId); }); }} /> ) : null} ); }