Skip to content

Commit f5ecb5b

Browse files
committed
feat: add server URL config to error page with validation and branding updates
1 parent d2c5e1e commit f5ecb5b

1 file changed

Lines changed: 70 additions & 7 deletions

File tree

packages/app/src/pages/error.tsx

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import { TextField } from "@opencode-ai/ui/text-field"
2-
import { Logo } from "@opencode-ai/ui/logo"
1+
import { AsciiLogo } from "@opencode-ai/ui/logo"
32
import { Button } from "@opencode-ai/ui/button"
4-
import { Component, createMemo, Show } from "solid-js"
3+
import { TextField } from "@opencode-ai/ui/text-field"
4+
import { Component, createMemo, createSignal, Show } from "solid-js"
55
import { usePlatform } from "@/context/platform"
66
import { Icon } from "@opencode-ai/ui/icon"
7-
import { getStoredServerUrl, clearStoredServerUrl } from "@/lib/server-url"
7+
import {
8+
addToServerUrlHistory,
9+
clearStoredServerUrl,
10+
getStoredServerUrl,
11+
hasMixedContentRisk,
12+
isValidServerUrl,
13+
setStoredServerUrl,
14+
} from "@/lib/server-url"
815

916
export type InitError = {
1017
name: string
@@ -133,16 +140,39 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
133140

134141
const hasServerOverride = createMemo(() => !!getStoredServerUrl())
135142
const showResetButton = createMemo(() => isConnectionError(props.error) && hasServerOverride())
143+
const showServerConfig = createMemo(() => isConnectionError(props.error))
144+
const [serverUrl, setServerUrl] = createSignal(getStoredServerUrl() ?? "")
145+
const inputValid = createMemo(() => {
146+
const url = serverUrl().trim()
147+
return url.length > 0 && isValidServerUrl(url)
148+
})
149+
const showInvalidUrl = createMemo(() => {
150+
const url = serverUrl().trim()
151+
return url.length > 0 && !isValidServerUrl(url)
152+
})
153+
const showMixedContentWarning = createMemo(() => {
154+
const url = serverUrl().trim()
155+
if (!url) return false
156+
return hasMixedContentRisk(url)
157+
})
136158

137159
function resetServerUrl() {
138160
clearStoredServerUrl()
139161
platform.restart?.() ?? window.location.reload()
140162
}
141163

164+
function applyServerUrl() {
165+
const url = serverUrl().trim()
166+
if (!isValidServerUrl(url)) return
167+
setStoredServerUrl(url)
168+
addToServerUrlHistory(url)
169+
platform.restart?.() ?? window.location.reload()
170+
}
171+
142172
return (
143173
<div class="relative flex-1 h-screen w-screen min-h-0 flex flex-col items-center justify-center bg-background-base font-sans">
144174
<div class="w-2/3 max-w-3xl flex flex-col items-center justify-center gap-8">
145-
<Logo class="w-58.5 opacity-12 shrink-0" />
175+
<AsciiLogo scale={1.1} class="opacity-25 shrink-0" />
146176
<div class="flex flex-col items-center gap-2 text-center">
147177
<h1 class="text-lg font-medium text-text-strong">Something went wrong</h1>
148178
<p class="text-sm text-text-weak">An error occurred while loading the application.</p>
@@ -156,6 +186,39 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
156186
label="Error Details"
157187
hideLabel
158188
/>
189+
<Show when={showServerConfig()}>
190+
<div class="flex flex-col gap-3 w-full">
191+
<div class="text-xs text-text-weak uppercase tracking-wide text-left">Server</div>
192+
<div class="flex w-full gap-2">
193+
<TextField
194+
value={serverUrl()}
195+
onInput={(e: InputEvent & { currentTarget: HTMLInputElement }) => setServerUrl(e.currentTarget.value)}
196+
placeholder="http://localhost:4096"
197+
class="flex-1 font-mono"
198+
onKeyDown={(e: KeyboardEvent) => {
199+
if (e.key === "Enter" && inputValid()) {
200+
applyServerUrl()
201+
}
202+
}}
203+
/>
204+
<Button size="large" onClick={applyServerUrl} disabled={!inputValid()}>
205+
Set & Restart
206+
</Button>
207+
</div>
208+
<Show when={showInvalidUrl()}>
209+
<p class="text-xs text-text-weak text-left">Enter a valid HTTP or HTTPS URL.</p>
210+
</Show>
211+
<Show when={showMixedContentWarning()}>
212+
<div class="flex items-start gap-2 p-2 rounded-md bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 text-xs">
213+
<Icon name="circle-ban-sign" size="small" class="shrink-0 mt-0.5" />
214+
<span>
215+
This HTTP URL will be blocked by your browser (mixed content). Use{" "}
216+
<code class="font-mono bg-surface-base px-1 rounded">localhost</code> or an HTTPS URL instead.
217+
</span>
218+
</div>
219+
</Show>
220+
</div>
221+
</Show>
159222
<div class="flex flex-col items-center gap-3">
160223
<Button size="large" onClick={platform.restart}>
161224
Restart
@@ -171,11 +234,11 @@ export const ErrorPage: Component<ErrorPageProps> = (props) => {
171234
</div>
172235
<div class="flex flex-col items-center gap-2">
173236
<div class="flex items-center justify-center gap-1">
174-
Please report this error to the OpenCode team
237+
Please report this error to the shuvcode team
175238
<button
176239
type="button"
177240
class="flex items-center text-text-interactive-base gap-1"
178-
onClick={() => platform.openLink("https://opencode.ai/desktop-feedback")}
241+
onClick={() => platform.openLink("https://shuv.ai")}
179242
>
180243
<div>on Discord</div>
181244
<Icon name="discord" class="text-text-interactive-base" />

0 commit comments

Comments
 (0)