Skip to content

Commit 07572d4

Browse files
committed
Provide update button for PWA.
1 parent 2850b83 commit 07572d4

File tree

6 files changed

+120
-70
lines changed

6 files changed

+120
-70
lines changed

frontend/public/manifest.json

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

frontend/src/App.tsx

Lines changed: 54 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import { useState, useEffect } from "react"
12
import { Redirect, Route } from "react-router-dom"
3+
import { registerSW } from "virtual:pwa-register"
24
import { QueryClient } from "@tanstack/react-query"
35
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client"
46
import {
@@ -60,43 +62,57 @@ const queryClient = new QueryClient({
6062

6163
const persister = createIDBPersister()
6264

63-
const App: React.FC = () => (
64-
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
65-
<IonApp>
66-
<IonReactRouter>
67-
<IonTabs>
68-
<IonRouterOutlet>
69-
<Route exact path="/collection">
70-
<CollectionPage />
71-
</Route>
72-
<Route exact path="/profile">
73-
<ProfilePage />
74-
</Route>
75-
<Route path="/search">
76-
<SearchPage />
77-
</Route>
78-
<Route exact path="/">
79-
<Redirect to="/collection" />
80-
</Route>
81-
</IonRouterOutlet>
82-
<IonTabBar slot="bottom" translucent>
83-
<IonTabButton tab="collection" href="/collection">
84-
<IonIcon aria-hidden="true" icon={discOutline} />
85-
<IonLabel>Collection</IonLabel>
86-
</IonTabButton>
87-
<IonTabButton tab="profile" href="/profile">
88-
<IonIcon aria-hidden="true" icon={personOutline} />
89-
<IonLabel>Profile</IonLabel>
90-
</IonTabButton>
91-
<IonTabButton tab="search" href="/search">
92-
<IonIcon aria-hidden="true" icon={searchOutline} />
93-
<IonLabel>Search</IonLabel>
94-
</IonTabButton>
95-
</IonTabBar>
96-
</IonTabs>
97-
</IonReactRouter>
98-
</IonApp>
99-
</PersistQueryClientProvider>
100-
)
65+
const App: React.FC = () => {
66+
const [updateAvailable, setUpdateAvailable] = useState<boolean>(false)
67+
68+
useEffect(() => {
69+
const updateSW = registerSW({
70+
onNeedRefresh() {
71+
setUpdateAvailable(true)
72+
},
73+
onOfflineReady() {
74+
console.log("The app is ready to work offline.")
75+
},
76+
})
77+
}, [])
78+
return (
79+
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
80+
<IonApp>
81+
<IonReactRouter>
82+
<IonTabs>
83+
<IonRouterOutlet>
84+
<Route exact path="/collection">
85+
<CollectionPage />
86+
</Route>
87+
<Route exact path="/profile">
88+
<ProfilePage hasUpdate={updateAvailable} />
89+
</Route>
90+
<Route path="/search">
91+
<SearchPage />
92+
</Route>
93+
<Route exact path="/">
94+
<Redirect to="/collection" />
95+
</Route>
96+
</IonRouterOutlet>
97+
<IonTabBar slot="bottom" translucent>
98+
<IonTabButton tab="collection" href="/collection">
99+
<IonIcon aria-hidden="true" icon={discOutline} />
100+
<IonLabel>Collection</IonLabel>
101+
</IonTabButton>
102+
<IonTabButton tab="profile" href="/profile">
103+
<IonIcon aria-hidden="true" icon={personOutline} />
104+
<IonLabel>Profile</IonLabel>
105+
</IonTabButton>
106+
<IonTabButton tab="search" href="/search">
107+
<IonIcon aria-hidden="true" icon={searchOutline} />
108+
<IonLabel>Search</IonLabel>
109+
</IonTabButton>
110+
</IonTabBar>
111+
</IonTabs>
112+
</IonReactRouter>
113+
</IonApp>
114+
</PersistQueryClientProvider>
115+
)
116+
}
101117

102118
export default App

frontend/src/modal/Settings.tsx

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import packageJson from "../../package.json"
2121

2222
interface Props {
2323
open: boolean
24+
hasUpdate: boolean
2425
onClose: () => void
2526
onSave: () => void
2627
}
2728

28-
const Settings: React.FC<Props> = ({ open, onClose, onSave }) => {
29+
const Settings: React.FC<Props> = ({ open, hasUpdate, onClose, onSave }) => {
2930
const queryClient = useQueryClient()
3031
const [{ username, token }, saveAuth, clearAuth] = useAuth()
3132
const [newUsername, setNewUsername] = useState<string>(username ?? "")
@@ -48,6 +49,12 @@ const Settings: React.FC<Props> = ({ open, onClose, onSave }) => {
4849
onSave()
4950
}
5051

52+
const handleUpdate = () => {
53+
if (window.location.reload) {
54+
window.location.reload()
55+
}
56+
}
57+
5158
return (
5259
<IonModal isOpen={open}>
5360
<IonHeader>
@@ -86,12 +93,24 @@ const Settings: React.FC<Props> = ({ open, onClose, onSave }) => {
8693
<IonNote color="medium" class="ion-margin-horizontal" style={{ display: "block" }}>
8794
Until OAuth is implemented, we currently use Access Token for authentication. To get your token,{" "}
8895
<a href="https://www.discogs.com/settings/developers">visit the Developer page</a> and copy your
89-
token, or click Generate if you do not have one.
96+
token, or click Generate if you do not have one.ssss
9097
</IonNote>
9198
<IonList inset={true}>
9299
<IonItem>
93100
<IonLabel>App Version</IonLabel>
94-
<IonLabel slot="end">{packageJson.version}</IonLabel>
101+
<IonLabel slot="end">
102+
{packageJson.version}
103+
{hasUpdate && (
104+
<IonButton
105+
onClick={handleUpdate}
106+
color="primary"
107+
size="small"
108+
style={{ marginLeft: "10px" }}
109+
>
110+
Update
111+
</IonButton>
112+
)}
113+
</IonLabel>
95114
</IonItem>
96115
<IonItem>
97116
<IonLabel>Storage Used</IonLabel>

frontend/src/pages/Profile.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { Settings } from "../modal"
2121
import { useAuth } from "../hooks"
2222
import "./Profile.css"
2323

24-
const ProfilePage: React.FC = () => {
24+
const ProfilePage: React.FC<{ hasUpdate: boolean }> = ({ hasUpdate }) => {
2525
const [openSettingsDialog, setOpenSettingsDialog] = useState<boolean>(false)
2626
const betaBanner = import.meta.env.VITE_BETA_BANNER
2727

@@ -51,6 +51,7 @@ const ProfilePage: React.FC = () => {
5151

5252
<Settings
5353
open={openSettingsDialog}
54+
hasUpdate={hasUpdate}
5455
onClose={() => setOpenSettingsDialog(false)}
5556
onSave={() => setOpenSettingsDialog(false)}
5657
/>
@@ -110,6 +111,7 @@ const ProfilePage: React.FC = () => {
110111

111112
<Settings
112113
open={openSettingsDialog}
114+
hasUpdate={hasUpdate}
113115
onClose={() => setOpenSettingsDialog(false)}
114116
onSave={() => setOpenSettingsDialog(false)}
115117
/>

frontend/src/vite-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/// <reference types="vite/client" />
2+
/// <reference types="vite-plugin-pwa/client" />

frontend/vite.config.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,46 @@ import { VitePWA } from "vite-plugin-pwa"
77

88
// https://vitejs.dev/config/
99
export default defineConfig({
10-
plugins: [react(), legacy(), VitePWA({ registerType: "autoUpdate" })],
10+
plugins: [
11+
react(),
12+
legacy(),
13+
VitePWA({
14+
registerType: "autoUpdate",
15+
devOptions: {
16+
enabled: false,
17+
},
18+
workbox: {
19+
cleanupOutdatedCaches: true,
20+
},
21+
manifest: {
22+
short_name: "Offline Collection",
23+
name: "Offline Collection",
24+
icons: [
25+
{
26+
src: "favicon.ico",
27+
sizes: "64x64",
28+
type: "image/png",
29+
},
30+
{
31+
src: "logo-mask-192.png",
32+
type: "image/png",
33+
sizes: "192x192",
34+
purpose: "maskable",
35+
},
36+
{
37+
src: "logo-mask-512.png",
38+
type: "image/png",
39+
sizes: "512x512",
40+
purpose: "maskable",
41+
},
42+
],
43+
start_url: ".",
44+
display: "standalone",
45+
theme_color: "#0d0d0d",
46+
background_color: "#0d0d0d",
47+
},
48+
}),
49+
],
1150
test: {
1251
globals: true,
1352
environment: "jsdom",

0 commit comments

Comments
 (0)