diff --git a/package.json b/package.json index 253a62c..64307d3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/RealTimeSettings.jsx b/src/components/RealTimeSettings.jsx index 85ea585..1a24ad3 100644 --- a/src/components/RealTimeSettings.jsx +++ b/src/components/RealTimeSettings.jsx @@ -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 ( - {realtimeType !== REALTIME_TYPES.DISABLED && - isPlusAccount(userProfile) ? ( - - ) : ( - - )} + { + handleRealtimeTypeChange( + null, + e.target.checked ? REALTIME_TYPES.SSE : REALTIME_TYPES.DISABLED, + ) + }} + disabled={!isPlusAccount(userProfile)} + inputProps={{ 'aria-label': 'Enable Real-time Updates' }} + /> { )} - { - 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) ? ( + + ) : ( + + )} - Get instant notifications when chores are updated + Get instant notifications when tasks are updated @@ -181,7 +180,7 @@ const RealTimeSettings = () => { 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. )} diff --git a/src/views/Settings/APITokenSettings.jsx b/src/views/Settings/APITokenSettings.jsx index 7addb98..899cc6b 100644 --- a/src/views/Settings/APITokenSettings.jsx +++ b/src/views/Settings/APITokenSettings.jsx @@ -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 && ( + + )} ) } diff --git a/src/views/Settings/Settings.jsx b/src/views/Settings/Settings.jsx index 395f76a..6e1dbc7 100644 --- a/src/views/Settings/Settings.jsx +++ b/src/views/Settings/Settings.jsx @@ -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 {userCircles.length > 0 && userCircles[0]?.userRole === 'member' && ( + + {/* Modals */} + {confirmModalConfig?.isOpen && ( + + )} ) }