feat: Refactor user profile management and improve token validation
- Introduced `useUserProfile` hook to centralize user profile fetching and management. - Updated various components to utilize the new `useUserProfile` hook instead of context. - Enhanced token validation logic in `Fetch` function to prevent unnecessary redirects. - Added `parseDueDate` function to handle due date parsing with improved logic. - Cleaned up user profile state management across multiple views and settings. - Improved loading states and error handling in user-related components. - remove usercontext and just use react query for userProfile
This commit is contained in:
29
src/App.jsx
29
src/App.jsx
@@ -7,12 +7,10 @@ import { Outlet, useNavigate } from 'react-router-dom'
|
|||||||
import { useRegisterSW } from 'virtual:pwa-register/react'
|
import { useRegisterSW } from 'virtual:pwa-register/react'
|
||||||
import { registerCapacitorListeners } from './CapacitorListener'
|
import { registerCapacitorListeners } from './CapacitorListener'
|
||||||
import { ImpersonateUserProvider } from './contexts/ImpersonateUserContext'
|
import { ImpersonateUserProvider } from './contexts/ImpersonateUserContext'
|
||||||
import { UserContext } from './contexts/UserContext'
|
|
||||||
import { useResource } from './queries/ResourceQueries'
|
import { useResource } from './queries/ResourceQueries'
|
||||||
import { AuthenticationProvider } from './service/AuthenticationService'
|
import { AuthenticationProvider } from './service/AuthenticationService'
|
||||||
import { ErrorProvider } from './service/ErrorProvider'
|
import { ErrorProvider } from './service/ErrorProvider'
|
||||||
import { GetUserProfile } from './utils/Fetcher'
|
import { apiManager } from './utils/TokenManager'
|
||||||
import { apiManager, isTokenValid } from './utils/TokenManager'
|
|
||||||
import NetworkBanner from './views/components/NetworkBanner'
|
import NetworkBanner from './views/components/NetworkBanner'
|
||||||
const add = className => {
|
const add = className => {
|
||||||
document.getElementById('root').classList.add(className)
|
document.getElementById('root').classList.add(className)
|
||||||
@@ -23,15 +21,14 @@ const remove = className => {
|
|||||||
}
|
}
|
||||||
// TODO: Update the interval to at 60 minutes
|
// TODO: Update the interval to at 60 minutes
|
||||||
const intervalMS = 5 * 60 * 1000 // 5 minutes
|
const intervalMS = 5 * 60 * 1000 // 5 minutes
|
||||||
|
const queryClient = new QueryClient({})
|
||||||
function App() {
|
function App() {
|
||||||
const resource = useResource()
|
const resource = useResource()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
startApiManager(navigate)
|
startApiManager(navigate)
|
||||||
startOpenReplay()
|
startOpenReplay()
|
||||||
const queryClient = new QueryClient()
|
|
||||||
const { mode, systemMode } = useColorScheme()
|
const { mode, systemMode } = useColorScheme()
|
||||||
const [userProfile, setUserProfile] = useState(null)
|
|
||||||
const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(true)
|
const [showUpdateSnackbar, setShowUpdateSnackbar] = useState(true)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -72,23 +69,12 @@ function App() {
|
|||||||
|
|
||||||
return remove('dark')
|
return remove('dark')
|
||||||
}
|
}
|
||||||
const getUserProfile = () => {
|
|
||||||
GetUserProfile()
|
|
||||||
.then(res => {
|
|
||||||
res.json().then(data => {
|
|
||||||
setUserProfile(data.res)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(error => {})
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setThemeClass()
|
setThemeClass()
|
||||||
}, [mode, systemMode])
|
}, [mode, systemMode])
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
registerCapacitorListeners()
|
registerCapacitorListeners()
|
||||||
if (isTokenValid()) {
|
|
||||||
if (!userProfile) getUserProfile()
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -99,10 +85,8 @@ function App() {
|
|||||||
<AuthenticationProvider />
|
<AuthenticationProvider />
|
||||||
<ErrorProvider>
|
<ErrorProvider>
|
||||||
<ImpersonateUserProvider>
|
<ImpersonateUserProvider>
|
||||||
<UserContext.Provider value={{ userProfile, setUserProfile }}>
|
<NavBar />
|
||||||
<NavBar />
|
<Outlet />
|
||||||
<Outlet />
|
|
||||||
</UserContext.Provider>
|
|
||||||
</ImpersonateUserProvider>
|
</ImpersonateUserProvider>
|
||||||
</ErrorProvider>
|
</ErrorProvider>
|
||||||
|
|
||||||
@@ -133,6 +117,7 @@ const startOpenReplay = () => {
|
|||||||
const tracker = new Tracker({
|
const tracker = new Tracker({
|
||||||
projectKey: import.meta.env.VITE_OPENREPLAY_PROJECT_KEY,
|
projectKey: import.meta.env.VITE_OPENREPLAY_PROJECT_KEY,
|
||||||
})
|
})
|
||||||
|
|
||||||
tracker.start()
|
tracker.start()
|
||||||
}
|
}
|
||||||
export default App
|
export default App
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Save } from '@mui/icons-material'
|
||||||
import AddIcon from '@mui/icons-material/Add'
|
import AddIcon from '@mui/icons-material/Add'
|
||||||
import DeleteIcon from '@mui/icons-material/Delete'
|
import DeleteIcon from '@mui/icons-material/Delete'
|
||||||
import InfoIcon from '@mui/icons-material/Info'
|
import InfoIcon from '@mui/icons-material/Info'
|
||||||
@@ -14,66 +15,64 @@ import Typography from '@mui/joy/Typography'
|
|||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
|
|
||||||
const timeUnits = [
|
const timeUnits = [
|
||||||
{ label: 'Minutes', value: 'minutes' },
|
{ label: 'Mins', value: 'minutes' },
|
||||||
{ label: 'Hours', value: 'hours' },
|
{ label: 'Hours', value: 'hours' },
|
||||||
{ label: 'Days', value: 'days' },
|
{ label: 'Days', value: 'days' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const beforeAfterOptions = [
|
const beforeAfterOptions = [
|
||||||
{ label: 'Before Due', value: 'before' },
|
{ label: 'Before', value: 'before' },
|
||||||
{ label: 'On Due', value: 'ondue' },
|
{ label: 'On Due', value: 'ondue' },
|
||||||
{ label: 'After Due', value: 'after' },
|
{ label: 'After', value: 'after' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function getRelativeLabel(notification) {
|
function getRelativeLabel(notification) {
|
||||||
const { amount, unit, when } = notification
|
const { value, unit, type } = notification
|
||||||
|
if (type === 'ondue') {
|
||||||
// For "On Due" notification
|
|
||||||
if (when === 'ondue') {
|
|
||||||
return 'On due date'
|
return 'On due date'
|
||||||
}
|
}
|
||||||
|
return `${value} ${unit} ${type === 'before' ? 'before' : 'after'} due`
|
||||||
// For before/after notifications
|
|
||||||
return `${amount} ${unit} ${when === 'before' ? 'before' : 'after'} due`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
const NotificationTemplate = ({
|
||||||
|
maxNotifications = 5,
|
||||||
|
onChange,
|
||||||
|
value,
|
||||||
|
showTimeline = true,
|
||||||
|
}) => {
|
||||||
const [templateName, setTemplateName] = useState(
|
const [templateName, setTemplateName] = useState(
|
||||||
value?.name || 'New Notification Template',
|
value?.name || 'New Notification Template',
|
||||||
)
|
)
|
||||||
const [notifications, setNotifications] = useState([
|
const [notifications, setNotifications] = useState(
|
||||||
value?.notifications || {
|
value?.templates ||
|
||||||
amount: 0,
|
JSON.parse(localStorage.getItem('defaultNotificationTemplate')) ||
|
||||||
when: 'ondue',
|
[],
|
||||||
},
|
)
|
||||||
])
|
|
||||||
const [error, setError] = useState(null)
|
|
||||||
|
|
||||||
|
const [error, setError] = useState(null)
|
||||||
|
const [showSaveDefault, setShowSaveDefault] = useState(false)
|
||||||
// Create a map of notification indices for timeline display
|
// Create a map of notification indices for timeline display
|
||||||
const [notificationIndexMap, setNotificationIndexMap] = useState({})
|
const [notificationIndexMap, setNotificationIndexMap] = useState({})
|
||||||
|
|
||||||
// Use useCallback to memoize the function
|
|
||||||
const updateNotificationIndices = useCallback(() => {
|
const updateNotificationIndices = useCallback(() => {
|
||||||
// Sort notifications for consistent ordering
|
// Sort notifications for consistent ordering
|
||||||
const sorted = [...notifications].sort((a, b) => {
|
const sorted = [...notifications].sort((a, b) => {
|
||||||
// Always ensure correct ordering: Before Due -> On Due -> After Due
|
// Always ensure correct ordering: Before Due -> On Due -> After Due
|
||||||
if (a.when !== b.when) {
|
if (a.type !== b.type) {
|
||||||
// Before Due always comes first
|
// Before Due first
|
||||||
if (a.when === 'before') return -1
|
if (a.type === 'before') return -1
|
||||||
if (b.when === 'before') return 1
|
if (b.type === 'before') return 1
|
||||||
|
|
||||||
// On Due comes before After Due
|
// On Due comes before After Due
|
||||||
if (a.when === 'ondue') return -1
|
if (a.type === 'ondue') return -1
|
||||||
if (b.when === 'ondue') return 1
|
if (b.type === 'ondue') return 1
|
||||||
|
// DEFAULT CASE ( NOT SURE FOR FUTURE )?
|
||||||
// Default case (should not be reached with our options)
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert everything to minutes for consistent comparison
|
|
||||||
const getMinutes = notif => {
|
const getMinutes = notif => {
|
||||||
const { amount, unit } = notif
|
const { value, unit } = notif
|
||||||
let minutes = amount
|
let minutes = value
|
||||||
if (unit === 'hours') minutes *= 60
|
if (unit === 'hours') minutes *= 60
|
||||||
if (unit === 'days') minutes *= 24 * 60
|
if (unit === 'days') minutes *= 24 * 60
|
||||||
return minutes
|
return minutes
|
||||||
@@ -83,17 +82,16 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
// For After Due: sort in ascending order (closest to due first)
|
// For After Due: sort in ascending order (closest to due first)
|
||||||
const aMinutes = getMinutes(a)
|
const aMinutes = getMinutes(a)
|
||||||
const bMinutes = getMinutes(b)
|
const bMinutes = getMinutes(b)
|
||||||
return a.when === 'before' ? bMinutes - aMinutes : aMinutes - bMinutes
|
return a.type === 'before' ? bMinutes - aMinutes : aMinutes - bMinutes
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create index mapping
|
|
||||||
const indexMap = {}
|
const indexMap = {}
|
||||||
sorted.forEach((item, index) => {
|
sorted.forEach((item, index) => {
|
||||||
const originalIdx = notifications.findIndex(
|
const originalIdx = notifications.findIndex(
|
||||||
n =>
|
n =>
|
||||||
n.amount === item.amount &&
|
n.value === item.value &&
|
||||||
n.unit === item.unit &&
|
n.unit === item.unit &&
|
||||||
n.when === item.when,
|
n.type === item.type,
|
||||||
)
|
)
|
||||||
indexMap[originalIdx] = index + 1
|
indexMap[originalIdx] = index + 1
|
||||||
})
|
})
|
||||||
@@ -104,27 +102,25 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
// Sort notifications and update the index mapping
|
// Sort notifications and update the index mapping
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateNotificationIndices()
|
updateNotificationIndices()
|
||||||
// Clear any errors when notifications change
|
|
||||||
setError(null)
|
setError(null)
|
||||||
}, [updateNotificationIndices])
|
}, [updateNotificationIndices])
|
||||||
|
|
||||||
// Notify parent component of changes including the template name
|
// Notify parent component of changes including the template name
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange({ name: templateName, notifications })
|
onChange({ notifications })
|
||||||
}
|
}
|
||||||
}, [templateName, notifications, onChange])
|
}, [notifications, onChange])
|
||||||
|
|
||||||
// Validates if a notification configuration already exists
|
// Validates if a notification configuration already exists
|
||||||
const isDuplicate = (notification, currentIdx = -1) => {
|
const isDuplicate = (notification, currentIdx = -1) => {
|
||||||
return notifications.some((n, idx) => {
|
return notifications.some((n, idx) => {
|
||||||
// Skip comparing with itself when editing
|
|
||||||
if (idx === currentIdx) return false
|
if (idx === currentIdx) return false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
n.amount === notification.amount &&
|
n.value === notification.value &&
|
||||||
n.unit === notification.unit &&
|
n.unit === notification.unit &&
|
||||||
n.when === notification.when
|
n.type === notification.type
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -136,17 +132,17 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for "On Due" option
|
// Special handling for "On Due" option
|
||||||
if (field === 'when' && value === 'ondue') {
|
if (field === 'type' && value === 'ondue') {
|
||||||
// Set default values for On Due (not applicable)
|
// Set default values for On Due (not applicable)
|
||||||
updatedNotification = {
|
updatedNotification = {
|
||||||
...updatedNotification,
|
...updatedNotification,
|
||||||
amount: 0,
|
value: 1,
|
||||||
unit: 'minutes', // default unit, not displayed to user
|
unit: 'minutes',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if another notification is already "On Due"
|
// Check if another notification is already "On Due"
|
||||||
const existingOnDue = notifications.findIndex(
|
const existingOnDue = notifications.findIndex(
|
||||||
(n, i) => i !== idx && n.when === 'ondue',
|
(n, i) => i !== idx && n.type === 'ondue',
|
||||||
)
|
)
|
||||||
|
|
||||||
if (existingOnDue !== -1) {
|
if (existingOnDue !== -1) {
|
||||||
@@ -157,7 +153,6 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicates when changing a notification
|
|
||||||
if (isDuplicate(updatedNotification, idx)) {
|
if (isDuplicate(updatedNotification, idx)) {
|
||||||
setError(
|
setError(
|
||||||
'This notification setting already exists. Please use a different timing.',
|
'This notification setting already exists. Please use a different timing.',
|
||||||
@@ -172,12 +167,9 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
setError(null)
|
setError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNameChange = e => {
|
|
||||||
setTemplateName(e.target.value)
|
|
||||||
}
|
|
||||||
const addSmartNotification = type => {
|
const addSmartNotification = type => {
|
||||||
if (notifications.length >= maxNotifications) return
|
if (notifications.length >= maxNotifications) return
|
||||||
|
setShowSaveDefault(true)
|
||||||
let newNotification
|
let newNotification
|
||||||
let suggestions = []
|
let suggestions = []
|
||||||
|
|
||||||
@@ -185,28 +177,28 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
case 'reminder':
|
case 'reminder':
|
||||||
// Suggest common reminder times that don't exist
|
// Suggest common reminder times that don't exist
|
||||||
suggestions = [
|
suggestions = [
|
||||||
{ amount: 1, unit: 'hours', when: 'before' },
|
{ value: 1, unit: 'hours', type: 'before' },
|
||||||
{ amount: 1, unit: 'days', when: 'before' },
|
{ value: 1, unit: 'days', type: 'before' },
|
||||||
{ amount: 30, unit: 'minutes', when: 'before' },
|
{ value: 30, unit: 'minutes', type: 'before' },
|
||||||
{ amount: 2, unit: 'hours', when: 'before' },
|
{ value: 2, unit: 'hours', type: 'before' },
|
||||||
{ amount: 3, unit: 'days', when: 'before' },
|
{ value: 3, unit: 'days', type: 'before' },
|
||||||
]
|
]
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'due':
|
case 'due':
|
||||||
if (notifications.some(n => n.when === 'ondue')) {
|
if (notifications.some(n => n.type === 'ondue')) {
|
||||||
setError('Only one "Due Alert" notification is allowed.')
|
setError('Only one "Due Alert" notification is allowed.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newNotification = { amount: 0, unit: 'minutes', when: 'ondue' }
|
newNotification = { value: 0, unit: 'minutes', type: 'ondue' }
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'followup':
|
case 'followup':
|
||||||
suggestions = [
|
suggestions = [
|
||||||
{ amount: 1, unit: 'hours', when: 'after' },
|
{ value: 1, unit: 'hours', type: 'after' },
|
||||||
{ amount: 1, unit: 'days', when: 'after' },
|
{ value: 1, unit: 'days', type: 'after' },
|
||||||
{ amount: 3, unit: 'days', when: 'after' },
|
{ value: 3, unit: 'days', type: 'after' },
|
||||||
{ amount: 1, unit: 'weeks', when: 'after' },
|
{ value: 1, unit: 'weeks', type: 'after' },
|
||||||
]
|
]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -226,14 +218,14 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
(a, b) => {
|
(a, b) => {
|
||||||
// Convert everything to minutes for consistent comparison
|
// Convert everything to minutes for consistent comparison
|
||||||
const getMinutes = notif => {
|
const getMinutes = notif => {
|
||||||
const { amount, unit, when } = notif
|
const { value, unit, type } = notif
|
||||||
// On Due is exactly at due date (0 minutes)
|
// On Due is exactly at due date (0 minutes)
|
||||||
if (when === 'ondue') return 0
|
if (type === 'ondue') return 0
|
||||||
|
|
||||||
let minutes = amount
|
let minutes = value
|
||||||
if (unit === 'hours') minutes *= 60
|
if (unit === 'hours') minutes *= 60
|
||||||
if (unit === 'days') minutes *= 24 * 60
|
if (unit === 'days') minutes *= 24 * 60
|
||||||
return when === 'before' ? -minutes : minutes
|
return type === 'before' ? -minutes : minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
return getMinutes(a) - getMinutes(b)
|
return getMinutes(a) - getMinutes(b)
|
||||||
@@ -249,20 +241,19 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
setNotifications(updated)
|
setNotifications(updated)
|
||||||
onChange && onChange(updated)
|
onChange && onChange(updated)
|
||||||
}
|
}
|
||||||
// Visualization: improved timeline with better scaling
|
|
||||||
const renderTimeline = () => {
|
const renderTimeline = () => {
|
||||||
// Sort notifications chronologically
|
// Sort notifications chronologically
|
||||||
const sorted = [...notifications].sort((a, b) => {
|
const sorted = [...notifications].sort((a, b) => {
|
||||||
// Convert everything to minutes for consistent comparison
|
// Convert everything to minutes for consistent comparison
|
||||||
const getMinutes = notif => {
|
const getMinutes = notif => {
|
||||||
const { amount, unit, when } = notif
|
const { value, unit, type } = notif
|
||||||
// On Due is exactly at due date (0 minutes)
|
// On Due is exactly at due date (0 minutes)
|
||||||
if (when === 'ondue') return 0
|
if (type === 'ondue') return 0
|
||||||
|
|
||||||
let minutes = amount
|
let minutes = value
|
||||||
if (unit === 'hours') minutes *= 60
|
if (unit === 'hours') minutes *= 60
|
||||||
if (unit === 'days') minutes *= 24 * 60
|
if (unit === 'days') minutes *= 24 * 60
|
||||||
return when === 'before' ? -minutes : minutes
|
return type === 'before' ? -minutes : minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
return getMinutes(a) - getMinutes(b)
|
return getMinutes(a) - getMinutes(b)
|
||||||
@@ -274,27 +265,24 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
// Find the original index of this item in notifications array
|
// Find the original index of this item in notifications array
|
||||||
const originalIdx = notifications.findIndex(
|
const originalIdx = notifications.findIndex(
|
||||||
n =>
|
n =>
|
||||||
n.amount === item.amount &&
|
n.value === item.value &&
|
||||||
n.unit === item.unit &&
|
n.unit === item.unit &&
|
||||||
n.when === item.when,
|
n.type === item.type,
|
||||||
)
|
)
|
||||||
notificationIndexMap[originalIdx] = index + 1
|
notificationIndexMap[originalIdx] = index + 1
|
||||||
}) // Get min and max notification times for dynamic scaling
|
}) // Get min and max notification times for dynamic scaling
|
||||||
const minutesValues = sorted.map(n => {
|
const minutesValues = sorted.map(n => {
|
||||||
// On Due is exactly at due date (0 minutes)
|
if (n.type === 'ondue') return 0
|
||||||
if (n.when === 'ondue') return 0
|
|
||||||
|
|
||||||
let minutes = n.amount
|
let minutes = n.value
|
||||||
if (n.unit === 'hours') minutes *= 60
|
if (n.unit === 'hours') minutes *= 60
|
||||||
if (n.unit === 'days') minutes *= 24 * 60
|
if (n.unit === 'days') minutes *= 24 * 60
|
||||||
return n.when === 'before' ? -minutes : minutes
|
return n.type === 'before' ? -minutes : minutes
|
||||||
})
|
})
|
||||||
|
|
||||||
// Find min (before) and max (after) notification times
|
|
||||||
const minBefore = Math.min(0, ...minutesValues) // Default to 0 if no "before" notifications
|
const minBefore = Math.min(0, ...minutesValues) // Default to 0 if no "before" notifications
|
||||||
const maxAfter = Math.max(0, ...minutesValues) // Default to 0 if no "after" notifications
|
const maxAfter = Math.max(0, ...minutesValues) // Default to 0 if no "after" notifications
|
||||||
|
|
||||||
// Dynamic scaling based on notification range
|
|
||||||
const getPositionPercent = minutes => {
|
const getPositionPercent = minutes => {
|
||||||
// Due date is always at center (50%)
|
// Due date is always at center (50%)
|
||||||
if (minutes === 0) return 50
|
if (minutes === 0) return 50
|
||||||
@@ -314,7 +302,7 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mt: 3, mb: 2 }}>
|
<Box sx={{ mt: 3, mb: 2 }}>
|
||||||
<Typography level={'body2'} sx={{ mb: 1, fontWeight: 'md' }}>
|
<Typography level={'body-md'} sx={{ mb: 1 }}>
|
||||||
Notification Timeline
|
Notification Timeline
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
@@ -322,7 +310,7 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
height: 100,
|
height: 90,
|
||||||
bgcolor: 'background.level1',
|
bgcolor: 'background.level1',
|
||||||
borderRadius: 'md',
|
borderRadius: 'md',
|
||||||
p: 2,
|
p: 2,
|
||||||
@@ -375,11 +363,11 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
{sorted.map((n, i) => {
|
{sorted.map((n, i) => {
|
||||||
// Convert to minutes for consistent scale
|
// Convert to minutes for consistent scale
|
||||||
let minutes = 0
|
let minutes = 0
|
||||||
if (n.when !== 'ondue') {
|
if (n.type !== 'ondue') {
|
||||||
minutes = n.amount
|
minutes = n.value
|
||||||
if (n.unit === 'hours') minutes *= 60
|
if (n.unit === 'hours') minutes *= 60
|
||||||
if (n.unit === 'days') minutes *= 24 * 60
|
if (n.unit === 'days') minutes *= 24 * 60
|
||||||
if (n.when === 'before') minutes = -minutes
|
if (n.type === 'before') minutes = -minutes
|
||||||
}
|
}
|
||||||
// On Due notifications are always at the due date (0 minutes)
|
// On Due notifications are always at the due date (0 minutes)
|
||||||
|
|
||||||
@@ -394,9 +382,9 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
left: `${percent}%`,
|
left: `${percent}%`,
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
color:
|
color:
|
||||||
n.when === 'before'
|
n.type === 'before'
|
||||||
? 'primary.600'
|
? 'primary.600'
|
||||||
: n.when === 'ondue'
|
: n.type === 'ondue'
|
||||||
? 'warning.600'
|
? 'warning.600'
|
||||||
: 'success.600',
|
: 'success.600',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -418,11 +406,11 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant={'solid'}
|
variant={'solid'}
|
||||||
color={
|
color={
|
||||||
n.when === 'before'
|
n.type === 'before'
|
||||||
? 'primary'
|
? 'success'
|
||||||
: n.when === 'ondue'
|
: n.type === 'ondue'
|
||||||
? 'warning'
|
? 'warning'
|
||||||
: 'success'
|
: 'danger'
|
||||||
}
|
}
|
||||||
sx={{
|
sx={{
|
||||||
'--Badge-paddingX': '4px',
|
'--Badge-paddingX': '4px',
|
||||||
@@ -452,22 +440,24 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={
|
||||||
border: '1px solid',
|
{
|
||||||
borderColor: 'neutral.outlinedBorder',
|
// border: '1px solid',
|
||||||
borderRadius: 2,
|
// borderColor: 'neutral.outlinedBorder',
|
||||||
p: 3,
|
// borderRadius: 2,
|
||||||
maxWidth: 500,
|
// p: 3,
|
||||||
bgcolor: 'background.body',
|
// maxWidth: 500,
|
||||||
boxShadow: 'sm',
|
// bgcolor: 'background.body',
|
||||||
}}
|
// boxShadow: 'sm',
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Typography level={'h4'} sx={{ mb: 2 }}>
|
{/* <Typography level={'h4'} sx={{ mb: 2 }}>
|
||||||
Schedule Name
|
Schedule Name
|
||||||
</Typography>
|
</Typography> */}
|
||||||
|
|
||||||
{/* Template Name Field */}
|
{/* Template Name Field */}
|
||||||
<Box sx={{ mb: 3 }}>
|
{/* <Box sx={{ mb: 3 }}>
|
||||||
<Typography level={'body2'} sx={{ mb: 1, fontWeight: 'md' }}>
|
<Typography level={'body2'} sx={{ mb: 1, fontWeight: 'md' }}>
|
||||||
Template Name
|
Template Name
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -477,7 +467,7 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
placeholder='Enter template name'
|
placeholder='Enter template name'
|
||||||
sx={{ width: '100%' }}
|
sx={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box> */}
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -500,9 +490,9 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'start',
|
||||||
width: 30,
|
width: 18,
|
||||||
mr: 1,
|
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -512,23 +502,23 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
sx={{
|
sx={{
|
||||||
'--Badge-minHeight': '20px',
|
'--Badge-minHeight': '20px',
|
||||||
'--Badge-fontSize': '0.75rem',
|
'--Badge-fontSize': '0.75rem',
|
||||||
|
// centering the badge:
|
||||||
}}
|
}}
|
||||||
color={
|
color={
|
||||||
n.when === 'before'
|
n.type === 'before'
|
||||||
? 'primary'
|
? 'success'
|
||||||
: n.when === 'ondue'
|
: n.type === 'ondue'
|
||||||
? 'warning'
|
? 'warning'
|
||||||
: 'success'
|
: 'danger'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Box sx={{ width: 4, height: 16 }} />
|
|
||||||
{/* Empty box to attach badge to */}
|
{/* Empty box to attach badge to */}
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
<Select
|
<Select
|
||||||
value={n.when}
|
value={n.type}
|
||||||
onChange={(_, value) => handleChange(idx, 'when', value)}
|
onChange={(_, value) => handleChange(idx, 'type', value)}
|
||||||
sx={{ mr: 1, minWidth: 120 }}
|
sx={{ mr: 1, minWidth: 100 }}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
>
|
>
|
||||||
{beforeAfterOptions.map(opt => (
|
{beforeAfterOptions.map(opt => (
|
||||||
@@ -541,32 +531,32 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
<Input
|
<Input
|
||||||
type={'number'}
|
type={'number'}
|
||||||
min={1}
|
min={1}
|
||||||
disabled={n.when === 'ondue'}
|
disabled={n.type === 'ondue'}
|
||||||
value={n.when === 'ondue' ? '—' : n.amount}
|
value={n.type === 'ondue' ? '—' : n.value}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
handleChange(idx, 'amount', Math.max(1, Number(e.target.value)))
|
handleChange(idx, 'value', Math.max(1, Number(e.target.value)))
|
||||||
}
|
}
|
||||||
sx={{
|
sx={{
|
||||||
width: 70,
|
width: 70,
|
||||||
mr: 1,
|
mr: 1,
|
||||||
opacity: n.when === 'ondue' ? 0.6 : 1,
|
opacity: n.type === 'ondue' ? 0.6 : 1,
|
||||||
...(n.when === 'ondue' && {
|
...(n.type === 'ondue' && {
|
||||||
'& input': {
|
'& input': {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
placeholder={n.when === 'ondue' ? '—' : ''}
|
placeholder={n.type === 'ondue' ? '—' : ''}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
value={n.unit}
|
value={n.unit}
|
||||||
disabled={n.when === 'ondue'}
|
disabled={n.type === 'ondue'}
|
||||||
onChange={(_, value) => handleChange(idx, 'unit', value)}
|
onChange={(_, value) => handleChange(idx, 'unit', value)}
|
||||||
sx={{
|
sx={{
|
||||||
mr: 1,
|
mr: 1,
|
||||||
minWidth: 100,
|
minWidth: 80,
|
||||||
opacity: n.when === 'ondue' ? 0.6 : 1,
|
opacity: n.type === 'ondue' ? 0.6 : 1,
|
||||||
}}
|
}}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
>
|
>
|
||||||
@@ -604,7 +594,7 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
onClick={() => addSmartNotification('due')}
|
onClick={() => addSmartNotification('due')}
|
||||||
disabled={
|
disabled={
|
||||||
notifications.length >= maxNotifications ||
|
notifications.length >= maxNotifications ||
|
||||||
notifications.some(n => n.when === 'ondue')
|
notifications.some(n => n.type === 'ondue')
|
||||||
}
|
}
|
||||||
startDecorator={<AddIcon />}
|
startDecorator={<AddIcon />}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
@@ -619,12 +609,31 @@ const NotificationTemplate = ({ maxNotifications = 5, onChange, value }) => {
|
|||||||
startDecorator={<AddIcon />}
|
startDecorator={<AddIcon />}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
variant={'outlined'}
|
variant={'outlined'}
|
||||||
color={'success'}
|
color={'danger'}
|
||||||
>
|
>
|
||||||
Follow-up
|
Follow-up
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
{renderTimeline()}
|
{showSaveDefault && (
|
||||||
|
<Button
|
||||||
|
variant='outlined'
|
||||||
|
size='sm'
|
||||||
|
color='neutral'
|
||||||
|
// sx={{ ml: 'auto', mt: 0.5 }}
|
||||||
|
startDecorator={<Save />}
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'defaultNotificationTemplate',
|
||||||
|
JSON.stringify(notifications),
|
||||||
|
)
|
||||||
|
setShowSaveDefault(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Save as Default for Future Tasks
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showTimeline && renderTimeline()}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { GetAllCircleMembers, GetAllUsers } from '../utils/Fetcher'
|
import {
|
||||||
|
GetAllCircleMembers,
|
||||||
|
GetAllUsers,
|
||||||
|
GetUserProfile,
|
||||||
|
} from '../utils/Fetcher'
|
||||||
|
import { isTokenValid } from '../utils/TokenManager'
|
||||||
|
|
||||||
export const useAllUsers = () => {
|
export const useAllUsers = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -22,3 +27,27 @@ export const useCircleMembers = () => {
|
|||||||
|
|
||||||
return { data, error, isLoading, handleRefetch }
|
return { data, error, isLoading, handleRefetch }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useUserProfile = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const { data, error, isLoading } = useQuery({
|
||||||
|
queryKey: ['userProfile'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const resp = await GetUserProfile()
|
||||||
|
const result = await resp.json()
|
||||||
|
if (!isTokenValid()) {
|
||||||
|
return null // Token is invalid, return null to indicate no profile
|
||||||
|
}
|
||||||
|
return result.res // Return the actual user profile data
|
||||||
|
},
|
||||||
|
staleTime: 30 * 60 * 1000, // 30 minutes in milliseconds
|
||||||
|
gcTime: 30 * 60 * 1000, // 30 minutes in milliseconds
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
error,
|
||||||
|
isLoading,
|
||||||
|
refetch: () => queryClient.invalidateQueries(['userProfile']),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,12 +68,16 @@ export async function UploadFile(url, options) {
|
|||||||
export async function Fetch(url, options) {
|
export async function Fetch(url, options) {
|
||||||
if (!isTokenValid()) {
|
if (!isTokenValid()) {
|
||||||
Cookies.set('ca_redirect', window.location.pathname)
|
Cookies.set('ca_redirect', window.location.pathname)
|
||||||
window.location.href = '/login'
|
if (!window.location.pathname === '/login') {
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {}
|
options = {}
|
||||||
}
|
}
|
||||||
|
// clone options to avoid mutation
|
||||||
|
const cacheKey = { ...options }
|
||||||
options.headers = { ...options.headers, ...HEADERS() }
|
options.headers = { ...options.headers, ...HEADERS() }
|
||||||
|
|
||||||
const baseURL = apiManager.getApiURL()
|
const baseURL = apiManager.getApiURL()
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Box, Button, CircularProgress, Container, Typography } from '@mui/joy'
|
import { Box, Button, CircularProgress, Container, Typography } from '@mui/joy'
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Logo from '../../Logo'
|
import Logo from '../../Logo'
|
||||||
import { apiManager } from '../../utils/TokenManager'
|
import { apiManager } from '../../utils/TokenManager'
|
||||||
|
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { Link, useNavigate, useParams } from 'react-router-dom'
|
import { Link, useNavigate, useParams } from 'react-router-dom'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import { GetUserProfile } from '../../utils/Fetcher'
|
import { GetUserProfile } from '../../utils/Fetcher'
|
||||||
|
|
||||||
const AuthenticationLoading = () => {
|
const AuthenticationLoading = () => {
|
||||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
const { data: userProfile, refetch: refetchUserProfile } = useUserProfile()
|
||||||
const Navigate = useNavigate()
|
const Navigate = useNavigate()
|
||||||
const hasCalledHandleOAuth2 = useRef(false)
|
const hasCalledHandleOAuth2 = useRef(false)
|
||||||
const [message, setMessage] = useState('Authenticating')
|
const [message, setMessage] = useState('Authenticating')
|
||||||
@@ -29,15 +29,16 @@ const AuthenticationLoading = () => {
|
|||||||
const getUserProfileAndNavigateToHome = () => {
|
const getUserProfileAndNavigateToHome = () => {
|
||||||
GetUserProfile().then(data => {
|
GetUserProfile().then(data => {
|
||||||
data.json().then(data => {
|
data.json().then(data => {
|
||||||
setUserProfile(data.res)
|
refetchUserProfile.then(() => {
|
||||||
// check if redirect url is set in cookie:
|
// check if redirect url is set in cookie:
|
||||||
const redirectUrl = Cookies.get('ca_redirect')
|
const redirectUrl = Cookies.get('ca_redirect')
|
||||||
if (redirectUrl) {
|
if (redirectUrl) {
|
||||||
Cookies.remove('ca_redirect')
|
Cookies.remove('ca_redirect')
|
||||||
Navigate(redirectUrl)
|
Navigate(redirectUrl)
|
||||||
} else {
|
} else {
|
||||||
Navigate('/my/chores')
|
Navigate('/my/chores')
|
||||||
}
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
|
||||||
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import { isPlusAccount } from '../../utils/Helpers'
|
import { isPlusAccount } from '../../utils/Helpers'
|
||||||
import ThingTriggerSection from './ThingTriggerSection'
|
import ThingTriggerSection from './ThingTriggerSection'
|
||||||
|
|
||||||
@@ -68,7 +69,6 @@ const RepeatOnSections = ({
|
|||||||
frequencyMetadata,
|
frequencyMetadata,
|
||||||
onFrequencyMetadataUpdate,
|
onFrequencyMetadataUpdate,
|
||||||
}) => {
|
}) => {
|
||||||
const [intervalUnit, setIntervalUnit] = useState('days')
|
|
||||||
// if time on frequencyMetadata is not set, try to set it to the nextDueDate if available,
|
// if time on frequencyMetadata is not set, try to set it to the nextDueDate if available,
|
||||||
// otherwise set it to 18:00 of the current day
|
// otherwise set it to 18:00 of the current day
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -117,13 +117,16 @@ const RepeatOnSections = ({
|
|||||||
onFrequencyUpdate(e.target.value)
|
onFrequencyUpdate(e.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Select placeholder='Unit' value={intervalUnit}>
|
<Select
|
||||||
|
placeholder='Unit'
|
||||||
|
value={frequencyMetadata?.unit || 'days'}
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
>
|
||||||
{['hours', 'days', 'weeks', 'months', 'years'].map(item => (
|
{['hours', 'days', 'weeks', 'months', 'years'].map(item => (
|
||||||
<Option
|
<Option
|
||||||
key={item}
|
key={item}
|
||||||
value={item}
|
value={item}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIntervalUnit(item)
|
|
||||||
onFrequencyMetadataUpdate({
|
onFrequencyMetadataUpdate({
|
||||||
...frequencyMetadata,
|
...frequencyMetadata,
|
||||||
unit: item,
|
unit: item,
|
||||||
@@ -337,7 +340,8 @@ const RepeatSection = ({
|
|||||||
isAttemptToSave,
|
isAttemptToSave,
|
||||||
selectedThing,
|
selectedThing,
|
||||||
}) => {
|
}) => {
|
||||||
const { userProfile } = useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box mt={2}>
|
<Box mt={2}>
|
||||||
<Typography level='h4'>Repeat :</Typography>
|
<Typography level='h4'>Repeat :</Typography>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import moment from 'moment'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useUserProfile } from '../../queries/UserQueries.jsx'
|
||||||
import { useError } from '../../service/ErrorProvider'
|
import { useError } from '../../service/ErrorProvider'
|
||||||
import { notInCompletionWindow } from '../../utils/Chores.jsx'
|
import { notInCompletionWindow } from '../../utils/Chores.jsx'
|
||||||
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
||||||
@@ -63,7 +63,8 @@ const ChoreCard = ({
|
|||||||
const [isPendingCompletion, setIsPendingCompletion] = React.useState(false)
|
const [isPendingCompletion, setIsPendingCompletion] = React.useState(false)
|
||||||
const [secondsLeftToCancel, setSecondsLeftToCancel] = React.useState(null)
|
const [secondsLeftToCancel, setSecondsLeftToCancel] = React.useState(null)
|
||||||
const [timeoutId, setTimeoutId] = React.useState(null)
|
const [timeoutId, setTimeoutId] = React.useState(null)
|
||||||
const { userProfile } = React.useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
const { impersonatedUser } = useImpersonateUser()
|
const { impersonatedUser } = useImpersonateUser()
|
||||||
|
|
||||||
const { showError } = useError()
|
const { showError } = useError()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import moment from 'moment'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useUserProfile } from '../../queries/UserQueries.jsx'
|
||||||
import { useError } from '../../service/ErrorProvider'
|
import { useError } from '../../service/ErrorProvider'
|
||||||
import { notInCompletionWindow } from '../../utils/Chores.jsx'
|
import { notInCompletionWindow } from '../../utils/Chores.jsx'
|
||||||
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
import { getTextColorFromBackgroundColor } from '../../utils/Colors.jsx'
|
||||||
@@ -60,7 +60,8 @@ const CompactChoreCard = ({
|
|||||||
const [isPendingCompletion, setIsPendingCompletion] = React.useState(false)
|
const [isPendingCompletion, setIsPendingCompletion] = React.useState(false)
|
||||||
const [secondsLeftToCancel, setSecondsLeftToCancel] = React.useState(null)
|
const [secondsLeftToCancel, setSecondsLeftToCancel] = React.useState(null)
|
||||||
const [timeoutId, setTimeoutId] = React.useState(null)
|
const [timeoutId, setTimeoutId] = React.useState(null)
|
||||||
const { userProfile } = React.useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
const { impersonatedUser } = useImpersonateUser()
|
const { impersonatedUser } = useImpersonateUser()
|
||||||
|
|
||||||
const { showError } = useError()
|
const { showError } = useError()
|
||||||
@@ -577,10 +578,11 @@ const CompactChoreCard = ({
|
|||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
sx={{
|
sx={{
|
||||||
width: 28,
|
width: 28,
|
||||||
|
marginRight: -3,
|
||||||
height: 28,
|
height: 28,
|
||||||
// opacity: 0.6,
|
// opacity: 0.6,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
opacity: 1,
|
opacity: 0,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { useContext, useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
|
||||||
import { useChores } from '../../queries/ChoreQueries'
|
import { useChores } from '../../queries/ChoreQueries'
|
||||||
import { GetArchivedChores } from '../../utils/Fetcher'
|
import { GetArchivedChores } from '../../utils/Fetcher'
|
||||||
import Priorities from '../../utils/Priorities'
|
import Priorities from '../../utils/Priorities'
|
||||||
@@ -42,7 +41,7 @@ import ChoreCard from './ChoreCard'
|
|||||||
import CompactChoreCard from './CompactChoreCard'
|
import CompactChoreCard from './CompactChoreCard'
|
||||||
import IconButtonWithMenu from './IconButtonWithMenu'
|
import IconButtonWithMenu from './IconButtonWithMenu'
|
||||||
|
|
||||||
import { useCircleMembers } from '../../queries/UserQueries'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||||
import { ChoreFilters, ChoresGrouper, ChoreSorter } from '../../utils/Chores'
|
import { ChoreFilters, ChoresGrouper, ChoreSorter } from '../../utils/Chores'
|
||||||
import TaskInput from '../components/AddTaskModal'
|
import TaskInput from '../components/AddTaskModal'
|
||||||
import {
|
import {
|
||||||
@@ -54,7 +53,8 @@ import Sidepanel from './Sidepanel'
|
|||||||
import SortAndGrouping from './SortAndGrouping'
|
import SortAndGrouping from './SortAndGrouping'
|
||||||
|
|
||||||
const MyChores = () => {
|
const MyChores = () => {
|
||||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
const { data: userProfile, isLoading: isUserProfileLoading } =
|
||||||
|
useUserProfile()
|
||||||
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
|
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
|
||||||
const [snackBarMessage, setSnackBarMessage] = useState(null)
|
const [snackBarMessage, setSnackBarMessage] = useState(null)
|
||||||
const [chores, setChores] = useState([])
|
const [chores, setChores] = useState([])
|
||||||
@@ -119,7 +119,7 @@ const MyChores = () => {
|
|||||||
scheduleChoreNotification(choresData.res, userProfile, membersData.res)
|
scheduleChoreNotification(choresData.res, userProfile, membersData.res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [membersLoading, choresLoading, userProfile])
|
}, [membersLoading, choresLoading, isUserProfileLoading])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.addEventListener('mousedown', handleMenuOutsideClick)
|
document.addEventListener('mousedown', handleMenuOutsideClick)
|
||||||
@@ -352,12 +352,39 @@ const MyChores = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
userProfile === null ||
|
isUserProfileLoading ||
|
||||||
userLabelsLoading ||
|
userLabelsLoading ||
|
||||||
performers.length === 0 ||
|
performers.length === 0 ||
|
||||||
choresLoading
|
choresLoading
|
||||||
) {
|
) {
|
||||||
return <LoadingComponent />
|
console.log(
|
||||||
|
'userProfile:',
|
||||||
|
userProfile,
|
||||||
|
'userLabelsLoading:',
|
||||||
|
userLabelsLoading,
|
||||||
|
'performers:',
|
||||||
|
performers.length,
|
||||||
|
'choresLoading:',
|
||||||
|
choresLoading,
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Typography level='title-lg' sx={{ mt: 2, mb: 2 }}>
|
||||||
|
{JSON.stringify(userProfile) === 'null'}
|
||||||
|
</Typography>
|
||||||
|
<Typography level='title-lg' sx={{ mt: 2, mb: 2 }}>
|
||||||
|
{userLabelsLoading}
|
||||||
|
</Typography>
|
||||||
|
<Typography level='title-lg' sx={{ mt: 2, mb: 2 }}>
|
||||||
|
{performers.length === 0}
|
||||||
|
</Typography>
|
||||||
|
<Typography level='title-lg' sx={{ mt: 2, mb: 2 }}>
|
||||||
|
{choresLoading}
|
||||||
|
</Typography>
|
||||||
|
<LoadingComponent />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { Person } from '@mui/icons-material'
|
import { Person } from '@mui/icons-material'
|
||||||
import { Avatar, Box, Button, Sheet, Typography } from '@mui/joy'
|
import { Avatar, Box, Button, Sheet, Typography } from '@mui/joy'
|
||||||
|
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext'
|
import { useImpersonateUser } from '../../contexts/ImpersonateUserContext'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||||
import { useCircleMembers } from '../../queries/UserQueries'
|
|
||||||
import UserModal from '../Modals/Inputs/UserModal'
|
import UserModal from '../Modals/Inputs/UserModal'
|
||||||
const WelcomeCard = () => {
|
const WelcomeCard = () => {
|
||||||
const { impersonatedUser, setImpersonatedUser } = useImpersonateUser()
|
const { impersonatedUser, setImpersonatedUser } = useImpersonateUser()
|
||||||
const [isAdmin, setIsAdmin] = useState(false)
|
const [isAdmin, setIsAdmin] = useState(false)
|
||||||
const { userProfile } = useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false)
|
const [isModalOpen, setIsModalOpen] = useState(false)
|
||||||
|
|
||||||
const { data: circleMembersData, isLoading: isCircleMembersLoading } =
|
const { data: circleMembersData, isLoading: isCircleMembersLoading } =
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { Box, Container, Input, Sheet, Typography } from '@mui/joy'
|
|||||||
import Logo from '../../Logo'
|
import Logo from '../../Logo'
|
||||||
|
|
||||||
import { Button } from '@mui/joy'
|
import { Button } from '@mui/joy'
|
||||||
import { useContext } from 'react'
|
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import { JoinCircle } from '../../utils/Fetcher'
|
import { JoinCircle } from '../../utils/Fetcher'
|
||||||
const JoinCircleView = () => {
|
const JoinCircleView = () => {
|
||||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
let [searchParams, setSearchParams] = useSearchParams()
|
let [searchParams, setSearchParams] = useSearchParams()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const code = searchParams.get('code')
|
const code = searchParams.get('code')
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useContext, useEffect, useState } from 'react'
|
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import {
|
import {
|
||||||
CreateLongLiveToken,
|
CreateLongLiveToken,
|
||||||
DeleteLongLiveToken,
|
DeleteLongLiveToken,
|
||||||
@@ -21,10 +22,10 @@ import { isPlusAccount } from '../../utils/Helpers'
|
|||||||
import TextModal from '../Modals/Inputs/TextModal'
|
import TextModal from '../Modals/Inputs/TextModal'
|
||||||
|
|
||||||
const APITokenSettings = () => {
|
const APITokenSettings = () => {
|
||||||
|
const { data: userProfile } = useUserProfile()
|
||||||
const [tokens, setTokens] = useState([])
|
const [tokens, setTokens] = useState([])
|
||||||
const [isGetTokenNameModalOpen, setIsGetTokenNameModalOpen] = useState(false)
|
const [isGetTokenNameModalOpen, setIsGetTokenNameModalOpen] = useState(false)
|
||||||
const [showTokenId, setShowTokenId] = useState(null)
|
const [showTokenId, setShowTokenId] = useState(null)
|
||||||
const { userProfile } = useContext(UserContext)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
GetLongLiveTokens().then(resp => {
|
GetLongLiveTokens().then(resp => {
|
||||||
resp.json().then(data => {
|
resp.json().then(data => {
|
||||||
|
|||||||
@@ -468,11 +468,6 @@ const MFASettings = () => {
|
|||||||
fontSize: '1.2em',
|
fontSize: '1.2em',
|
||||||
letterSpacing: verificationCode.length === 0 ? '' : '0.4em',
|
letterSpacing: verificationCode.length === 0 ? '' : '0.4em',
|
||||||
}}
|
}}
|
||||||
onKeyDown={e => {
|
|
||||||
if (e.key === 'Enter' && verificationCode.length === 6) {
|
|
||||||
handleDisableMFA()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
slotProps={{
|
slotProps={{
|
||||||
input: {
|
input: {
|
||||||
maxLength: 6,
|
maxLength: 6,
|
||||||
|
|||||||
@@ -18,27 +18,18 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import React, { useContext, useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
|
||||||
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import {
|
import {
|
||||||
GetUserProfile,
|
|
||||||
UpdateNotificationTarget,
|
UpdateNotificationTarget,
|
||||||
UpdateUserDetails,
|
UpdateUserDetails,
|
||||||
} from '../../utils/Fetcher'
|
} from '../../utils/Fetcher'
|
||||||
|
|
||||||
const NotificationSetting = () => {
|
const NotificationSetting = () => {
|
||||||
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
|
const [isSnackbarOpen, setIsSnackbarOpen] = useState(false)
|
||||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
const { data: userProfile, refetch: refetchUserProfile } = useUserProfile()
|
||||||
useEffect(() => {
|
|
||||||
if (!userProfile) {
|
|
||||||
GetUserProfile().then(resp => {
|
|
||||||
resp.json().then(data => {
|
|
||||||
setUserProfile(data.res)
|
|
||||||
setChatID(data.res.chatID)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
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)
|
||||||
@@ -134,13 +125,7 @@ const NotificationSetting = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setUserProfile({
|
refetchUserProfile()
|
||||||
...userProfile,
|
|
||||||
notification_target: {
|
|
||||||
target: chatID,
|
|
||||||
type: Number(notificationTarget),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
alert('Notification target updated')
|
alert('Notification target updated')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -339,7 +324,7 @@ const NotificationSetting = () => {
|
|||||||
chatID: Number(0),
|
chatID: Number(0),
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
resp.json().then(data => {
|
resp.json().then(data => {
|
||||||
setUserProfile(data)
|
refetchUserProfile()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,16 @@ import {
|
|||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import Modal from '@mui/joy/Modal'
|
import Modal from '@mui/joy/Modal'
|
||||||
import ModalDialog from '@mui/joy/ModalDialog'
|
import ModalDialog from '@mui/joy/ModalDialog'
|
||||||
import { useContext, useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import Cropper from 'react-easy-crop'
|
import Cropper from 'react-easy-crop'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import { UpdateUserDetails } from '../../utils/Fetcher'
|
import { UpdateUserDetails } from '../../utils/Fetcher'
|
||||||
import { resolvePhotoURL } from '../../utils/Helpers'
|
import { resolvePhotoURL } from '../../utils/Helpers'
|
||||||
import { getCroppedImg } from '../../utils/imageCropUtils'
|
import { getCroppedImg } from '../../utils/imageCropUtils'
|
||||||
import { UploadFile } from '../../utils/TokenManager'
|
import { UploadFile } from '../../utils/TokenManager'
|
||||||
|
|
||||||
const ProfileSettings = () => {
|
const ProfileSettings = () => {
|
||||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
const [displayName, setDisplayName] = useState(userProfile?.displayName || '')
|
const [displayName, setDisplayName] = useState(userProfile?.displayName || '')
|
||||||
const [timezone, setTimezone] = useState(
|
const [timezone, setTimezone] = useState(
|
||||||
userProfile?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
userProfile?.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
@@ -75,7 +75,6 @@ const ProfileSettings = () => {
|
|||||||
const url = resolvePhotoURL(data.url || data.sign)
|
const url = resolvePhotoURL(data.url || data.sign)
|
||||||
|
|
||||||
setPhotoURL(url)
|
setPhotoURL(url)
|
||||||
setUserProfile({ ...userProfile, image: url })
|
|
||||||
setSnackbar({
|
setSnackbar({
|
||||||
open: true,
|
open: true,
|
||||||
message: 'Profile photo updated!',
|
message: 'Profile photo updated!',
|
||||||
@@ -101,7 +100,6 @@ const ProfileSettings = () => {
|
|||||||
const response = await UpdateUserDetails(userDetails)
|
const response = await UpdateUserDetails(userDetails)
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
setUserProfile({ ...userProfile, displayName, timezone })
|
|
||||||
setSnackbar({
|
setSnackbar({
|
||||||
open: true,
|
open: true,
|
||||||
message: 'Profile updated successfully!',
|
message: 'Profile updated successfully!',
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
|
||||||
import Logo from '../../Logo'
|
import Logo from '../../Logo'
|
||||||
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import {
|
import {
|
||||||
AcceptCircleMemberRequest,
|
AcceptCircleMemberRequest,
|
||||||
CancelSubscription,
|
CancelSubscription,
|
||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
GetCircleMemberRequests,
|
GetCircleMemberRequests,
|
||||||
GetSubscriptionSession,
|
GetSubscriptionSession,
|
||||||
GetUserCircle,
|
GetUserCircle,
|
||||||
GetUserProfile,
|
|
||||||
JoinCircle,
|
JoinCircle,
|
||||||
LeaveCircle,
|
LeaveCircle,
|
||||||
PutWebhookURL,
|
PutWebhookURL,
|
||||||
@@ -44,7 +43,8 @@ import StorageSettings from './StorageSettings'
|
|||||||
import ThemeToggle from './ThemeToggle'
|
import ThemeToggle from './ThemeToggle'
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const { userProfile, setUserProfile } = useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
const [userCircles, setUserCircles] = useState([])
|
const [userCircles, setUserCircles] = useState([])
|
||||||
const [circleMemberRequests, setCircleMemberRequests] = useState([])
|
const [circleMemberRequests, setCircleMemberRequests] = useState([])
|
||||||
const [circleInviteCode, setCircleInviteCode] = useState('')
|
const [circleInviteCode, setCircleInviteCode] = useState('')
|
||||||
@@ -55,11 +55,6 @@ const Settings = () => {
|
|||||||
|
|
||||||
const [changePasswordModal, setChangePasswordModal] = useState(false)
|
const [changePasswordModal, setChangePasswordModal] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
GetUserProfile().then(resp => {
|
|
||||||
resp.json().then(data => {
|
|
||||||
setUserProfile(data.res)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
GetUserCircle().then(resp => {
|
GetUserCircle().then(resp => {
|
||||||
resp.json().then(data => {
|
resp.json().then(data => {
|
||||||
setUserCircles(data.res ? data.res : [])
|
setUserCircles(data.res ? data.res : [])
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ import {
|
|||||||
LinearProgress,
|
LinearProgress,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import { useContext, useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useUserProfile } from '../../queries/UserQueries'
|
||||||
import { GetStorageUsage } from '../../utils/Fetcher'
|
import { GetStorageUsage } from '../../utils/Fetcher'
|
||||||
import { isPlusAccount } from '../../utils/Helpers'
|
import { isPlusAccount } from '../../utils/Helpers'
|
||||||
|
|
||||||
const StorageSettings = () => {
|
const StorageSettings = () => {
|
||||||
const Navigate = useNavigate()
|
const Navigate = useNavigate()
|
||||||
const { userProfile } = useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
const [usage, setUsage] = useState({ used: 0, total: 0 })
|
const [usage, setUsage] = useState({ used: 0, total: 0 })
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from '@mui/joy'
|
} from '@mui/joy'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
|
||||||
import { useChores, useChoresHistory } from '../../queries/ChoreQueries'
|
import { useChores, useChoresHistory } from '../../queries/ChoreQueries'
|
||||||
import { useCircleMembers } from '../../queries/UserQueries.jsx'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries.jsx'
|
||||||
import { ChoresGrouper } from '../../utils/Chores'
|
import { ChoresGrouper } from '../../utils/Chores'
|
||||||
import { TASK_COLOR } from '../../utils/Colors.jsx'
|
import { TASK_COLOR } from '../../utils/Colors.jsx'
|
||||||
import { resolvePhotoURL } from '../../utils/Helpers.jsx'
|
import { resolvePhotoURL } from '../../utils/Helpers.jsx'
|
||||||
@@ -167,7 +167,8 @@ const USER_FILTER = (history, userId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const UserActivites = () => {
|
const UserActivites = () => {
|
||||||
const { userProfile } = React.useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
const [tabValue, setTabValue] = React.useState(30)
|
const [tabValue, setTabValue] = React.useState(30)
|
||||||
const [selectedHistory, setSelectedHistory] = React.useState([])
|
const [selectedHistory, setSelectedHistory] = React.useState([])
|
||||||
const [enrichedHistory, setEnrichedHistory] = React.useState([])
|
const [enrichedHistory, setEnrichedHistory] = React.useState([])
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { CalendarMonth } from '@mui/icons-material'
|
import { CalendarMonth } from '@mui/icons-material'
|
||||||
import { Avatar, Box, Chip, Grid, Typography } from '@mui/joy'
|
import { Avatar, Box, Chip, Grid, Typography } from '@mui/joy'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React, { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import Calendar from 'react-calendar'
|
import Calendar from 'react-calendar'
|
||||||
import 'react-calendar/dist/Calendar.css'
|
import 'react-calendar/dist/Calendar.css'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { UserContext } from '../../contexts/UserContext'
|
import { useCircleMembers, useUserProfile } from '../../queries/UserQueries'
|
||||||
import { useCircleMembers } from '../../queries/UserQueries'
|
|
||||||
import { TASK_COLOR } from '../../utils/Colors'
|
import { TASK_COLOR } from '../../utils/Colors'
|
||||||
import './Calendar.css'
|
import './Calendar.css'
|
||||||
|
|
||||||
@@ -17,7 +16,8 @@ const getAssigneeColor = (assignee, userProfile) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CalendarView = ({ chores }) => {
|
const CalendarView = ({ chores }) => {
|
||||||
const { userProfile } = React.useContext(UserContext)
|
const { data: userProfile } = useUserProfile()
|
||||||
|
|
||||||
const [selectedDate, setSeletedDate] = useState(null)
|
const [selectedDate, setSeletedDate] = useState(null)
|
||||||
const Navigate = useNavigate()
|
const Navigate = useNavigate()
|
||||||
|
|
||||||
|
|||||||
@@ -402,3 +402,80 @@ export const parseAssignees = (inputSentence, users) => {
|
|||||||
}
|
}
|
||||||
return { result: null, cleanedSentence: sentence }
|
return { result: null, cleanedSentence: sentence }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const parseDueDate = (inputSentence, chrono) => {
|
||||||
|
// Parse the due date using chrono
|
||||||
|
const parsedDueDate = chrono.parse(inputSentence, new Date(), {
|
||||||
|
forwardDate: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!parsedDueDate[0] || parsedDueDate[0].index === -1) {
|
||||||
|
return {
|
||||||
|
result: null,
|
||||||
|
highlight: [],
|
||||||
|
cleanedSentence: inputSentence,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const dueDateMatch = parsedDueDate[0]
|
||||||
|
const dueDateText = dueDateMatch.text
|
||||||
|
const dueDateStartIndex = dueDateMatch.index
|
||||||
|
const dueDateEndIndex = dueDateStartIndex + dueDateText.length
|
||||||
|
|
||||||
|
// Define words that might precede the due date and should be removed
|
||||||
|
const precedingWords = [
|
||||||
|
'starting',
|
||||||
|
'from',
|
||||||
|
'beginning',
|
||||||
|
'begin',
|
||||||
|
'commence',
|
||||||
|
'commencing',
|
||||||
|
]
|
||||||
|
|
||||||
|
// Look for preceding words before the due date
|
||||||
|
let cleanStartIndex = dueDateStartIndex
|
||||||
|
let highlightStartIndex = dueDateStartIndex
|
||||||
|
let precedingWord = ''
|
||||||
|
|
||||||
|
// Extract text before the due date to check for preceding words
|
||||||
|
const textBeforeDueDate = inputSentence.substring(0, dueDateStartIndex).trim()
|
||||||
|
|
||||||
|
for (const word of precedingWords) {
|
||||||
|
// Check if the text before due date ends with this preceding word
|
||||||
|
const wordPattern = new RegExp(`\\b${word}\\s*$`, 'i')
|
||||||
|
const match = textBeforeDueDate.match(wordPattern)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
// Found a preceding word, include it in the text to be removed
|
||||||
|
const matchStart = textBeforeDueDate.length - match[0].length
|
||||||
|
cleanStartIndex = matchStart
|
||||||
|
highlightStartIndex = matchStart
|
||||||
|
precedingWord = match[0].trim()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the highlight text
|
||||||
|
const fullHighlightText = precedingWord
|
||||||
|
? `${precedingWord} ${dueDateText}`
|
||||||
|
: dueDateText
|
||||||
|
|
||||||
|
// Create cleaned sentence by removing the full match (preceding word + due date)
|
||||||
|
const textToRemove = inputSentence.substring(cleanStartIndex, dueDateEndIndex)
|
||||||
|
const cleanedSentence = inputSentence
|
||||||
|
.replace(textToRemove, '')
|
||||||
|
.replace(/\s+/g, ' ') // Replace multiple spaces with single space
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: dueDateMatch.start.date(),
|
||||||
|
highlight: [
|
||||||
|
{
|
||||||
|
text: fullHighlightText,
|
||||||
|
start: highlightStartIndex,
|
||||||
|
end: dueDateEndIndex,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cleanedSentence: cleanedSentence,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user