Add confirmation modals for token removal and storage clearing actions
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
"chrono-node": "^2.7.7",
|
||||
"dotenv": "^16.4.5",
|
||||
"esm": "^3.2.25",
|
||||
"event-source-polyfill": "^1.0.31",
|
||||
"farmhash": "^4.0.1",
|
||||
"fuse.js": "^7.0.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
|
||||
@@ -63,15 +63,15 @@ const RealTimeSettings = () => {
|
||||
|
||||
const getStatusDescription = () => {
|
||||
if (!isPlusAccount(userProfile)) {
|
||||
return 'Real-time updates are not available in the Basic plan. Upgrade to Plus to receive instant notifications when chores are updated.'
|
||||
return 'Real-time updates are not available in the Basic plan. Upgrade to Plus to receive instant notifications when tasks are updated.'
|
||||
}
|
||||
|
||||
if (realtimeType === REALTIME_TYPES.DISABLED) {
|
||||
return 'Real-time updates are disabled. Enable them to see live changes when you or other circle members complete, skip, or modify chores.'
|
||||
return 'Real-time updates are disabled. Enable them to see live changes when you or other circle members complete, skip, or modify tasks.'
|
||||
}
|
||||
|
||||
if (context.isConnected) {
|
||||
return "Real-time updates are working. You'll see live changes when you or other circle members complete, skip, or modify chores."
|
||||
return "Real-time updates are working. You'll see live changes when you or other circle members complete, skip, or modify tasks."
|
||||
}
|
||||
|
||||
if (context.isConnecting) {
|
||||
@@ -97,12 +97,18 @@ const RealTimeSettings = () => {
|
||||
return (
|
||||
<Card sx={{ mt: 2, p: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2, mb: 2 }}>
|
||||
{realtimeType !== REALTIME_TYPES.DISABLED &&
|
||||
isPlusAccount(userProfile) ? (
|
||||
<Sync color={context.isConnected ? 'success' : 'disabled'} />
|
||||
) : (
|
||||
<SyncDisabled color='disabled' />
|
||||
)}
|
||||
<Switch
|
||||
color='success'
|
||||
checked={realtimeType !== REALTIME_TYPES.DISABLED}
|
||||
onChange={e => {
|
||||
handleRealtimeTypeChange(
|
||||
null,
|
||||
e.target.checked ? REALTIME_TYPES.SSE : REALTIME_TYPES.DISABLED,
|
||||
)
|
||||
}}
|
||||
disabled={!isPlusAccount(userProfile)}
|
||||
inputProps={{ 'aria-label': 'Enable Real-time Updates' }}
|
||||
/>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -121,22 +127,15 @@ const RealTimeSettings = () => {
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
<Switch
|
||||
checked={realtimeType !== REALTIME_TYPES.DISABLED}
|
||||
onChange={e => {
|
||||
handleRealtimeTypeChange(
|
||||
null,
|
||||
e.target.checked
|
||||
? REALTIME_TYPES.SSE
|
||||
: REALTIME_TYPES.DISABLED,
|
||||
)
|
||||
}}
|
||||
disabled={!isPlusAccount(userProfile)}
|
||||
inputProps={{ 'aria-label': 'Enable Real-time Updates' }}
|
||||
/>
|
||||
{realtimeType !== REALTIME_TYPES.DISABLED &&
|
||||
isPlusAccount(userProfile) ? (
|
||||
<Sync color={context.isConnected ? 'success' : 'disabled'} />
|
||||
) : (
|
||||
<SyncDisabled color='disabled' />
|
||||
)}
|
||||
</Box>
|
||||
<Typography level='body-sm' color='neutral'>
|
||||
Get instant notifications when chores are updated
|
||||
Get instant notifications when tasks are updated
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -181,7 +180,7 @@ const RealTimeSettings = () => {
|
||||
<Typography level='body-sm' color='warning' sx={{ mt: 1 }}>
|
||||
Real-time updates are not available in the Basic plan. Upgrade to Plus
|
||||
to receive instant notifications when you or other circle members
|
||||
complete, skip, or modify chores.
|
||||
complete, skip, or modify tasks.
|
||||
</Typography>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -13,19 +13,47 @@ import moment from 'moment'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { useNotification } from '../../service/NotificationProvider'
|
||||
import {
|
||||
CreateLongLiveToken,
|
||||
DeleteLongLiveToken,
|
||||
GetLongLiveTokens,
|
||||
} from '../../utils/Fetcher'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||
import TextModal from '../Modals/Inputs/TextModal'
|
||||
|
||||
const APITokenSettings = () => {
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const { showNotification } = useNotification()
|
||||
const [tokens, setTokens] = useState([])
|
||||
const [isGetTokenNameModalOpen, setIsGetTokenNameModalOpen] = useState(false)
|
||||
const [showTokenId, setShowTokenId] = useState(null)
|
||||
const [confirmModalConfig, setConfirmModalConfig] = useState({})
|
||||
|
||||
const showConfirmation = (
|
||||
message,
|
||||
title,
|
||||
onConfirm,
|
||||
confirmText = 'Confirm',
|
||||
cancelText = 'Cancel',
|
||||
color = 'primary',
|
||||
) => {
|
||||
setConfirmModalConfig({
|
||||
isOpen: true,
|
||||
message,
|
||||
title,
|
||||
confirmText,
|
||||
cancelText,
|
||||
color,
|
||||
onClose: isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
onConfirm()
|
||||
}
|
||||
setConfirmModalConfig({})
|
||||
},
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
GetLongLiveTokens().then(resp => {
|
||||
resp.json().then(data => {
|
||||
@@ -100,18 +128,28 @@ const APITokenSettings = () => {
|
||||
variant='outlined'
|
||||
color='danger'
|
||||
onClick={() => {
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to remove ${token.name} ?`,
|
||||
showConfirmation(
|
||||
`Are you sure you want to remove ${token.name}?`,
|
||||
'Remove Token',
|
||||
() => {
|
||||
DeleteLongLiveToken(token.id).then(resp => {
|
||||
if (resp.ok) {
|
||||
showNotification({
|
||||
type: 'success',
|
||||
title: 'Removed',
|
||||
message: 'API token has been removed',
|
||||
})
|
||||
const newTokens = tokens.filter(
|
||||
t => t.id !== token.id,
|
||||
)
|
||||
setTokens(newTokens)
|
||||
}
|
||||
})
|
||||
},
|
||||
'Remove',
|
||||
'Cancel',
|
||||
'danger',
|
||||
)
|
||||
if (confirmed) {
|
||||
DeleteLongLiveToken(token.id).then(resp => {
|
||||
if (resp.ok) {
|
||||
alert('Token removed')
|
||||
const newTokens = tokens.filter(t => t.id !== token.id)
|
||||
setTokens(newTokens)
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -130,7 +168,10 @@ const APITokenSettings = () => {
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(token.token)
|
||||
alert('Token copied to clipboard')
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Token copied to clipboard',
|
||||
})
|
||||
setShowTokenId(null)
|
||||
}}
|
||||
>
|
||||
@@ -166,6 +207,11 @@ const APITokenSettings = () => {
|
||||
okText={'Generate Token'}
|
||||
onSave={handleSaveToken}
|
||||
/>
|
||||
|
||||
{/* Modals */}
|
||||
{confirmModalConfig?.isOpen && (
|
||||
<ConfirmationModal config={confirmModalConfig} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
UpdatePassword,
|
||||
} from '../../utils/Fetcher'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||
import PassowrdChangeModal from '../Modals/Inputs/PasswordChangeModal'
|
||||
import APITokenSettings from './APITokenSettings'
|
||||
import MFASettings from './MFASettings'
|
||||
@@ -41,9 +42,11 @@ import NotificationSetting from './NotificationSetting'
|
||||
import ProfileSettings from './ProfileSettings'
|
||||
import StorageSettings from './StorageSettings'
|
||||
import ThemeToggle from './ThemeToggle'
|
||||
import { useNotification } from '../../service/NotificationProvider'
|
||||
|
||||
const Settings = () => {
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const { showNotification } = useNotification()
|
||||
|
||||
const [userCircles, setUserCircles] = useState([])
|
||||
const [circleMemberRequests, setCircleMemberRequests] = useState([])
|
||||
@@ -54,6 +57,31 @@ const Settings = () => {
|
||||
const [isAdmin, setIsAdmin] = useState(false)
|
||||
|
||||
const [changePasswordModal, setChangePasswordModal] = useState(false)
|
||||
const [confirmModalConfig, setConfirmModalConfig] = useState({})
|
||||
|
||||
const showConfirmation = (
|
||||
message,
|
||||
title,
|
||||
onConfirm,
|
||||
confirmText = 'Confirm',
|
||||
cancelText = 'Cancel',
|
||||
color = 'primary',
|
||||
) => {
|
||||
setConfirmModalConfig({
|
||||
isOpen: true,
|
||||
message,
|
||||
title,
|
||||
confirmText,
|
||||
cancelText,
|
||||
color,
|
||||
onClose: isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
onConfirm()
|
||||
}
|
||||
setConfirmModalConfig({})
|
||||
},
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
GetUserCircle().then(resp => {
|
||||
resp.json().then(data => {
|
||||
@@ -165,7 +193,10 @@ const Settings = () => {
|
||||
variant='soft'
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(userCircles[0]?.invite_code)
|
||||
alert('Code Copied to clipboard')
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Code copied to clipboard',
|
||||
})
|
||||
}}
|
||||
>
|
||||
Copy Code
|
||||
@@ -180,27 +211,42 @@ const Settings = () => {
|
||||
window.location.host +
|
||||
`/circle/join?code=${userCircles[0]?.invite_code}`,
|
||||
)
|
||||
alert('Link Copied to clipboard')
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Link copied to clipboard',
|
||||
})
|
||||
}}
|
||||
>
|
||||
Copy Link
|
||||
</Button>
|
||||
{userCircles.length > 0 && userCircles[0]?.userRole === 'member' && (
|
||||
<Button
|
||||
color='danger'
|
||||
variant='outlined'
|
||||
sx={{ ml: 1 }}
|
||||
onClick={() => {
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to leave your circle?`,
|
||||
showConfirmation(
|
||||
'Are you sure you want to leave your circle?',
|
||||
'Leave Circle',
|
||||
() => {
|
||||
LeaveCircle(userCircles[0]?.id).then(resp => {
|
||||
if (resp.ok) {
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Left circle successfully',
|
||||
})
|
||||
} else {
|
||||
showNotification({
|
||||
type: 'error',
|
||||
message: 'Failed to leave circle',
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
'Leave',
|
||||
'Cancel',
|
||||
'danger',
|
||||
)
|
||||
if (confirmed) {
|
||||
LeaveCircle(userCircles[0]?.id).then(resp => {
|
||||
if (resp.ok) {
|
||||
alert('Left circle successfully.')
|
||||
} else {
|
||||
alert('Failed to leave circle.')
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
Leave Circle
|
||||
@@ -257,7 +303,10 @@ const Settings = () => {
|
||||
})
|
||||
setCircleMembers(newCircleMembers)
|
||||
} else {
|
||||
alert('Failed to update role')
|
||||
showNotification({
|
||||
type: 'error',
|
||||
message: 'Failed to update role',
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
@@ -318,19 +367,26 @@ const Settings = () => {
|
||||
color='danger'
|
||||
size='sm'
|
||||
onClick={() => {
|
||||
const confirmed = confirm(
|
||||
showConfirmation(
|
||||
`Are you sure you want to remove ${member.displayName} from your circle?`,
|
||||
'Remove Member',
|
||||
() => {
|
||||
DeleteCircleMember(
|
||||
member.circleId,
|
||||
member.userId,
|
||||
).then(resp => {
|
||||
if (resp.ok) {
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Removed member successfully',
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
'Remove',
|
||||
'Cancel',
|
||||
'danger',
|
||||
)
|
||||
if (confirmed) {
|
||||
DeleteCircleMember(
|
||||
member.circleId,
|
||||
member.userId,
|
||||
).then(resp => {
|
||||
if (resp.ok) {
|
||||
alert('Removed member successfully.')
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -353,18 +409,24 @@ const Settings = () => {
|
||||
variant='soft'
|
||||
color='success'
|
||||
onClick={() => {
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to accept ${request.displayName}(username:${request.username}) to join your circle?`,
|
||||
showConfirmation(
|
||||
`Are you sure you want to accept ${request.displayName} (username: ${request.username}) to join your circle?`,
|
||||
'Accept Member Request',
|
||||
() => {
|
||||
AcceptCircleMemberRequest(request.id).then(resp => {
|
||||
if (resp.ok) {
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Accepted request successfully',
|
||||
})
|
||||
// reload the page
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
},
|
||||
'Accept',
|
||||
'Cancel',
|
||||
)
|
||||
if (confirmed) {
|
||||
AcceptCircleMemberRequest(request.id).then(resp => {
|
||||
if (resp.ok) {
|
||||
alert('Accepted request successfully.')
|
||||
// reload the page
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
Accept
|
||||
@@ -393,18 +455,23 @@ const Settings = () => {
|
||||
<Button
|
||||
variant='soft'
|
||||
onClick={() => {
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to leave you circle and join '${circleInviteCode}'?`,
|
||||
showConfirmation(
|
||||
`Are you sure you want to leave your circle and join '${circleInviteCode}'?`,
|
||||
'Join Circle',
|
||||
() => {
|
||||
JoinCircle(circleInviteCode).then(resp => {
|
||||
if (resp.ok) {
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message:
|
||||
'Joined circle successfully, wait for the circle owner to accept your request.',
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
'Join',
|
||||
'Cancel',
|
||||
)
|
||||
if (confirmed) {
|
||||
JoinCircle(circleInviteCode).then(resp => {
|
||||
if (resp.ok) {
|
||||
alert(
|
||||
'Joined circle successfully, wait for the circle owner to accept your request.',
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
Join Circle
|
||||
@@ -479,9 +546,15 @@ const Settings = () => {
|
||||
onClick={() => {
|
||||
PutWebhookURL(webhookURL).then(resp => {
|
||||
if (resp.ok) {
|
||||
alert('Webhook URL updated successfully.')
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Webhook URL updated successfully',
|
||||
})
|
||||
} else {
|
||||
alert('Failed to update webhook URL.')
|
||||
showNotification({
|
||||
type: 'error',
|
||||
message: 'Failed to update webhook URL',
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
@@ -541,10 +614,14 @@ const Settings = () => {
|
||||
ml: 1,
|
||||
}}
|
||||
variant='outlined'
|
||||
color='danger'
|
||||
onClick={() => {
|
||||
CancelSubscription().then(resp => {
|
||||
if (resp.ok) {
|
||||
alert('Subscription cancelled.')
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Subscription cancelled',
|
||||
})
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
@@ -575,9 +652,15 @@ const Settings = () => {
|
||||
if (password) {
|
||||
UpdatePassword(password).then(resp => {
|
||||
if (resp.ok) {
|
||||
alert('Password changed successfully')
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message: 'Password changed successfully',
|
||||
})
|
||||
} else {
|
||||
alert('Password change failed')
|
||||
showNotification({
|
||||
type: 'error',
|
||||
message: 'Password change failed',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -601,6 +684,11 @@ const Settings = () => {
|
||||
</Typography>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
{/* Modals */}
|
||||
{confirmModalConfig?.isOpen && (
|
||||
<ConfirmationModal config={confirmModalConfig} />
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,12 +12,38 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import { GetStorageUsage } from '../../utils/Fetcher'
|
||||
import { isPlusAccount } from '../../utils/Helpers'
|
||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||
|
||||
const StorageSettings = () => {
|
||||
const Navigate = useNavigate()
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const [usage, setUsage] = useState({ used: 0, total: 0 })
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [confirmModalConfig, setConfirmModalConfig] = useState({})
|
||||
|
||||
const showConfirmation = (
|
||||
message,
|
||||
title,
|
||||
onConfirm,
|
||||
confirmText = 'Confirm',
|
||||
cancelText = 'Cancel',
|
||||
color = 'primary',
|
||||
) => {
|
||||
setConfirmModalConfig({
|
||||
isOpen: true,
|
||||
message,
|
||||
title,
|
||||
confirmText,
|
||||
cancelText,
|
||||
color,
|
||||
onClose: isConfirmed => {
|
||||
if (isConfirmed) {
|
||||
onConfirm()
|
||||
}
|
||||
setConfirmModalConfig({})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlusAccount(userProfile)) {
|
||||
@@ -101,13 +127,17 @@ const StorageSettings = () => {
|
||||
variant='soft'
|
||||
color='danger'
|
||||
onClick={() => {
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to clear your local storage and cache? This will remove all your data from this browser and require login.`,
|
||||
showConfirmation(
|
||||
'Are you sure you want to clear your local storage and cache? This will remove all your data from this browser and require login.',
|
||||
'Clear All Local Storage',
|
||||
() => {
|
||||
localStorage.clear()
|
||||
Navigate('/login')
|
||||
},
|
||||
'Clear All',
|
||||
'Cancel',
|
||||
'danger',
|
||||
)
|
||||
if (confirmed) {
|
||||
localStorage.clear()
|
||||
Navigate('/login')
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear All Local Storage and Cache
|
||||
@@ -116,20 +146,29 @@ const StorageSettings = () => {
|
||||
variant='outlined'
|
||||
color='danger'
|
||||
onClick={() => {
|
||||
const confirmed = confirm(
|
||||
`Are you sure you want to clear only the offline cache and tasks?`,
|
||||
showConfirmation(
|
||||
'Are you sure you want to clear only the offline cache and tasks?',
|
||||
'Clear Offline Cache',
|
||||
() => {
|
||||
localStorage.removeItem('offline_cache')
|
||||
localStorage.removeItem('offline_request_queue')
|
||||
localStorage.removeItem('offlineTasks')
|
||||
},
|
||||
'Clear Cache',
|
||||
'Cancel',
|
||||
'danger',
|
||||
)
|
||||
if (confirmed) {
|
||||
localStorage.removeItem('offline_cache')
|
||||
localStorage.removeItem('offline_request_queue')
|
||||
localStorage.removeItem('offlineTasks')
|
||||
}
|
||||
}}
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
Clear Offline Cache and Offline Tasks
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
{/* Modals */}
|
||||
{confirmModalConfig?.isOpen && (
|
||||
<ConfirmationModal config={confirmModalConfig} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user