From 5701543746ec76352c6c89e2ea7121589457d71d Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 15 Mar 2026 12:52:29 +0700 Subject: [PATCH] refactor: move startup logic to app-support, improve welcome screen UI --- .../src/{app => app-support}/index-startup.ts | 0 packages/app/src/app/_layout.tsx | 4 +- packages/app/src/app/index.tsx | 2 +- .../app/src/components/welcome-screen.tsx | 126 ++++++++++++------ .../components/desktop-updates-section.tsx | 29 ++-- .../src/hooks/use-sidebar-workspaces-list.ts | 3 +- 6 files changed, 108 insertions(+), 56 deletions(-) rename packages/app/src/{app => app-support}/index-startup.ts (100%) diff --git a/packages/app/src/app/index-startup.ts b/packages/app/src/app-support/index-startup.ts similarity index 100% rename from packages/app/src/app/index-startup.ts rename to packages/app/src/app-support/index-startup.ts diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx index 974a34469..bd9dd25d2 100644 --- a/packages/app/src/app/_layout.tsx +++ b/packages/app/src/app/_layout.tsx @@ -61,6 +61,7 @@ import { import { buildNotificationRoute } from "@/utils/notification-routing"; import { buildHostRootRoute, + parseServerIdFromPathname, parseHostAgentRouteFromPathname, parseWorkspaceOpenIntent, } from "@/utils/host-routes"; @@ -469,7 +470,8 @@ function AppWithSidebar({ children }: { children: ReactNode }) { const pathname = usePathname(); const params = useGlobalSearchParams<{ open?: string | string[] }>(); useFaviconStatus(); - const shouldShowAppChrome = pathname !== "/" && pathname !== ""; + const activeServerId = useMemo(() => parseServerIdFromPathname(pathname), [pathname]); + const shouldShowAppChrome = activeServerId !== null; // Parse selectedAgentKey directly from pathname // useLocalSearchParams doesn't update when navigating between same-pattern routes diff --git a/packages/app/src/app/index.tsx b/packages/app/src/app/index.tsx index a3fe70314..45e3d983a 100644 --- a/packages/app/src/app/index.tsx +++ b/packages/app/src/app/index.tsx @@ -10,7 +10,7 @@ import { shouldRedirectToWelcome, shouldWaitOnStartupRace, WELCOME_ROUTE, -} from './index-startup' +} from '@/app-support/index-startup' const STARTUP_TIMEOUT_MS = 30_000 function useAnyHostOnline(serverIds: string[]): string | null { diff --git a/packages/app/src/components/welcome-screen.tsx b/packages/app/src/components/welcome-screen.tsx index 6f59604f5..3936cd33a 100644 --- a/packages/app/src/components/welcome-screen.tsx +++ b/packages/app/src/components/welcome-screen.tsx @@ -1,8 +1,9 @@ import { useCallback, useState } from "react"; -import { Image, Pressable, Text, View, Platform, ScrollView } from "react-native"; +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"; @@ -12,12 +13,23 @@ 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: { @@ -26,11 +38,6 @@ const styles = StyleSheet.create((theme) => ({ justifyContent: "center", alignItems: "center", }, - logo: { - width: 96, - height: 96, - marginBottom: theme.spacing[6], - }, title: { color: theme.colors.foreground, fontSize: theme.fontSize.xl, @@ -61,8 +68,8 @@ const styles = StyleSheet.create((theme) => ({ borderColor: theme.colors.border, }, actionButtonPrimary: { - backgroundColor: theme.colors.palette.blue[500], - borderColor: theme.colors.palette.blue[500], + backgroundColor: theme.colors.accent, + borderColor: theme.colors.accent, }, actionText: { color: theme.colors.foreground, @@ -70,7 +77,7 @@ const styles = StyleSheet.create((theme) => ({ fontWeight: theme.fontWeight.medium, }, actionTextPrimary: { - color: theme.colors.palette.white, + color: theme.colors.accentForeground, }, versionLabel: { color: theme.colors.foregroundMuted, @@ -86,6 +93,7 @@ export interface WelcomeScreenProps { export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) { const { theme } = useUnistyles(); + const insets = useSafeAreaInsets(); const router = useRouter(); const { renameHost } = useHostMutations(); const appVersion = resolveAppVersion(); @@ -111,51 +119,87 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) { [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 - Add a host to start. + Connect to your host to start - setIsDirectOpen(true)} - testID="welcome-direct-connection" - > - - Direct connection - - - setIsPasteLinkOpen(true)} - testID="welcome-paste-pairing-link" - > - - Paste pairing link - - - {Platform.OS !== "web" ? ( + {actions.map((action) => { + const Icon = action.icon; + return ( router.push("/pair-scan?source=onboarding")} - testID="welcome-scan-qr" + key={action.key} + style={[styles.actionButton, action.primary ? styles.actionButtonPrimary : null]} + onPress={action.onPress} + testID={action.testID} > - - Scan QR code + + + {action.label} + - ) : null} + ); + })} {appVersionText} diff --git a/packages/app/src/desktop/components/desktop-updates-section.tsx b/packages/app/src/desktop/components/desktop-updates-section.tsx index 898f1ef89..3876c667c 100644 --- a/packages/app/src/desktop/components/desktop-updates-section.tsx +++ b/packages/app/src/desktop/components/desktop-updates-section.tsx @@ -554,7 +554,7 @@ function PairingOfferDialogContent(input: { void QRCode.toDataURL(pairingOffer.url, { errorCorrectionLevel: 'M', margin: 1, - width: 320, + width: 480, }) .then((dataUrl) => { if (cancelled) { @@ -606,17 +606,19 @@ function PairingOfferDialogContent(input: { {qrDataUrl ? ( - + ) : qrError ? ( QR unavailable: {qrError} ) : ( )} - Pairing link - - {pairingOffer.url} - + + Pairing link + + {pairingOffer.url} + +