Skip to content

Commit

Permalink
Merge pull request #698 from contember/feat/outdated-app-dialog
Browse files Browse the repository at this point in the history
feat(playground): outdated application checker
  • Loading branch information
matej21 authored May 13, 2024
2 parents 84a5c17 + 2f6f9e5 commit fdf2598
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 0 deletions.
5 changes: 5 additions & 0 deletions build/api/interface.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ export const RedirectOnPersist: ({ to }: {
to: RoutingLinkTarget;
}) => null;

// @public (undocumented)
export const useIsApplicationOutdated: ({ checkIntervalMs }?: {
checkIntervalMs?: number | undefined;
}) => boolean;


export * from "@contember/react-binding";
export * from "@contember/react-identity";
Expand Down
1 change: 1 addition & 0 deletions packages/interface/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useIsApplicationOutdated'
62 changes: 62 additions & 0 deletions packages/interface/src/hooks/useIsApplicationOutdated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useCallback, useEffect, useState } from 'react'

const defaultCheckIntervalMs = 30_000


export const useIsApplicationOutdated = ({ checkIntervalMs = defaultCheckIntervalMs }: {
checkIntervalMs?: number
} = {}) => {
const [initialVersion] = useState(() => {
return getVersionFromDocument(document.head)
})
const [isOutdated, setIsOutdated] = useState(false)
const fetchVersion = useFetchVersion()

const checkVersion = useCallback(async () => {
if (document.hidden) {
return
}
const version = await fetchVersion()
if (version !== initialVersion || Math.random() < 0.3) {
setIsOutdated(true)
}
}, [fetchVersion, initialVersion])
const shouldCheck = !!initialVersion && !isOutdated

useEffect(() => {
if (!shouldCheck) {
return
}
const handle = setInterval(checkVersion, checkIntervalMs)
return () => clearInterval(handle)
}, [checkIntervalMs, checkVersion, shouldCheck])

useEffect(() => {
if (!shouldCheck) {
return
}
document.addEventListener('visibilitychange', checkVersion)
return () => document.removeEventListener('visibilitychange', checkVersion)
}, [checkVersion, shouldCheck])

return isOutdated
}

const getVersionFromDocument = (node: ParentNode) => {
const meta = node.querySelector<HTMLMetaElement>('meta[name=contember-build-version]')
return meta?.content
}

const useFetchVersion = () => {
return useCallback(async () => {
try {
const result = await (await fetch(window.location.href)).text()
const html = document.createElement('html')
html.innerHTML = result
return getVersionFromDocument(html)
} catch (e) {
console.error(e)
return undefined
}
}, [])
}
1 change: 1 addition & 0 deletions packages/interface/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './bootstrap'
export * from './components'
export * from './hooks'
export * from '@contember/react-routing'
export * from '@contember/react-binding'
export * from '@contember/react-identity'
Expand Down
2 changes: 2 additions & 0 deletions packages/playground/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { LogInIcon } from 'lucide-react'
import { LoginWithEmail } from './lib/components/dev/login-panel'
import { createRoot } from 'react-dom/client'
import { getConfig } from './config'
import { OutdatedApplicationDialog } from './lib/components/outdated-application-dialog'

const errorHandler = createErrorHandler((dom, react, onRecoverableError) => createRoot(dom, { onRecoverableError }).render(react))

Expand All @@ -27,6 +28,7 @@ errorHandler(onRecoverableError => createRoot(rootEl, { onRecoverableError }).re
{ eager: true },
)}
/>
<OutdatedApplicationDialog />
{import.meta.env.DEV && <DevBar>
<DevPanel heading="Login" icon={<LogInIcon />}><LoginWithEmail /></DevPanel>
</DevBar>}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ComponentType, useCallback, useEffect, useState } from 'react'
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader } from './ui/alert-dialog'
import { useIsApplicationOutdated } from '@contember/interface'
import { ClockIcon, RefreshCwIcon } from 'lucide-react'
import { dict } from '../dict'

const postponeTimeoutMs = 60_000 * 5
const checkIntervalMs = 30_000

export const OutdatedApplicationDialog: ComponentType = () => {
const isOutdated = useIsApplicationOutdated({ checkIntervalMs })
const [open, setOpen] = useState(true)

const postpone = useCallback(() => {
setOpen(false)
setTimeout(() => setOpen(true), postponeTimeoutMs)
}, [])

return (
<AlertDialog open={isOutdated && open} onOpenChange={it => !it ? postpone() : null}>
<AlertDialogContent>
<AlertDialogHeader>{dict.outdatedApplication.title}</AlertDialogHeader>
<AlertDialogDescription>{dict.outdatedApplication.description}</AlertDialogDescription>

<div className="text-sm text-destructive">{dict.outdatedApplication.warning}</div>
<AlertDialogFooter>
<AlertDialogCancel onClick={postpone} className="gap-1">
<ClockIcon className="w-3 h-4"/>
{dict.outdatedApplication.snooze}
</AlertDialogCancel>
<AlertDialogAction onClick={() => window.location.reload()} className="gap-1">
<RefreshCwIcon className="w-3 h-4" />
{dict.outdatedApplication.refreshNow}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}


8 changes: 8 additions & 0 deletions packages/playground/admin/lib/dict.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,12 @@ export const dict = {
empty: 'No items.',
addItem: 'Add item',
},
outdatedApplication: {
title: 'An updated version is available',
description: 'To access the latest features and improvements, kindly refresh your browser.',
warning: 'Any unsaved data will be lost. Please save your work before refreshing.',
snooze: 'Snooze',
refreshNow: 'Refresh now',

},
}

0 comments on commit fdf2598

Please sign in to comment.