import { Plus, LogIn, Clock, Server, ChevronRight, Laptop, Terminal, Monitor, Cast } from 'lucide-react' import { useTranslation } from 'react-i18next' import HostDistroIcon from '../common/HostDistroIcon' import { useEffect, useRef, useState, useCallback, useMemo } from 'react' import { useSessionStore } from '../../stores' import { useTabStore } from '../../stores/tabStore' import { useEntitlementStore } from '../../stores/entitlementStore' import EmptyState from '../common/EmptyState' import { openLocalShellTab, type ShellOption } from '../layout/sidebar/LocalShellMenu' import appIconUrl from '../../assets/opsxshell-app-icon.png' import { shellDisplayFromPath } from '../../utils/shellName' import { fallbackLocalShells, modKeyLabel } from '../../utils/platformShells' import { buildOpenTabPayload, defaultPortForProtocol, sessionProtocol } from '../../utils/sessionProtocol' interface WelcomeViewProps { onNewSession?: () => void onQuickConnect?: () => void onRdpConnect?: () => void onVncConnect?: () => void onImportSessions?: () => void } function Kbd({ children }: { children: string }) { return ( {children} ) } export default function WelcomeView({ onNewSession, onQuickConnect, onRdpConnect, onVncConnect, onImportSessions, }: WelcomeViewProps) { const { t } = useTranslation('sessions') const { addTab } = useTabStore() const requirePro = useEntitlementStore((s) => s.requirePro) const sessions = useSessionStore((s) => s.sessions) const recentSessions = sessions.slice(-8).reverse() const modKey = useMemo(() => modKeyLabel(), []) const [shells, setShells] = useState([]) const [shellMenuOpen, setShellMenuOpen] = useState(false) const shellMenuRef = useRef(null) useEffect(() => { const fallback = () => setShells(fallbackLocalShells() as ShellOption[]) void window.opsxApi ?.localListShells?.() .then((res) => { if (res?.success && res.shells?.length) setShells(res.shells) else fallback() }) .catch(fallback) }, []) useEffect(() => { if (!shellMenuOpen) return const onDoc = (e: MouseEvent) => { if (shellMenuRef.current && !shellMenuRef.current.contains(e.target as Node)) { setShellMenuOpen(false) } } document.addEventListener('mousedown', onDoc) return () => document.removeEventListener('mousedown', onDoc) }, [shellMenuOpen]) const defaultShell = shells.find((s) => s.isDefault) || shells[0] const defaultShellLabel = defaultShell ? shellDisplayFromPath(defaultShell.path || defaultShell.name).title : 'Shell' const openDefaultLocal = useCallback(() => { openLocalShellTab( defaultShell ? { path: defaultShell.path, kind: defaultShell.kind, name: defaultShell.name } : undefined ) }, [defaultShell]) const handleSessionClick = (session: (typeof sessions)[number]) => { const protocol = session.protocol ?? 'ssh' if ((protocol === 'rdp' || protocol === 'vnc') && !requirePro(protocol)) return addTab(buildOpenTabPayload(session)) } return ( OpsXShell {t('welcome.tagline')} onQuickConnect?.()} className="border-dark-700/60 bg-dark-800/50 group flex flex-col gap-4 rounded-xl border p-4 text-left transition-colors hover:border-accent-blue/35 hover:bg-dark-800" > {`${modKey}K`} {t('welcome.quickConnect.title')} {t('welcome.quickConnect.desc')} onNewSession?.()} className="border-dark-700/60 bg-dark-800/50 group flex flex-col gap-4 rounded-xl border p-4 text-left transition-colors hover:border-accent-green/35 hover:bg-dark-800" > {`${modKey}N`} {t('welcome.newSession.title')} {t('welcome.newSession.desc')} {t('welcome.localShell.title')} {defaultShellLabel} setShellMenuOpen((v) => !v)} className="border-dark-700/60 hover:bg-dark-700/40 flex w-9 flex-shrink-0 items-center justify-center border-l text-txt-faint transition-colors hover:text-content-secondary" > {shellMenuOpen && shells.length > 0 && ( {shells.map((s) => ( { openLocalShellTab({ path: s.path, kind: s.kind, name: s.name }) setShellMenuOpen(false) }} className="hover:bg-dark-700/70 flex w-full items-center gap-2.5 px-3 py-2 text-left transition-colors" > {s.kind} {s.name} {s.isDefault && ( {t('welcome.localShell.default')} )} ))} )} {t('welcome.sectionDesktop')} { if (!requirePro('rdp')) return onRdpConnect?.() }} className="border-dark-700/50 bg-dark-800/30 flex items-center gap-3 rounded-xl border px-4 py-3 text-left transition-colors hover:border-accent-blue/40" > {t('welcome.rdp.title')} {t('welcome.rdp.desc')} { if (!requirePro('vnc')) return onVncConnect?.() }} className="border-dark-700/50 bg-dark-800/30 flex items-center gap-3 rounded-xl border px-4 py-3 text-left transition-colors hover:border-accent-blue/40" > {t('welcome.vnc.title')} {t('welcome.vnc.desc')} {t('welcome.sectionRecent')} {recentSessions.length > 0 && {recentSessions.length}} {recentSessions.length === 0 ? ( ) : ( {recentSessions.map((session) => ( handleSessionClick(session)} className="hover:bg-dark-800/80 group flex w-full items-center gap-3 px-3.5 py-2.5 text-left transition-colors" > {session.name} {session.username}@{session.host}: {session.port || defaultPortForProtocol(sessionProtocol(session))} {session.group ? ( {session.group} ) : null} ))} )} ) }
{t('welcome.tagline')}