11import { motion } from "framer-motion" ;
2- import { Check , Pencil , Plus , Server , Trash2 } from "lucide-react" ;
2+ import { Check , ChevronRight , Cloud , Pencil , Plus , Server , Trash2 } from "lucide-react" ;
33import { useState } from "react" ;
4- import { SettingsForm } from "@/components/settings/SettingsForm" ;
4+ import { type ConnectionPreset , SettingsForm } from "@/components/settings/SettingsForm" ;
55import { Button } from "@/components/ui/button" ;
66import { Muted } from "@/components/ui/typography" ;
77import { useInstances } from "@/hooks/useInstances" ;
8- import type { Instance } from "@/lib/config" ;
8+ import { HONCHO_CLOUD_URL , type Instance , isCloudInstance } from "@/lib/config" ;
99import { COLOR } from "@/lib/constants" ;
1010
11- type Mode = { kind : "list" } | { kind : "create" } | { kind : "edit" ; id : string } ;
11+ type Mode =
12+ | { kind : "list" }
13+ | { kind : "choose-type" }
14+ | { kind : "create" ; preset : ConnectionPreset }
15+ | { kind : "edit" ; id : string } ;
1216
1317interface InstancesManagerProps {
1418 onActivated ?: ( ) => void ;
1519}
1620
1721export function InstancesManager ( { onActivated } : InstancesManagerProps ) {
1822 const { instances, activeId, activate, remove } = useInstances ( ) ;
19- const [ mode , setMode ] = useState < Mode > ( { kind : "list" } ) ;
23+ const isFirstRun = instances . length === 0 ;
24+ const [ mode , setMode ] = useState < Mode > ( isFirstRun ? { kind : "choose-type" } : { kind : "list" } ) ;
25+
26+ const backFromCreate = ( ) => setMode ( isFirstRun ? { kind : "choose-type" } : { kind : "list" } ) ;
27+
28+ if ( mode . kind === "choose-type" ) {
29+ return (
30+ < ConnectionTypeChooser
31+ onPick = { ( preset ) => setMode ( { kind : "create" , preset } ) }
32+ onCancel = { isFirstRun ? undefined : ( ) => setMode ( { kind : "list" } ) }
33+ />
34+ ) ;
35+ }
2036
2137 if ( mode . kind === "create" ) {
2238 return (
2339 < SettingsForm
2440 instance = { null }
41+ preset = { mode . preset }
2542 onSaved = { ( ) => {
2643 setMode ( { kind : "list" } ) ;
2744 onActivated ?.( ) ;
2845 } }
29- onCancel = { instances . length > 0 ? ( ) => setMode ( { kind : "list" } ) : undefined }
30- hideCancel = { instances . length === 0 }
46+ onCancel = { backFromCreate }
47+ hideCancel = { false }
48+ submitLabel = { isFirstRun ? "Save Connection" : undefined }
3149 />
3250 ) ;
3351 }
@@ -44,17 +62,6 @@ export function InstancesManager({ onActivated }: InstancesManagerProps) {
4462 ) ;
4563 }
4664
47- if ( instances . length === 0 ) {
48- return (
49- < SettingsForm
50- instance = { null }
51- onSaved = { ( ) => onActivated ?.( ) }
52- hideCancel
53- submitLabel = "Save Connection"
54- />
55- ) ;
56- }
57-
5865 return (
5966 < div className = "space-y-3" >
6067 < div className = "space-y-2" >
@@ -76,7 +83,7 @@ export function InstancesManager({ onActivated }: InstancesManagerProps) {
7683 < Button
7784 type = "button"
7885 variant = "ghost"
79- onClick = { ( ) => setMode ( { kind : "create " } ) }
86+ onClick = { ( ) => setMode ( { kind : "choose-type " } ) }
8087 className = "w-full py-2.5 px-4 rounded-xl flex items-center justify-center gap-2"
8188 >
8289 < Plus className = "w-4 h-4" strokeWidth = { 1.5 } />
@@ -86,6 +93,109 @@ export function InstancesManager({ onActivated }: InstancesManagerProps) {
8693 ) ;
8794}
8895
96+ interface ConnectionTypeChooserProps {
97+ onPick : ( preset : ConnectionPreset ) => void ;
98+ onCancel ?: ( ) => void ;
99+ }
100+
101+ function ConnectionTypeChooser ( { onPick, onCancel } : ConnectionTypeChooserProps ) {
102+ return (
103+ < div
104+ className = "rounded-2xl p-6 space-y-3"
105+ style = { {
106+ background : "var(--bg-2)" ,
107+ border : "1px solid var(--border)" ,
108+ } }
109+ >
110+ < div className = "mb-2" >
111+ < h2 className = "text-base font-medium" style = { { color : "var(--text-1)" } } >
112+ How do you want to connect?
113+ </ h2 >
114+ < Muted className = "text-xs mt-1" >
115+ You can add more connections later — Cloud, self-hosted, or both.
116+ </ Muted >
117+ </ div >
118+
119+ < ConnectionTypeButton
120+ icon = { Cloud }
121+ title = "Honcho Cloud"
122+ description = { `Hosted at ${ HONCHO_CLOUD_URL . replace ( / ^ h t t p s ? : \/ \/ / , "" ) } — sign in with your API key` }
123+ accent
124+ onClick = { ( ) => onPick ( "cloud" ) }
125+ />
126+
127+ < ConnectionTypeButton
128+ icon = { Server }
129+ title = "Self-Hosted"
130+ description = "Connect to your own Honcho deployment"
131+ onClick = { ( ) => onPick ( "self-hosted" ) }
132+ />
133+
134+ { onCancel && (
135+ < div className = "pt-1" >
136+ < Button
137+ type = "button"
138+ variant = "ghost"
139+ onClick = { onCancel }
140+ className = "w-full py-2 px-4 rounded-xl"
141+ >
142+ Cancel
143+ </ Button >
144+ </ div >
145+ ) }
146+ </ div >
147+ ) ;
148+ }
149+
150+ interface ConnectionTypeButtonProps {
151+ icon : typeof Cloud ;
152+ title : string ;
153+ description : string ;
154+ accent ?: boolean ;
155+ onClick : ( ) => void ;
156+ }
157+
158+ function ConnectionTypeButton ( {
159+ icon : Icon ,
160+ title,
161+ description,
162+ accent,
163+ onClick,
164+ } : ConnectionTypeButtonProps ) {
165+ return (
166+ < button
167+ type = "button"
168+ onClick = { onClick }
169+ className = "w-full rounded-xl p-4 flex items-center gap-3 text-left transition-colors"
170+ style = { {
171+ background : "var(--surface)" ,
172+ border : `1px solid ${ accent ? "var(--accent-border)" : "var(--border)" } ` ,
173+ } }
174+ >
175+ < div
176+ className = "w-10 h-10 rounded-lg flex items-center justify-center shrink-0"
177+ style = { {
178+ background : accent ? "var(--accent)" : "var(--bg-2)" ,
179+ color : accent ? "white" : "var(--text-2)" ,
180+ } }
181+ >
182+ < Icon className = "w-5 h-5" strokeWidth = { 1.5 } />
183+ </ div >
184+ < div className = "min-w-0 flex-1" >
185+ < p className = "text-sm font-medium" style = { { color : "var(--text-1)" } } >
186+ { title }
187+ </ p >
188+ < Muted className = "text-xs mt-0.5" > { description } </ Muted >
189+ </ div >
190+ < ChevronRight
191+ className = "w-4 h-4 shrink-0"
192+ style = { { color : "var(--text-3)" } }
193+ strokeWidth = { 1.5 }
194+ />
195+ </ button >
196+ ) ;
197+ }
198+
89199interface InstanceRowProps {
90200 instance : Instance ;
91201 active : boolean ;
@@ -96,6 +206,7 @@ interface InstanceRowProps {
96206
97207function InstanceRow ( { instance, active, onActivate, onEdit, onDelete } : InstanceRowProps ) {
98208 const [ confirmingDelete , setConfirmingDelete ] = useState ( false ) ;
209+ const cloud = isCloudInstance ( instance ) ;
99210
100211 return (
101212 < motion . div
@@ -122,6 +233,8 @@ function InstanceRow({ instance, active, onActivate, onEdit, onDelete }: Instanc
122233 >
123234 { active ? (
124235 < Check className = "w-4 h-4" strokeWidth = { 2 } />
236+ ) : cloud ? (
237+ < Cloud className = "w-4 h-4" strokeWidth = { 1.5 } />
125238 ) : (
126239 < Server className = "w-4 h-4" strokeWidth = { 1.5 } />
127240 ) }
@@ -134,7 +247,7 @@ function InstanceRow({ instance, active, onActivate, onEdit, onDelete }: Instanc
134247 { instance . name }
135248 </ p >
136249 < Muted className = "text-xs font-mono truncate" >
137- { instance . baseUrl . replace ( / ^ h t t p s ? : \/ \/ / , "" ) }
250+ { cloud ? "Honcho Cloud" : instance . baseUrl . replace ( / ^ h t t p s ? : \/ \/ / , "" ) }
138251 </ Muted >
139252 </ div >
140253 </ button >
0 commit comments