import { useCallback, useState } from "react"; import { 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 { useSafeAreaInsets } from "react-native-safe-area-context"; import type { HostProfile } from "@/types/host-connection"; import { useHostMutations } from "@/runtime/host-runtime"; 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"; import { buildHostRootRoute } from "@/utils/host-routes"; import { PaseoLogo } from "@/components/icons/paseo-logo"; type WelcomeAction = { key: "scan-qr" | "direct-connection" | "paste-pairing-link"; label: string; testID: string; primary: boolean; icon: typeof QrCode; onPress: () => void; }; const styles = StyleSheet.create((theme) => ({ container: { flexGrow: 1, backgroundColor: theme.colors.surface0, padding: theme.spacing[6], paddingBottom: 0, alignItems: "center", }, content: { width: "100%", flexGrow: 1, justifyContent: "center", alignItems: "center", }, 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.accent, borderColor: theme.colors.accent, }, actionText: { color: theme.colors.foreground, fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.medium, }, actionTextPrimary: { color: theme.colors.accentForeground, }, 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 insets = useSafeAreaInsets(); const router = useRouter(); const { renameHost } = useHostMutations(); 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(buildHostRootRoute(serverId) as any); }, [router] ); const actions: WelcomeAction[] = Platform.OS === "web" ? [ { key: "direct-connection", label: "Direct connection", testID: "welcome-direct-connection", primary: true, icon: Link2, onPress: () => setIsDirectOpen(true), }, { key: "paste-pairing-link", label: "Paste pairing link", testID: "welcome-paste-pairing-link", primary: false, icon: ClipboardPaste, onPress: () => setIsPasteLinkOpen(true), }, ] : [ { key: "scan-qr", label: "Scan QR code", testID: "welcome-scan-qr", primary: true, icon: QrCode, onPress: () => router.push("/pair-scan?source=onboarding"), }, { key: "direct-connection", label: "Direct connection", testID: "welcome-direct-connection", primary: false, icon: Link2, onPress: () => setIsDirectOpen(true), }, { key: "paste-pairing-link", label: "Paste pairing link", testID: "welcome-paste-pairing-link", primary: false, icon: ClipboardPaste, onPress: () => setIsPasteLinkOpen(true), }, ]; return ( Welcome to Paseo Connect to your host to start {actions.map((action) => { const Icon = action.icon; return ( {action.label} ); })} {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 renameHost(pendingNameHost.serverId, label).finally(() => { setPendingNameHost(null); setPendingRedirectServerId(null); finishOnboarding(serverId); }); }} /> ) : null} ); }