Skip to content

Commit 374a8c0

Browse files
committed
feat(update): make updates work without stopping the process
1 parent fe893d7 commit 374a8c0

8 files changed

Lines changed: 432 additions & 277 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { type checkForUpdatesCached } from '@epic-web/workshop-utils/git.server'
2+
import { useEffect, useRef } from 'react'
3+
import { toast } from 'sonner'
4+
import { z } from 'zod'
5+
6+
export function UpdateToast({
7+
repoUpdates,
8+
}: {
9+
repoUpdates: Awaited<ReturnType<typeof checkForUpdatesCached>>
10+
}) {
11+
// const { updatesAvailable, diffLink, remoteCommit } = repoUpdates
12+
// FIXME: mock data
13+
const updatesAvailable = true
14+
const diffLink =
15+
'https://github.com/epic-web/workshop-app/compare/main...update'
16+
const remoteCommit = '1234567890'
17+
18+
// Track the in-progress toast id and update notification id
19+
const inProgressToastId = useRef<ReturnType<typeof toast.loading> | null>(
20+
null,
21+
)
22+
const updateNotificationId = useRef<ReturnType<typeof toast.info> | null>(
23+
null,
24+
)
25+
const updateInProgress = useRef(false)
26+
27+
useEffect(() => {
28+
if (updatesAvailable && remoteCommit) {
29+
const id = toast.info('New workshop updates available', {
30+
duration: Infinity,
31+
description: (
32+
<div>
33+
{`Get the latest updates by clicking the update button. `}
34+
{diffLink ? (
35+
<a
36+
href={diffLink}
37+
target="_blank"
38+
rel="noreferrer"
39+
className="text-xs underline"
40+
>
41+
View changes
42+
</a>
43+
) : null}
44+
</div>
45+
),
46+
onDismiss: () => {
47+
// No-op for now, could call a dismiss endpoint if needed
48+
},
49+
action: {
50+
label: 'Update',
51+
onClick: async () => {
52+
// Dismiss the update notification immediately
53+
if (updateNotificationId.current) {
54+
toast.dismiss(updateNotificationId.current)
55+
updateNotificationId.current = null
56+
}
57+
// Show in-progress toast
58+
if (!inProgressToastId.current) {
59+
inProgressToastId.current = toast.loading('Update in progress...')
60+
}
61+
if (updateInProgress.current) return
62+
updateInProgress.current = true
63+
try {
64+
const { EPICSHOP_PARENT_PORT, EPICSHOP_PARENT_TOKEN } =
65+
window.ENV || {}
66+
if (!EPICSHOP_PARENT_PORT || !EPICSHOP_PARENT_TOKEN) {
67+
throw new Error('Update API not available')
68+
}
69+
const res = await fetch(
70+
`http://localhost:${EPICSHOP_PARENT_PORT}/__epicshop-restart`,
71+
{
72+
method: 'POST',
73+
headers: {
74+
'x-epicshop-token': EPICSHOP_PARENT_TOKEN,
75+
},
76+
},
77+
)
78+
if (res.ok) {
79+
throw new Error('Request to update workshop failed')
80+
}
81+
const data = await res.json().catch(() => ({}))
82+
const schema = z.object({
83+
status: z.enum(['ok', 'error']),
84+
message: z.string().optional(),
85+
})
86+
const parsed = schema.safeParse(data)
87+
if (!parsed.success) {
88+
console.error('Invalid response from update API', data)
89+
throw new Error('Invalid response from update API')
90+
}
91+
const { status, message } = parsed.data
92+
if (status === 'ok') {
93+
toast.success('Workshop updated', {
94+
description: 'Refreshing the page...',
95+
})
96+
window.location.reload()
97+
} else {
98+
toast.error('Failed to update workshop', {
99+
description: message || 'Unknown error',
100+
})
101+
}
102+
} catch (err) {
103+
toast.error('Failed to update workshop', {
104+
description: err instanceof Error ? err.message : String(err),
105+
})
106+
} finally {
107+
updateInProgress.current = false
108+
if (inProgressToastId.current) {
109+
toast.dismiss(inProgressToastId.current)
110+
inProgressToastId.current = null
111+
}
112+
}
113+
},
114+
},
115+
})
116+
updateNotificationId.current = id
117+
}
118+
}, [updatesAvailable, diffLink, remoteCommit])
119+
120+
return null
121+
}

packages/workshop-app/app/root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ import { GeneralErrorBoundary } from './components/error-boundary.tsx'
4545
import { EpicProgress } from './components/progress-bar.tsx'
4646
import { EpicToaster } from './components/toaster.tsx'
4747
import { TooltipProvider } from './components/ui/tooltip.tsx'
48+
import { UpdateToast } from './components/update-repo.tsx'
4849
import { Notifications } from './routes/admin+/notifications.tsx'
49-
import { UpdateToast } from './routes/admin+/update-repo.tsx'
5050
import { useTheme } from './routes/theme/index.tsx'
5151
import { getTheme } from './routes/theme/theme-session.server.ts'
5252
import appStylesheetUrl from './styles/app.css?url'

packages/workshop-app/app/routes/_app+/app.$appName+/epic_ws[.js].ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
5050
epicLiveReloadConnect({
5151
onOpen: () => window.location.reload(),
5252
}),
53-
1000
53+
1000
5454
);
5555
}
5656
};

packages/workshop-app/app/routes/admin+/update-repo.tsx

Lines changed: 0 additions & 138 deletions
This file was deleted.

0 commit comments

Comments
 (0)