forked from Shiloh/remnantchat
* feat: [#24] wire up settings page * feat: [#24] stand up settings UI * feat: [#24] implement storage deletion * feat: [#24] confirm deletion of settings data
This commit is contained in:
parent
20d8337082
commit
e259196942
@ -4,10 +4,12 @@ import { v4 as uuid } from 'uuid'
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
|
|
||||||
import * as serviceWorkerRegistration from 'serviceWorkerRegistration'
|
import * as serviceWorkerRegistration from 'serviceWorkerRegistration'
|
||||||
|
import { StorageContext } from 'contexts/StorageContext'
|
||||||
import { SettingsContext } from 'contexts/SettingsContext'
|
import { SettingsContext } from 'contexts/SettingsContext'
|
||||||
import { routes } from 'config/routes'
|
import { routes } from 'config/routes'
|
||||||
import { Home } from 'pages/Home'
|
import { Home } from 'pages/Home'
|
||||||
import { About } from 'pages/About'
|
import { About } from 'pages/About'
|
||||||
|
import { Settings } from 'pages/Settings'
|
||||||
import { PublicRoom } from 'pages/PublicRoom'
|
import { PublicRoom } from 'pages/PublicRoom'
|
||||||
import { UserSettings } from 'models/settings'
|
import { UserSettings } from 'models/settings'
|
||||||
import { PersistedStorageKeys } from 'models/storage'
|
import { PersistedStorageKeys } from 'models/storage'
|
||||||
@ -19,12 +21,13 @@ export interface BootstrapProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Bootstrap({
|
function Bootstrap({
|
||||||
persistedStorage = localforage.createInstance({
|
persistedStorage: persistedStorageProp = localforage.createInstance({
|
||||||
name: 'chitchatter',
|
name: 'chitchatter',
|
||||||
description: 'Persisted settings data for chitchatter',
|
description: 'Persisted settings data for chitchatter',
|
||||||
}),
|
}),
|
||||||
getUuid = uuid,
|
getUuid = uuid,
|
||||||
}: BootstrapProps) {
|
}: BootstrapProps) {
|
||||||
|
const [persistedStorage] = useState(persistedStorageProp)
|
||||||
const [appNeedsUpdate, setAppNeedsUpdate] = useState(false)
|
const [appNeedsUpdate, setAppNeedsUpdate] = useState(false)
|
||||||
const [hasLoadedSettings, setHasLoadedSettings] = useState(false)
|
const [hasLoadedSettings, setHasLoadedSettings] = useState(false)
|
||||||
const [userSettings, setUserSettings] = useState<UserSettings>({
|
const [userSettings, setUserSettings] = useState<UserSettings>({
|
||||||
@ -46,14 +49,14 @@ function Bootstrap({
|
|||||||
if (hasLoadedSettings) return
|
if (hasLoadedSettings) return
|
||||||
|
|
||||||
const persistedUserSettings =
|
const persistedUserSettings =
|
||||||
await persistedStorage.getItem<UserSettings>(
|
await persistedStorageProp.getItem<UserSettings>(
|
||||||
PersistedStorageKeys.USER_SETTINGS
|
PersistedStorageKeys.USER_SETTINGS
|
||||||
)
|
)
|
||||||
|
|
||||||
if (persistedUserSettings) {
|
if (persistedUserSettings) {
|
||||||
setUserSettings(persistedUserSettings)
|
setUserSettings(persistedUserSettings)
|
||||||
} else {
|
} else {
|
||||||
await persistedStorage.setItem(
|
await persistedStorageProp.setItem(
|
||||||
PersistedStorageKeys.USER_SETTINGS,
|
PersistedStorageKeys.USER_SETTINGS,
|
||||||
userSettings
|
userSettings
|
||||||
)
|
)
|
||||||
@ -61,7 +64,7 @@ function Bootstrap({
|
|||||||
|
|
||||||
setHasLoadedSettings(true)
|
setHasLoadedSettings(true)
|
||||||
})()
|
})()
|
||||||
}, [hasLoadedSettings, persistedStorage, userSettings, userId])
|
}, [hasLoadedSettings, persistedStorageProp, userSettings, userId])
|
||||||
|
|
||||||
const settingsContextValue = {
|
const settingsContextValue = {
|
||||||
updateUserSettings: async (changedSettings: Partial<UserSettings>) => {
|
updateUserSettings: async (changedSettings: Partial<UserSettings>) => {
|
||||||
@ -70,7 +73,7 @@ function Bootstrap({
|
|||||||
...changedSettings,
|
...changedSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
await persistedStorage.setItem(
|
await persistedStorageProp.setItem(
|
||||||
PersistedStorageKeys.USER_SETTINGS,
|
PersistedStorageKeys.USER_SETTINGS,
|
||||||
newSettings
|
newSettings
|
||||||
)
|
)
|
||||||
@ -80,8 +83,13 @@ function Bootstrap({
|
|||||||
getUserSettings: () => ({ ...userSettings }),
|
getUserSettings: () => ({ ...userSettings }),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const storageContextValue = {
|
||||||
|
getPersistedStorage: () => persistedStorage,
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
|
<StorageContext.Provider value={storageContextValue}>
|
||||||
<SettingsContext.Provider value={settingsContextValue}>
|
<SettingsContext.Provider value={settingsContextValue}>
|
||||||
<Shell appNeedsUpdate={appNeedsUpdate} userPeerId={userId}>
|
<Shell appNeedsUpdate={appNeedsUpdate} userPeerId={userId}>
|
||||||
{hasLoadedSettings ? (
|
{hasLoadedSettings ? (
|
||||||
@ -94,6 +102,10 @@ function Bootstrap({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Route path={routes.ABOUT} element={<About />} />
|
<Route path={routes.ABOUT} element={<About />} />
|
||||||
|
<Route
|
||||||
|
path={routes.SETTINGS}
|
||||||
|
element={<Settings userId={userId} />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={routes.PUBLIC_ROOM}
|
path={routes.PUBLIC_ROOM}
|
||||||
element={<PublicRoom userId={userId} />}
|
element={<PublicRoom userId={userId} />}
|
||||||
@ -104,6 +116,7 @@ function Bootstrap({
|
|||||||
)}
|
)}
|
||||||
</Shell>
|
</Shell>
|
||||||
</SettingsContext.Provider>
|
</SettingsContext.Provider>
|
||||||
|
</StorageContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
59
src/components/ConfirmDialog/ConfirmDialog.tsx
Normal file
59
src/components/ConfirmDialog/ConfirmDialog.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import Dialog from '@mui/material/Dialog'
|
||||||
|
import DialogActions from '@mui/material/DialogActions'
|
||||||
|
import DialogContent from '@mui/material/DialogContent'
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText'
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
|
import WarningIcon from '@mui/icons-material/Warning'
|
||||||
|
|
||||||
|
interface ConfirmDialogProps {
|
||||||
|
isOpen: boolean
|
||||||
|
onCancel: () => void
|
||||||
|
onConfirm: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConfirmDialog = ({
|
||||||
|
isOpen,
|
||||||
|
onCancel,
|
||||||
|
onConfirm,
|
||||||
|
}: ConfirmDialogProps) => {
|
||||||
|
const handleDialogClose = () => {
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
onClose={handleDialogClose}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<WarningIcon
|
||||||
|
fontSize="medium"
|
||||||
|
sx={theme => ({
|
||||||
|
color: theme.palette.warning.main,
|
||||||
|
mr: theme.spacing(1),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
Are you sure?
|
||||||
|
</Box>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">
|
||||||
|
This action cannot be undone.
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onCancel} autoFocus>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onConfirm} color="error">
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/ConfirmDialog/index.ts
Normal file
1
src/components/ConfirmDialog/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './ConfirmDialog'
|
@ -13,6 +13,7 @@ import ListItemButton from '@mui/material/ListItemButton'
|
|||||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
import ListItemIcon from '@mui/material/ListItemIcon'
|
||||||
import ListItemText from '@mui/material/ListItemText'
|
import ListItemText from '@mui/material/ListItemText'
|
||||||
import Home from '@mui/icons-material/Home'
|
import Home from '@mui/icons-material/Home'
|
||||||
|
import SettingsApplications from '@mui/icons-material/SettingsRounded'
|
||||||
import QuestionMark from '@mui/icons-material/QuestionMark'
|
import QuestionMark from '@mui/icons-material/QuestionMark'
|
||||||
import Brightness4Icon from '@mui/icons-material/Brightness4'
|
import Brightness4Icon from '@mui/icons-material/Brightness4'
|
||||||
import Brightness7Icon from '@mui/icons-material/Brightness7'
|
import Brightness7Icon from '@mui/icons-material/Brightness7'
|
||||||
@ -27,18 +28,20 @@ export const drawerWidth = 240
|
|||||||
|
|
||||||
export interface DrawerProps extends PropsWithChildren {
|
export interface DrawerProps extends PropsWithChildren {
|
||||||
isDrawerOpen: boolean
|
isDrawerOpen: boolean
|
||||||
|
onAboutLinkClick: () => void
|
||||||
onDrawerClose: () => void
|
onDrawerClose: () => void
|
||||||
onHomeLinkClick: () => void
|
onHomeLinkClick: () => void
|
||||||
onAboutLinkClick: () => void
|
onSettingsLinkClick: () => void
|
||||||
theme: Theme
|
theme: Theme
|
||||||
userPeerId: string
|
userPeerId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Drawer = ({
|
export const Drawer = ({
|
||||||
isDrawerOpen,
|
isDrawerOpen,
|
||||||
|
onAboutLinkClick,
|
||||||
onDrawerClose,
|
onDrawerClose,
|
||||||
onHomeLinkClick,
|
onHomeLinkClick,
|
||||||
onAboutLinkClick,
|
onSettingsLinkClick,
|
||||||
theme,
|
theme,
|
||||||
userPeerId,
|
userPeerId,
|
||||||
}: DrawerProps) => {
|
}: DrawerProps) => {
|
||||||
@ -101,6 +104,16 @@ export const Drawer = ({
|
|||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link to={routes.SETTINGS} onClick={onSettingsLinkClick}>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton>
|
||||||
|
<ListItemIcon>
|
||||||
|
<SettingsApplications />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Settings" />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
</Link>
|
||||||
<Link to={routes.ABOUT} onClick={onAboutLinkClick}>
|
<Link to={routes.ABOUT} onClick={onAboutLinkClick}>
|
||||||
<ListItem disablePadding>
|
<ListItem disablePadding>
|
||||||
<ListItemButton>
|
<ListItemButton>
|
||||||
|
@ -107,6 +107,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
setIsDrawerOpen(false)
|
setIsDrawerOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSettingsLinkClick = () => {
|
||||||
|
setIsDrawerOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShellContext.Provider value={shellContextValue}>
|
<ShellContext.Provider value={shellContextValue}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
@ -135,9 +139,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
/>
|
/>
|
||||||
<Drawer
|
<Drawer
|
||||||
isDrawerOpen={isDrawerOpen}
|
isDrawerOpen={isDrawerOpen}
|
||||||
|
onAboutLinkClick={handleAboutLinkClick}
|
||||||
onDrawerClose={handleDrawerClose}
|
onDrawerClose={handleDrawerClose}
|
||||||
onHomeLinkClick={handleHomeLinkClick}
|
onHomeLinkClick={handleHomeLinkClick}
|
||||||
onAboutLinkClick={handleAboutLinkClick}
|
onSettingsLinkClick={handleSettingsLinkClick}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
userPeerId={userPeerId}
|
userPeerId={userPeerId}
|
||||||
/>
|
/>
|
||||||
|
@ -4,4 +4,5 @@ export enum routes {
|
|||||||
INDEX_HTML = '/index.html',
|
INDEX_HTML = '/index.html',
|
||||||
PUBLIC_ROOM = '/public/:roomId',
|
PUBLIC_ROOM = '/public/:roomId',
|
||||||
ROOT = '/',
|
ROOT = '/',
|
||||||
|
SETTINGS = '/settings',
|
||||||
}
|
}
|
||||||
|
10
src/contexts/StorageContext.ts
Normal file
10
src/contexts/StorageContext.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { createContext } from 'react'
|
||||||
|
import localforage from 'localforage'
|
||||||
|
|
||||||
|
interface StorageContextProps {
|
||||||
|
getPersistedStorage: () => typeof localforage
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StorageContext = createContext<StorageContextProps>({
|
||||||
|
getPersistedStorage: () => localforage,
|
||||||
|
})
|
114
src/pages/Settings/Settings.tsx
Normal file
114
src/pages/Settings/Settings.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import { useContext, useEffect, useState } from 'react'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import Button from '@mui/material/Button'
|
||||||
|
import Typography from '@mui/material/Typography'
|
||||||
|
import Divider from '@mui/material/Divider'
|
||||||
|
|
||||||
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
|
import { StorageContext } from 'contexts/StorageContext'
|
||||||
|
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||||
|
|
||||||
|
import { ConfirmDialog } from '../../components/ConfirmDialog'
|
||||||
|
|
||||||
|
interface SettingsProps {
|
||||||
|
userId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Settings = ({ userId }: SettingsProps) => {
|
||||||
|
const { setTitle } = useContext(ShellContext)
|
||||||
|
const { getPersistedStorage } = useContext(StorageContext)
|
||||||
|
const [
|
||||||
|
isDeleteSettingsConfirmDiaglogOpen,
|
||||||
|
setIsDeleteSettingsConfirmDiaglogOpen,
|
||||||
|
] = useState(false)
|
||||||
|
|
||||||
|
const persistedStorage = getPersistedStorage()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle('Settings')
|
||||||
|
}, [setTitle])
|
||||||
|
|
||||||
|
const handleDeleteSettingsClick = () => {
|
||||||
|
setIsDeleteSettingsConfirmDiaglogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteSettingsCancel = () => {
|
||||||
|
setIsDeleteSettingsConfirmDiaglogOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteSettingsConfirm = async () => {
|
||||||
|
await persistedStorage.clear()
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className="max-w-3xl mx-auto p-4">
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={theme => ({
|
||||||
|
fontSize: theme.typography.h3.fontSize,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
mb: 2,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Data
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={theme => ({
|
||||||
|
fontSize: theme.typography.h5.fontSize,
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
mb: 1.5,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Delete all settings data
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={_theme => ({
|
||||||
|
mb: 2,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<strong>Be careful with this</strong>. This will cause your user name to
|
||||||
|
change from{' '}
|
||||||
|
<strong>
|
||||||
|
<PeerNameDisplay
|
||||||
|
sx={theme => ({
|
||||||
|
fontWeight: theme.typography.fontWeightMedium,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{userId}
|
||||||
|
</PeerNameDisplay>
|
||||||
|
</strong>{' '}
|
||||||
|
to a new, randomly-assigned name. It will also reset all of your saved
|
||||||
|
Chitchatter application preferences.
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="error"
|
||||||
|
sx={_theme => ({
|
||||||
|
mb: 2,
|
||||||
|
})}
|
||||||
|
onClick={handleDeleteSettingsClick}
|
||||||
|
>
|
||||||
|
Delete all data and restart
|
||||||
|
</Button>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isDeleteSettingsConfirmDiaglogOpen}
|
||||||
|
onCancel={handleDeleteSettingsCancel}
|
||||||
|
onConfirm={handleDeleteSettingsConfirm}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
variant="subtitle2"
|
||||||
|
sx={_theme => ({
|
||||||
|
mb: 2,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Chitchatter only stores user preferences and never message content of
|
||||||
|
any kind. This preference data is only stored locally on your device and
|
||||||
|
not a server.
|
||||||
|
</Typography>
|
||||||
|
<Divider sx={{ my: 2 }} />
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
1
src/pages/Settings/index.ts
Normal file
1
src/pages/Settings/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Settings'
|
Loading…
Reference in New Issue
Block a user