Update maximum file size for caching in service worker to 6MB

This commit is contained in:
Mo Tarbin 2024-12-26 18:54:23 -05:00
parent bcd32a8616
commit 8f93cc5e62
3 changed files with 353 additions and 318 deletions

View file

@ -1,55 +1,52 @@
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { API_URL } from '../Config' import { API_URL } from '../Config'
import { login, RefreshToken } from './Fetcher' import { RefreshToken } from './Fetcher'
import { Preferences } from '@capacitor/preferences' import { Preferences } from '@capacitor/preferences'
class ApiManager { class ApiManager {
constructor(){ constructor() {
this.customServerURL = API_URL this.customServerURL = API_URL
this.initialized = false this.initialized = false
} }
async init(){ async init() {
if(this.initialized){ if (this.initialized) {
return return
} }
const { value: serverURL } = await Preferences.get({ key: 'customServerUrl' }); const { value: serverURL } = await Preferences.get({
key: 'customServerUrl',
})
this.customServerURL = serverURL || this.apiURL this.customServerURL = serverURL || this.apiURL
this.initialized = true this.initialized = true
} }
getApiURL(){ getApiURL() {
return this.customServerURL return this.customServerURL
} }
updateApiURL(url){ updateApiURL(url) {
this.customServerURL = url this.customServerURL = url
} }
} }
export const apiManager = new ApiManager()
export const apiManager = new ApiManager();
export function Fetch(url, options) { export function Fetch(url, options) {
if (!isTokenValid()) { if (!isTokenValid()) {
// store current location in cookie // store current location in cookie
Cookies.set('ca_redirect', window.location.pathname); Cookies.set('ca_redirect', window.location.pathname)
// Assuming you have a function isTokenValid() that checks token validity // Assuming you have a function isTokenValid() that checks token validity
window.location.href = '/login'; // Redirect to login page window.location.href = '/login' // Redirect to login page
// return Promise.reject("Token is not valid"); // return Promise.reject("Token is not valid");
} }
if (!options) { if (!options) {
options = {}; options = {}
} }
options.headers = { ...options.headers, ...HEADERS() }; options.headers = { ...options.headers, ...HEADERS() }
const baseURL = apiManager.getApiURL(); const baseURL = apiManager.getApiURL()
const fullURL = `${baseURL}${url}`; const fullURL = `${baseURL}${url}`
return fetch(fullURL, options); return fetch(fullURL, options)
} }
export const HEADERS = () => { export const HEADERS = () => {
@ -81,8 +78,7 @@ export const isTokenValid = () => {
} }
export const refreshAccessToken = () => { export const refreshAccessToken = () => {
RefreshToken() RefreshToken().then(res => {
.then(res => {
if (res.status === 200) { if (res.status === 200) {
res.json().then(data => { res.json().then(data => {
localStorage.setItem('ca_token', data.token) localStorage.setItem('ca_token', data.token)

View file

@ -1,13 +1,30 @@
import { Box, Button, Card, Checkbox, Divider, FormControl, FormHelperText, FormLabel, IconButton, Input, List, ListItem, Option, Select, Snackbar, Switch, Typography } from '@mui/joy' import { Capacitor } from '@capacitor/core'
import { LocalNotifications } from '@capacitor/local-notifications'
import { Preferences } from '@capacitor/preferences'
import { Close } from '@mui/icons-material'
import {
Box,
Button,
Card,
Divider,
FormControl,
FormHelperText,
FormLabel,
IconButton,
Input,
Option,
Select,
Snackbar,
Switch,
Typography,
} from '@mui/joy'
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import { UserContext } from '../../contexts/UserContext' import { UserContext } from '../../contexts/UserContext'
import { GetUserProfile, UpdateUserDetails } from '../../utils/Fetcher' import {
import { Capacitor } from '@capacitor/core' GetUserProfile,
import { Preferences } from '@capacitor/preferences' UpdateNotificationTarget,
import { LocalNotifications } from '@capacitor/local-notifications' UpdateUserDetails,
import { Close } from '@mui/icons-material' } from '../../utils/Fetcher'
import { PushNotifications } from '@capacitor/push-notifications'
import { UpdateNotificationTarget } from '../../utils/Fetcher'
const NotificationSetting = () => { const NotificationSetting = () => {
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false) const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
@ -23,50 +40,53 @@ const NotificationSetting = () => {
} }
}, []) }, [])
const getNotificationPreferences = async () => { const getNotificationPreferences = async () => {
const ret = await Preferences.get({ key: 'notificationPreferences' }); const ret = await Preferences.get({ key: 'notificationPreferences' })
return JSON.parse(ret.value); return JSON.parse(ret.value)
}; }
const setNotificationPreferences = async (value) => { const setNotificationPreferences = async value => {
if (value.granted === false){ if (value.granted === false) {
await Preferences.set({ key: 'notificationPreferences', value: JSON.stringify({ granted: false }) }); await Preferences.set({
key: 'notificationPreferences',
value: JSON.stringify({ granted: false }),
})
return return
} }
const currentSettings = await getNotificationPreferences(); const currentSettings = await getNotificationPreferences()
await Preferences.set({ key: 'notificationPreferences', value: JSON.stringify({ ...currentSettings, ...value }) }); await Preferences.set({
}; key: 'notificationPreferences',
value: JSON.stringify({ ...currentSettings, ...value }),
})
}
const getPushNotificationPreferences = async () => { const getPushNotificationPreferences = async () => {
const ret = await Preferences.get({ key: 'pushNotificationPreferences' }); const ret = await Preferences.get({ key: 'pushNotificationPreferences' })
return JSON.parse(ret.value); return JSON.parse(ret.value)
}; }
const setPushNotificationPreferences = async (value) => { const setPushNotificationPreferences = async value => {
await Preferences.set({ key: 'pushNotificationPreferences', value: JSON.stringify(value) }); await Preferences.set({
}; key: 'pushNotificationPreferences',
value: JSON.stringify(value),
})
}
const [deviceNotification, setDeviceNotification] = useState( const [deviceNotification, setDeviceNotification] = useState(false)
false
)
const [dueNotification, setDueNotification] = useState(true) const [dueNotification, setDueNotification] = useState(true)
const [preDueNotification, setPreDueNotification] = useState(false) const [preDueNotification, setPreDueNotification] = useState(false)
const [naggingNotification, setNaggingNotification] = useState(false) const [naggingNotification, setNaggingNotification] = useState(false)
const [pushNotification, setPushNotification] = useState( const [pushNotification, setPushNotification] = useState(false)
false
)
useEffect(() => { useEffect(() => {
getNotificationPreferences().then((resp) => { getNotificationPreferences().then(resp => {
setDeviceNotification(resp.granted) setDeviceNotification(resp.granted)
setDueNotification(resp.dueNotification) setDueNotification(resp.dueNotification)
setPreDueNotification(resp.preDueNotification) setPreDueNotification(resp.preDueNotification)
setNaggingNotification(resp.naggingNotification) setNaggingNotification(resp.naggingNotification)
} })
) getPushNotificationPreferences().then(resp => {
getPushNotificationPreferences().then((resp) => {
setPushNotification(resp.granted) setPushNotification(resp.granted)
} })
)
}, []) }, [])
const [notificationTarget, setNotificationTarget] = useState( const [notificationTarget, setNotificationTarget] = useState(
@ -126,100 +146,104 @@ const NotificationSetting = () => {
<Typography level='body-md'>Manage your Device Notificaiton</Typography> <Typography level='body-md'>Manage your Device Notificaiton</Typography>
<FormControl <FormControl
orientation="horizontal" orientation='horizontal'
sx={{ width: 400, justifyContent: 'space-between' }} sx={{ width: 400, justifyContent: 'space-between' }}
> >
<div> <div>
<FormLabel>Device Notification</FormLabel> <FormLabel>Device Notification</FormLabel>
<FormHelperText sx={{ mt: 0 }}>{Capacitor.isNativePlatform()? 'Receive notification on your device when a task is due' : 'This feature is only available on mobile devices'} </FormHelperText> <FormHelperText sx={{ mt: 0 }}>
</div> {Capacitor.isNativePlatform()
<Switch ? 'Receive notification on your device when a task is due'
disabled={!Capacitor.isNativePlatform()} : 'This feature is only available on mobile devices'}{' '}
checked={deviceNotification} </FormHelperText>
onClick={(event) =>{ </div>
event.preventDefault() <Switch
if (deviceNotification === false){ disabled={!Capacitor.isNativePlatform()}
LocalNotifications.requestPermissions().then((resp) => { checked={deviceNotification}
if (resp.display === 'granted') { onClick={event => {
event.preventDefault()
setDeviceNotification(true) if (deviceNotification === false) {
setNotificationPreferences({granted: true}) LocalNotifications.requestPermissions().then(resp => {
} if (resp.display === 'granted') {
else if (resp.display === 'denied') { setDeviceNotification(true)
setIsSnackbarOpen(true) setNotificationPreferences({ granted: true })
setDeviceNotification(false) } else if (resp.display === 'denied') {
setNotificationPreferences({granted: false}) setIsSnackbarOpen(true)
} setDeviceNotification(false)
}) setNotificationPreferences({ granted: false })
} }
else{ })
setDeviceNotification(false) } else {
} setDeviceNotification(false)
} }
} }}
color={deviceNotification ? 'success' : 'neutral'} color={deviceNotification ? 'success' : 'neutral'}
variant={deviceNotification ? 'solid' : 'outlined'} variant={deviceNotification ? 'solid' : 'outlined'}
endDecorator={deviceNotification ? 'On' : 'Off'} endDecorator={deviceNotification ? 'On' : 'Off'}
slotProps={{ slotProps={{
endDecorator: { endDecorator: {
sx: { sx: {
minWidth: 24, minWidth: 24,
},
}, },
}, }}
}} />
/> </FormControl>
</FormControl> {deviceNotification && (
{deviceNotification && ( <Card>
<Card> {[
{[ {
{ title: 'Due Date Notification',
'title': 'Due Date Notification', checked: dueNotification,
'checked': dueNotification, set: setDueNotification,
'set': setDueNotification, label: 'Notification when the task is due',
'label': 'Notification when the task is due', property: 'dueNotification',
'property': 'dueNotification', disabled: false,
'disabled': false },
}, {
{ title: 'Pre-Due Date Notification',
'title': 'Pre-Due Date Notification', checked: preDueNotification,
'checked': preDueNotification, set: setPreDueNotification,
'set': setPreDueNotification, label: 'Notification a few hours before the task is due',
'label': 'Notification a few hours before the task is due', property: 'preDueNotification',
'property': 'preDueNotification', disabled: true,
'disabled': true },
}, {
{ title: 'Overdue Notification',
'title': 'Overdue Notification', checked: naggingNotification,
'checked': naggingNotification, set: setNaggingNotification,
'set': setNaggingNotification, label: 'Notification when the task is overdue',
'label': 'Notification when the task is overdue', property: 'naggingNotification',
'property': 'naggingNotification', disabled: true,
'disabled': true },
} ].map(item => (
] <FormControl
.map(item => ( key={item.property}
<FormControl orientation='horizontal'
orientation="horizontal" sx={{ width: 385, justifyContent: 'space-between' }}
sx={{ width: 385, justifyContent: 'space-between' }} >
> <div>
<div> <FormLabel>{item.title}</FormLabel>
<FormLabel>{item.title}</FormLabel> <FormHelperText sx={{ mt: 0 }}>{item.label} </FormHelperText>
<FormHelperText sx={{ mt: 0 }}>{item.label} </FormHelperText> </div>
</div>
<Switch checked={item.checked} <Switch
disabled={item.disabled} checked={item.checked}
onClick={() =>{ disabled={item.disabled}
setNotificationPreferences({[item.property]: !item.checked}) onClick={() => {
item.set(!item.checked) setNotificationPreferences({ [item.property]: !item.checked })
}} item.set(!item.checked)
color={item.checked ? 'success' : ''} }}
variant='solid' endDecorator={item.checked ? 'On' : 'Off'} slotProps={{ endDecorator: { sx: { minWidth: 24 } } }} /> color={item.checked ? 'success' : ''}
</FormControl> variant='solid'
))} endDecorator={item.checked ? 'On' : 'Off'}
</Card> slotProps={{ endDecorator: { sx: { minWidth: 24 } } }}
)} />
{/* <FormControl </FormControl>
))}
</Card>
)}
{/* <FormControl
orientation="horizontal" orientation="horizontal"
sx={{ width: 400, justifyContent: 'space-between' }} sx={{ width: 400, justifyContent: 'space-between' }}
> >
@ -267,180 +291,194 @@ const NotificationSetting = () => {
/> />
</FormControl> */} </FormControl> */}
<Button <Button
variant='soft' variant='soft'
color='primary' color='primary'
sx={{ sx={{
width: '210px', width: '210px',
mb: 1, mb: 1,
}} }}
onClick={() => {
onClick={() => { // schedule a local notification in 5 seconds
// schedule a local notification in 5 seconds LocalNotifications.schedule({
LocalNotifications.schedule({ notifications: [
notifications: [ {
{ title: 'Task Reminder',
title: 'Task Reminder', body: 'You have a task due soon',
body: 'You have a task due soon', id: 1,
id: 1, schedule: { at: new Date(Date.now() + 3000) },
schedule: { at: new Date(Date.now() + 3000) }, sound: null,
sound: null, attachments: null,
attachments: null, actionTypeId: '',
actionTypeId: '', extra: null,
extra: null, },
}, ],
], })
}); }}
} >
}>Test Notification </Button> Test Notification{' '}
</Button>
<Typography level='h3'>Custom Notification</Typography> <Typography level='h3'>Custom Notification</Typography>
<Divider /> <Divider />
<Typography level='body-md'>Notificaiton through other platform like Telegram or Pushover</Typography>
<FormControl
orientation="horizontal"
sx={{ width: 400, justifyContent: 'space-between' }}
>
<div>
<FormLabel>Custom Notification</FormLabel>
<FormHelperText sx={{ mt: 0 }}>Receive notification on other platform</FormHelperText>
</div>
<Switch
checked={chatID !== 0}
onClick={(event) =>{
event.preventDefault()
if (chatID !== 0){
setChatID(0)
}
else{
setChatID('')
UpdateUserDetails({
chatID: Number(0),
}).then(resp => {
resp.json().then(data => {
setUserProfile(data)
})
})
}
setNotificationTarget('0')
handleSave()
}
}
color={chatID!==0 ? 'success' : 'neutral'}
variant={chatID!==0 ? 'solid' : 'outlined'}
endDecorator={chatID!==0 ? 'On' : 'Off'}
slotProps={{
endDecorator: {
sx: {
minWidth: 24,
},
},
}}
/>
</FormControl>
{chatID !== 0&& (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Select
value={notificationTarget}
sx={{ maxWidth: '200px' }}
onChange={(e, selected) => setNotificationTarget(selected)}
>
<Option value='0'>None</Option>
<Option value='1'>Telegram</Option>
<Option value='2'>Pushover</Option>
</Select>
{notificationTarget === '1' && (
<>
<Typography level='body-xs'>
You need to initiate a message to the bot in order for the Telegram
notification to work{' '}
<a
style={{
textDecoration: 'underline',
color: '#0891b2',
}}
href='https://t.me/DonetickBot'
>
Click here
</a>{' '}
to start a chat
</Typography>
<Typography level='body-sm'>Chat ID</Typography>
<Input
value={chatID}
onChange={e => setChatID(e.target.value)}
placeholder='User ID / Chat ID'
sx={{
width: '200px',
}}
/>
<Typography mt={0} level='body-xs'>
If you don't know your Chat ID, start chat with userinfobot and it
will send you your Chat ID.{' '}
<a
style={{
textDecoration: 'underline',
color: '#0891b2',
}}
href='https://t.me/userinfobot'
>
Click here
</a>{' '}
to start chat with userinfobot{' '}
</Typography>
</>
)}
{notificationTarget === '2' && (
<>
<Typography level='body-sm'>User key</Typography>
<Input
value={chatID}
onChange={e => setChatID(e.target.value)}
placeholder='User ID'
sx={{
width: '200px',
}}
/>
</>
)}
{error && (
<Typography color='warning' level='body-sm'>
{error}
</Typography>
)}
<Button
sx={{
width: '110px',
mb: 1,
}}
onClick={handleSave}
>
Save
</Button>
</Box>
)}
<Snackbar open={isSnackbarOpen} autoHideDuration={8000} onClose={() => setIsSnackbarOpen(false)} endDecorator={<IconButton size='md' onClick={() => setIsSnackbarOpen(false)}><Close/></IconButton>}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<Typography level='title-md'>Permission Denied</Typography>
<Typography level='body-md'> <Typography level='body-md'>
You have denied the permission to receive notification on this device. Please enable it in your device settings Notificaiton through other platform like Telegram or Pushover
</Typography> </Typography>
</div>
</Snackbar> <FormControl
orientation='horizontal'
sx={{ width: 400, justifyContent: 'space-between' }}
>
<div>
<FormLabel>Custom Notification</FormLabel>
<FormHelperText sx={{ mt: 0 }}>
Receive notification on other platform
</FormHelperText>
</div>
<Switch
checked={chatID !== 0}
onClick={event => {
event.preventDefault()
if (chatID !== 0) {
setChatID(0)
} else {
setChatID('')
UpdateUserDetails({
chatID: Number(0),
}).then(resp => {
resp.json().then(data => {
setUserProfile(data)
})
})
}
setNotificationTarget('0')
handleSave()
}}
color={chatID !== 0 ? 'success' : 'neutral'}
variant={chatID !== 0 ? 'solid' : 'outlined'}
endDecorator={chatID !== 0 ? 'On' : 'Off'}
slotProps={{
endDecorator: {
sx: {
minWidth: 24,
},
},
}}
/>
</FormControl>
{chatID !== 0 && (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
}}
>
<Select
value={notificationTarget}
sx={{ maxWidth: '200px' }}
onChange={(e, selected) => setNotificationTarget(selected)}
>
<Option value='0'>None</Option>
<Option value='1'>Telegram</Option>
<Option value='2'>Pushover</Option>
</Select>
{notificationTarget === '1' && (
<>
<Typography level='body-xs'>
You need to initiate a message to the bot in order for the
Telegram notification to work{' '}
<a
style={{
textDecoration: 'underline',
color: '#0891b2',
}}
href='https://t.me/DonetickBot'
>
Click here
</a>{' '}
to start a chat
</Typography>
<Typography level='body-sm'>Chat ID</Typography>
<Input
value={chatID}
onChange={e => setChatID(e.target.value)}
placeholder='User ID / Chat ID'
sx={{
width: '200px',
}}
/>
<Typography mt={0} level='body-xs'>
If you don't know your Chat ID, start chat with userinfobot and
it will send you your Chat ID.{' '}
<a
style={{
textDecoration: 'underline',
color: '#0891b2',
}}
href='https://t.me/userinfobot'
>
Click here
</a>{' '}
to start chat with userinfobot{' '}
</Typography>
</>
)}
{notificationTarget === '2' && (
<>
<Typography level='body-sm'>User key</Typography>
<Input
value={chatID}
onChange={e => setChatID(e.target.value)}
placeholder='User ID'
sx={{
width: '200px',
}}
/>
</>
)}
{error && (
<Typography color='warning' level='body-sm'>
{error}
</Typography>
)}
<Button
sx={{
width: '110px',
mb: 1,
}}
onClick={handleSave}
>
Save
</Button>
</Box>
)}
<Snackbar
open={isSnackbarOpen}
autoHideDuration={8000}
onClose={() => setIsSnackbarOpen(false)}
endDecorator={
<IconButton size='md' onClick={() => setIsSnackbarOpen(false)}>
<Close />
</IconButton>
}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography level='title-md'>Permission Denied</Typography>
<Typography level='body-md'>
You have denied the permission to receive notification on this
device. Please enable it in your device settings
</Typography>
</div>
</Snackbar>
</div> </div>
) )
} }

View file

@ -61,6 +61,7 @@ export default defineConfig({
workbox: { workbox: {
skipWaiting: true, // Force the waiting service worker to become the active service worker skipWaiting: true, // Force the waiting service worker to become the active service worker
clientsClaim: true, // Take control of uncontrolled clients as soon as the service worker becomes active clientsClaim: true, // Take control of uncontrolled clients as soon as the service worker becomes active
maximumFileSizeToCacheInBytes: 6000000, // 6MB
}, },
}), }),
], ],