feat: add policy update notice and handling
- Introduced a new policy update modal to inform users of changes to the Privacy Policy and Terms of Service. - Implemented a service to manage the state of policy updates, including versioning and acknowledgment tracking. - Added a prompt component that surfaces the policy update modal based on user profile and acknowledgment status. - Updated Privacy Policy and Terms of Service documents with new effective dates and content. - Enhanced developer settings to allow manual triggering of the policy update notice for testing purposes.
This commit is contained in:
@@ -69,6 +69,13 @@
|
||||
"typeToSearch": "Type to search"
|
||||
}
|
||||
},
|
||||
"policyUpdate": {
|
||||
"title": "We've updated our Privacy Policy and Terms",
|
||||
"subtitle": "Please take a moment to review them. By continuing to use Donetick, you accept the updated documents.",
|
||||
"readPrivacy": "Privacy Policy",
|
||||
"readTerms": "Terms",
|
||||
"acknowledge": "Got it"
|
||||
},
|
||||
"feedback": {
|
||||
"later": "Maybe later",
|
||||
"sentiment": {
|
||||
|
||||
13
src/constants/policyUpdates.js
Normal file
13
src/constants/policyUpdates.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// The single source of truth for "have the legal documents changed since the
|
||||
// user last saw them". Bump POLICY_VERSION whenever PrivacyPolicyView or
|
||||
// TermsView change in a way users should be told about, and move
|
||||
// POLICY_EFFECTIVE_DATE to the same date shown at the bottom of those pages.
|
||||
//
|
||||
// The notice always points at the documents, so a revision needs nothing here
|
||||
// beyond these two values plus, optionally, a summary.
|
||||
|
||||
export const POLICY_VERSION = 2
|
||||
|
||||
// ISO date. Accounts created on or after this date signed up under the current
|
||||
// documents, so they are never shown the update notice.
|
||||
export const POLICY_EFFECTIVE_DATE = '2026-08-12'
|
||||
90
src/service/PolicyUpdateService.js
Normal file
90
src/service/PolicyUpdateService.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Preferences } from '@capacitor/preferences'
|
||||
|
||||
import {
|
||||
POLICY_EFFECTIVE_DATE,
|
||||
POLICY_VERSION,
|
||||
} from '../constants/policyUpdates'
|
||||
|
||||
const STATE_KEY = 'policyUpdateState'
|
||||
|
||||
const defaultState = {
|
||||
// Highest POLICY_VERSION the user has dismissed. 0 = never acknowledged.
|
||||
acknowledgedVersion: 0,
|
||||
acknowledgedAt: null,
|
||||
// Developer Settings escape hatch; never set in normal use.
|
||||
devForced: false,
|
||||
}
|
||||
|
||||
let cachedState = null
|
||||
|
||||
const readState = async () => {
|
||||
if (cachedState) return cachedState
|
||||
try {
|
||||
const { value } = await Preferences.get({ key: STATE_KEY })
|
||||
cachedState = { ...defaultState, ...(value ? JSON.parse(value) : {}) }
|
||||
} catch (error) {
|
||||
console.warn('PolicyUpdateService: unable to read state', error)
|
||||
cachedState = { ...defaultState }
|
||||
}
|
||||
return cachedState
|
||||
}
|
||||
|
||||
const writeState = async patch => {
|
||||
const current = await readState()
|
||||
cachedState = { ...current, ...patch }
|
||||
try {
|
||||
await Preferences.set({
|
||||
key: STATE_KEY,
|
||||
value: JSON.stringify(cachedState),
|
||||
})
|
||||
} catch (error) {
|
||||
// Worst case the notice is shown once more on the next launch, which is
|
||||
// strictly better than suppressing a legal notice we failed to record.
|
||||
console.warn('PolicyUpdateService: unable to persist state', error)
|
||||
}
|
||||
}
|
||||
|
||||
export const getPolicyUpdateState = readState
|
||||
|
||||
// FeedbackService sees both spellings off the profile endpoint; match it.
|
||||
const getSignupDate = userProfile =>
|
||||
userProfile?.createdAt || userProfile?.created_at || null
|
||||
|
||||
/**
|
||||
* The notice is for people who agreed to an *earlier* revision. Accounts
|
||||
* created on or after the effective date already signed up under the current
|
||||
* documents, so they are silently marked as acknowledged instead of being
|
||||
* interrupted by a change they never experienced.
|
||||
*/
|
||||
export const shouldShowPolicyUpdate = async ({ userProfile } = {}) => {
|
||||
const state = await readState()
|
||||
if (state.devForced) return true
|
||||
if (state.acknowledgedVersion >= POLICY_VERSION) return false
|
||||
|
||||
// An unreadable signup date errs toward showing: a missed legal notice is
|
||||
// worse than one extra dismissal.
|
||||
const signupDate = getSignupDate(userProfile)
|
||||
const signedUpAt = signupDate ? new Date(signupDate) : null
|
||||
|
||||
if (
|
||||
signedUpAt &&
|
||||
!isNaN(signedUpAt.getTime()) &&
|
||||
signedUpAt >= new Date(`${POLICY_EFFECTIVE_DATE}T00:00:00Z`)
|
||||
) {
|
||||
await acknowledgePolicyUpdate()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export const acknowledgePolicyUpdate = () =>
|
||||
writeState({
|
||||
acknowledgedVersion: POLICY_VERSION,
|
||||
acknowledgedAt: new Date().toISOString(),
|
||||
devForced: false,
|
||||
})
|
||||
|
||||
/** Developer Settings only: replay the notice on the next eligible mount. */
|
||||
export const resetPolicyUpdate = () =>
|
||||
writeState({ acknowledgedVersion: 0, acknowledgedAt: null, devForced: true })
|
||||
@@ -45,6 +45,7 @@ import CalendarDual from '../components/CalendarDual'
|
||||
import CalendarMonthly from '../components/CalendarMonthly.jsx'
|
||||
import FeedbackPrompt from '../components/FeedbackPrompt.jsx'
|
||||
import LoadingComponent from '../components/Loading'
|
||||
import PolicyUpdatePrompt from '../components/PolicyUpdatePrompt.jsx'
|
||||
import { useLabels } from '../Labels/LabelQueries'
|
||||
import AdvancedFilterBuilder from '../Modals/Inputs/AdvancedFilterBuilder'
|
||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||
@@ -1514,6 +1515,7 @@ const MyChores = () => {
|
||||
/>
|
||||
</Box>
|
||||
<NotificationAccessSnackbar />
|
||||
<PolicyUpdatePrompt />
|
||||
<FeedbackPrompt />
|
||||
{addTaskModalOpen && (
|
||||
<TaskInput
|
||||
|
||||
81
src/views/Modals/PolicyUpdateModal.jsx
Normal file
81
src/views/Modals/PolicyUpdateModal.jsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { ChevronRight, Gavel, PrivacyTip } from '@mui/icons-material'
|
||||
import { Button, Stack } from '@mui/joy'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import ModalActions from '../../components/common/ModalActions.jsx'
|
||||
import { useResponsiveModal } from '../../hooks/useResponsiveModal.js'
|
||||
|
||||
/**
|
||||
* One-time notice that the Privacy Policy and Terms changed. The frame is
|
||||
* generic and always points at the documents, so a future revision only needs
|
||||
* POLICY_VERSION bumped.
|
||||
*/
|
||||
const PolicyUpdateModal = ({ onAcknowledge, onClose, open }) => {
|
||||
const { t } = useTranslation()
|
||||
const { ResponsiveModal } = useResponsiveModal()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleClose = () => {
|
||||
onAcknowledge?.()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const openDocument = path => {
|
||||
handleClose()
|
||||
navigate(path)
|
||||
}
|
||||
|
||||
const documentButtonSx = {
|
||||
justifyContent: 'flex-start',
|
||||
fontWeight: 500,
|
||||
'--Button-gap': '12px',
|
||||
'& .MuiButton-endDecorator': { ml: 'auto' },
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponsiveModal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
size='sm'
|
||||
title={t('policyUpdate.title')}
|
||||
description={t('policyUpdate.subtitle')}
|
||||
footer={
|
||||
<ModalActions
|
||||
primary={{
|
||||
label: t('policyUpdate.acknowledge'),
|
||||
onClick: handleClose,
|
||||
sx: { width: { xs: '100%', sm: 'auto' } },
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Stack spacing={1}>
|
||||
<Button
|
||||
variant='outlined'
|
||||
color='neutral'
|
||||
fullWidth
|
||||
startDecorator={<PrivacyTip />}
|
||||
endDecorator={<ChevronRight />}
|
||||
onClick={() => openDocument('/privacy')}
|
||||
sx={documentButtonSx}
|
||||
>
|
||||
{t('policyUpdate.readPrivacy')}
|
||||
</Button>
|
||||
<Button
|
||||
variant='outlined'
|
||||
color='neutral'
|
||||
fullWidth
|
||||
startDecorator={<Gavel />}
|
||||
endDecorator={<ChevronRight />}
|
||||
onClick={() => openDocument('/terms')}
|
||||
sx={documentButtonSx}
|
||||
>
|
||||
{t('policyUpdate.readTerms')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</ResponsiveModal>
|
||||
)
|
||||
}
|
||||
|
||||
export default PolicyUpdateModal
|
||||
@@ -1,97 +1,144 @@
|
||||
const PrivacyPolicyView = () => {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '800px',
|
||||
margin: '0 auto',
|
||||
padding: '20px',
|
||||
lineHeight: '1.6',
|
||||
}}
|
||||
>
|
||||
<h1>Privacy Policy</h1>
|
||||
<p>
|
||||
Favoro LLC ("we," "us," or "our") operates the Donetick application and
|
||||
website (collectively, the "Service"). This Privacy Policy informs you
|
||||
of our policies regarding the collection, use, and disclosure of
|
||||
personal data when you use our Service and the choices you have
|
||||
associated with that data.
|
||||
</p>
|
||||
<h2>Information We Collect</h2>
|
||||
<p>
|
||||
<strong>Personal Data:</strong> When you register for an account or use
|
||||
the Service, we may collect certain personally identifiable information,
|
||||
such as your name and email address.
|
||||
<em>How Donetick handles your data</em>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Usage Data:</strong> We collect information on how you use the
|
||||
Service, such as your IP address, browser type, pages visited, and the
|
||||
time and date of your visit.
|
||||
<strong>Last updated August 12, 2026</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Donetick is made by Favoro LLC ("we", "us", "our"). This policy covers
|
||||
the Donetick Cloud Service and our website. If you run Donetick
|
||||
yourself, your data lives on your own server and you control it. Only
|
||||
the analytics section below applies, and only if you opt in.
|
||||
</p>
|
||||
|
||||
<h2>1. What we collect</h2>
|
||||
<p>
|
||||
• <strong>Account info:</strong> your email, your name, and the circle
|
||||
(household or group) you belong to.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Task Data:</strong> We store the tasks and chores you create
|
||||
within the app, including their details and any assigned users.
|
||||
</p>
|
||||
<h2>How We Use Your Information</h2>
|
||||
<p>
|
||||
<strong>Provide and Maintain the Service:</strong> We use your
|
||||
information to operate, maintain, and improve the Service.
|
||||
• <strong>Your content:</strong> the tasks, chores, labels, notes, and
|
||||
schedules you create.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Communicate with You:</strong> We may use your email address to
|
||||
send you notifications, updates, and promotional materials related to
|
||||
the Service.
|
||||
• <strong>Usage data:</strong> basic, privacy-respecting information
|
||||
about how the app is used so we can keep it working and improve it.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Analyze Usage:</strong> We analyze usage data to understand how
|
||||
the Service is used and to make improvements.
|
||||
• <strong>Billing:</strong> if you upgrade, payment is handled by
|
||||
Stripe. We never see or store your full card details.
|
||||
</p>
|
||||
<h2>How We Share Your Information</h2>
|
||||
|
||||
<h2>2. How we use it</h2>
|
||||
<p>
|
||||
<strong>With Your Consent:</strong> We will not share your personal data
|
||||
with third parties without your consent, except as described in this
|
||||
Privacy Policy.
|
||||
To run the core product: store and sync your tasks across your devices,
|
||||
remind you when things are due, share chores with your circle, and
|
||||
provide support. We use your content to operate the Service for you, not
|
||||
to build advertising profiles, and never to train AI models.
|
||||
</p>
|
||||
|
||||
<h2>3. Analytics and error reports</h2>
|
||||
<p>
|
||||
We use{' '}
|
||||
<a href='https://posthog.com' target='_blank' rel='noreferrer'>
|
||||
PostHog
|
||||
</a>{' '}
|
||||
to see which features are used and to catch crashes. Every event is
|
||||
checked against a fixed list of allowed properties before it is sent, so
|
||||
only this can leave your device: feature usage (for example, that a task
|
||||
was created and whether it had a due date), technical details about your
|
||||
installation (platform, OS version, app version, Cloud or self-hosted),
|
||||
whether your account is on a paid plan and how many people are in your
|
||||
circle, and, for errors, status codes and stack traces.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Service Providers:</strong> We may engage third-party companies
|
||||
or individuals to perform services on our behalf (e.g., hosting,
|
||||
analytics). These third parties have access to your personal data only
|
||||
to perform these tasks and are obligated not to disclose or use it for
|
||||
any other purpose.
|
||||
We never send the content of your tasks, chores, notes, search queries,
|
||||
circle names, or label names. We don't use session recording or
|
||||
automatic click capture.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Compliance with Law:</strong> We may disclose your personal data
|
||||
if required to do so by law or in response to valid requests by public
|
||||
authorities (e.g., a court or government agency).
|
||||
For our public website we also use{' '}
|
||||
<a href='https://plausible.io' target='_blank' rel='noreferrer'>
|
||||
Plausible
|
||||
</a>
|
||||
, a privacy-friendly analytics tool that counts page views without
|
||||
cookies, without cross-site tracking, and without building a profile of
|
||||
you.
|
||||
</p>
|
||||
<h2>Security</h2>
|
||||
|
||||
<h2>4. Your analytics choices</h2>
|
||||
<p>
|
||||
We value your privacy and have implemented reasonable security measures
|
||||
to protect your personal data from unauthorized access, disclosure,
|
||||
alteration, or destruction. However, no method of transmission over the
|
||||
Internet or electronic storage is 100% secure, and we cannot guarantee
|
||||
absolute security.
|
||||
Product analytics and crash reporting are two separate switches under{' '}
|
||||
<strong>Settings → Privacy & Analytics</strong>. On the Cloud
|
||||
Service they're on by default and you can turn them off at any time. On
|
||||
self-hosted installations they're off by default and require you to opt
|
||||
in. Turning one off stops new data of that type immediately.
|
||||
</p>
|
||||
<h2>Your Choices</h2>
|
||||
|
||||
<h2>5. Where it lives</h2>
|
||||
<p>
|
||||
<strong>Account Information:</strong> You can update or correct your
|
||||
account information at any time.
|
||||
Cloud Service data is stored on our hosted infrastructure, scoped so
|
||||
that only you and the circle you share with can access it. Files you
|
||||
upload, like task photos and attachments, are kept in secure cloud
|
||||
object storage provided by Cloudflare, and are served over private,
|
||||
expiring links rather than public URLs. We use reasonable safeguards to
|
||||
protect all of it, though no system on the internet is perfectly secure.
|
||||
</p>
|
||||
|
||||
<h2>6. Sharing</h2>
|
||||
<p>
|
||||
<strong>Marketing Communications:</strong> You can opt out of receiving
|
||||
promotional emails by following the unsubscribe instructions included in
|
||||
those emails.
|
||||
We don't sell your data. We share it only with the service providers
|
||||
that make Donetick work (Cloudflare for hosting and file storage,
|
||||
PostHog and Plausible for analytics, Stripe for payments), or when
|
||||
required by law.
|
||||
</p>
|
||||
<h2>Children's Privacy</h2>
|
||||
|
||||
<h2>7. Your choices</h2>
|
||||
<p>
|
||||
Our Service is not intended for children under 13 years of age. We do
|
||||
not knowingly collect personal data from children under 13. If you are a
|
||||
parent or guardian and you are aware that your child has provided us
|
||||
with personal data, please contact us.
|
||||
You can update your profile and permanently delete your account at any
|
||||
time from <strong>Settings</strong>. Deleting your account removes your
|
||||
content from our systems. You can also opt out of promotional emails
|
||||
using the unsubscribe link in any of them.
|
||||
</p>
|
||||
<h2>Changes to This Privacy Policy</h2>
|
||||
|
||||
<h2>8. Retention</h2>
|
||||
<p>
|
||||
We may update our Privacy Policy from time to time. We will notify you
|
||||
of any changes by posting the new Privacy Policy on this page and
|
||||
updating the "Effective Date" at the top of this Privacy Policy.
|
||||
We keep your data while your account is active. When you delete your
|
||||
account, we delete your content (backups age out on a rolling basis).
|
||||
</p>
|
||||
<h2>Contact Us</h2>
|
||||
|
||||
<h2>9. Children</h2>
|
||||
<p>
|
||||
If you have any questions about this Privacy Policy, please contact us
|
||||
at:
|
||||
Donetick isn't directed to children under 13, and we don't knowingly
|
||||
collect their data. Child accounts created by a parent inside a circle
|
||||
are managed by that parent.
|
||||
</p>
|
||||
|
||||
<h2>10. Changes</h2>
|
||||
<p>
|
||||
We'll post any updates here with a new date, and flag material changes
|
||||
with a one-time notice inside the app.
|
||||
</p>
|
||||
|
||||
<h2>11. Contact</h2>
|
||||
<p>
|
||||
Privacy questions or requests? Email{' '}
|
||||
<a href='mailto:support@donetick.com'>support@donetick.com</a>.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
<p>Favoro LLC</p>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Refresh, Star, Token } from '@mui/icons-material'
|
||||
import { Box, Button, Card, Chip, Divider, Typography } from '@mui/joy'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import { networkManager } from '../../hooks/NetworkManager'
|
||||
import useConfirmationModal from '../../hooks/useConfirmationModal'
|
||||
import { useSSEContext } from '../../hooks/useSSEContext'
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
setDevForcedPrompt,
|
||||
} from '../../service/FeedbackService'
|
||||
import { useNotification } from '../../service/NotificationProvider'
|
||||
import { resetPolicyUpdate } from '../../service/PolicyUpdateService'
|
||||
import { apiClient } from '../../utils/ApiClient'
|
||||
import { commandQueue } from '../../utils/CommandQueue'
|
||||
import { RefreshToken } from '../../utils/Fetcher'
|
||||
@@ -24,18 +26,19 @@ import { syncEngine } from '../../utils/SyncEngine'
|
||||
import { getRefreshTokenExpiry, isNative } from '../../utils/TokenStorage'
|
||||
import FeedbackModal from '../Modals/FeedbackModal'
|
||||
import ConfirmationModal from '../Modals/Inputs/ConfirmationModal'
|
||||
import PolicyUpdateModal from '../Modals/PolicyUpdateModal'
|
||||
|
||||
const DeveloperSettings = () => {
|
||||
const queryClient = useQueryClient()
|
||||
const { confirmModalConfig, showConfirmation } = useConfirmationModal()
|
||||
const { data: userProfile } = useUserProfile()
|
||||
const {
|
||||
isConnected,
|
||||
isConnecting,
|
||||
lastEvent,
|
||||
error: sseError,
|
||||
getConnectionStatus,
|
||||
getDebugInfo,
|
||||
isConnected,
|
||||
isConnecting,
|
||||
lastEvent,
|
||||
} = useSSEContext()
|
||||
|
||||
const [accessTokenExpiry, setAccessTokenExpiry] = useState(null)
|
||||
@@ -53,6 +56,7 @@ const DeveloperSettings = () => {
|
||||
const [isLoadingNotifications, setIsLoadingNotifications] = useState(false)
|
||||
const [isResettingSync, setIsResettingSync] = useState(false)
|
||||
const [feedbackModalOpen, setFeedbackModalOpen] = useState(false)
|
||||
const [policyModalOpen, setPolicyModalOpen] = useState(false)
|
||||
const [feedbackEligibility, setFeedbackEligibility] = useState(null)
|
||||
const [syncDiagnostics, setSyncDiagnostics] = useState({
|
||||
cursor: null,
|
||||
@@ -394,6 +398,15 @@ const DeveloperSettings = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const handleForcePolicyUpdate = async () => {
|
||||
await resetPolicyUpdate()
|
||||
showNotification({
|
||||
type: 'success',
|
||||
message:
|
||||
'Next visit to My Chores will show the policy notice after ~1.5s',
|
||||
})
|
||||
}
|
||||
|
||||
const handleResetFeedbackState = async () => {
|
||||
await resetFeedbackState()
|
||||
await refreshFeedbackEligibility()
|
||||
@@ -854,6 +867,35 @@ const DeveloperSettings = () => {
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
<Card variant='outlined'>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Typography level='title-lg'>Policy Update Notice</Typography>
|
||||
<Divider />
|
||||
<Typography level='body-sm' color='neutral'>
|
||||
Shown once per POLICY_VERSION to accounts created before the policy
|
||||
effective date. "Force Next Prompt" clears the
|
||||
acknowledgement, then open My Chores to see the automatic trigger.
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='soft'
|
||||
onClick={() => setPolicyModalOpen(true)}
|
||||
>
|
||||
Open Notice
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant='outlined'
|
||||
color='neutral'
|
||||
onClick={handleForcePolicyUpdate}
|
||||
>
|
||||
Force Next Prompt
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
|
||||
{isNativePlatform && (
|
||||
<Card variant='outlined'>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
@@ -1210,6 +1252,13 @@ const DeveloperSettings = () => {
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Preview only — deliberately does not acknowledge, so opening it here
|
||||
never suppresses the real notice. */}
|
||||
<PolicyUpdateModal
|
||||
open={policyModalOpen}
|
||||
onClose={() => setPolicyModalOpen(false)}
|
||||
/>
|
||||
|
||||
<ConfirmationModal config={confirmModalConfig} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,247 +2,241 @@ import React from 'react'
|
||||
|
||||
const TermsView = () => {
|
||||
return (
|
||||
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px', lineHeight: '1.6' }}>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: '800px',
|
||||
margin: '0 auto',
|
||||
padding: '20px',
|
||||
lineHeight: '1.6',
|
||||
}}
|
||||
>
|
||||
<h1>Terms of Service</h1>
|
||||
<p><em>Effective Date: January 1, 2024</em></p>
|
||||
|
||||
<p>
|
||||
These Terms of Service ("Terms") govern your access to and use of the
|
||||
Donetick task management platform provided by Favoro LLC ("Donetick", "we", "us", or "our").
|
||||
By accessing or using our services, you agree to be bound by these Terms. If you do not agree to
|
||||
these Terms, you may not access or use our services.
|
||||
<strong>Last updated August 12, 2026</strong>
|
||||
</p>
|
||||
|
||||
<div style={{ backgroundColor: '#f5f5f5', padding: '15px', borderRadius: '5px', margin: '20px 0' }}>
|
||||
<h3>Important: Cloud vs. Self-Hosted Services</h3>
|
||||
<p>
|
||||
Donetick is available in two deployment models with different terms and responsibilities:
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Cloud-Hosted Service:</strong> Hosted at donetick.com where we provide infrastructure and data management</li>
|
||||
<li><strong>Self-Hosted Service:</strong> Open-source software you deploy on your own infrastructure</li>
|
||||
</ul>
|
||||
<p>Please review the applicable sections carefully as they define different rights and obligations.</p>
|
||||
</div>
|
||||
|
||||
<h2>1. Definitions</h2>
|
||||
<ul>
|
||||
<li><strong>"Cloud Service"</strong> refers to the Donetick platform hosted at donetick.com and managed by Favoro LLC</li>
|
||||
<li><strong>"Self-Hosted Service"</strong> refers to the open-source Donetick software deployed on user-controlled infrastructure</li>
|
||||
<li><strong>"Content"</strong> means all data, information, tasks, files, and other materials you create, upload, or store using our services</li>
|
||||
<li><strong>"Circle"</strong> means a collaborative workspace shared between users for task management</li>
|
||||
</ul>
|
||||
|
||||
<h2>2. Eligibility and Account Registration</h2>
|
||||
<ul>
|
||||
<li>You must be at least 13 years old to use our services</li>
|
||||
<li>Users under 18 must have parental consent</li>
|
||||
<li>You are responsible for maintaining the confidentiality of your account credentials</li>
|
||||
<li>You must provide accurate and complete information when creating an account</li>
|
||||
<li>One person or legal entity may maintain only one account</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Cloud-Hosted Service Terms</h2>
|
||||
<p><em>This section applies only to users of the Cloud Service at donetick.com</em></p>
|
||||
|
||||
<h3>3.1 Service Availability and Uptime</h3>
|
||||
<ul>
|
||||
<li>We strive to maintain 99.5% uptime for the Cloud Service on a monthly basis</li>
|
||||
<li>Scheduled maintenance will be announced at least 24 hours in advance when possible</li>
|
||||
<li>We reserve the right to temporarily suspend service for emergency maintenance</li>
|
||||
<li>No SLA credits or compensation are provided for downtime unless otherwise specified in a separate agreement</li>
|
||||
</ul>
|
||||
|
||||
<h3>3.2 Data Ownership and Responsibility</h3>
|
||||
<ul>
|
||||
<li><strong>Your Data:</strong> You retain ownership of all Content you create or upload</li>
|
||||
<li><strong>Our Responsibility:</strong> We provide secure hosting, backup, and infrastructure management</li>
|
||||
<li><strong>Data Portability:</strong> You can export your data at any time through our export features</li>
|
||||
<li><strong>Data Retention:</strong> We retain your data for 90 days after account deletion to allow recovery</li>
|
||||
</ul>
|
||||
|
||||
<h3>3.3 Subscriptions and Billing</h3>
|
||||
<ul>
|
||||
<li>Cloud Service subscriptions are billed monthly or annually in advance</li>
|
||||
<li>Subscription fees will automatically renew unless cancelled before the next billing cycle</li>
|
||||
<li>You may cancel your subscription at any time through your account settings</li>
|
||||
<li>Upon cancellation, you retain access until the end of your current billing period</li>
|
||||
<li>We may modify subscription prices with 30 days advance notice</li>
|
||||
<li>Refunds are considered on a case-by-case basis at our discretion</li>
|
||||
</ul>
|
||||
|
||||
<h3>3.4 Cloud Service Limitations</h3>
|
||||
<ul>
|
||||
<li>Storage limits apply based on your subscription tier</li>
|
||||
<li>API rate limits may be enforced to ensure service stability</li>
|
||||
<li>We may suspend accounts that exceed reasonable usage limits</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Self-Hosted Service Terms</h2>
|
||||
<p><em>This section applies to users deploying Donetick on their own infrastructure</em></p>
|
||||
|
||||
<h3>4.1 Open Source License</h3>
|
||||
<ul>
|
||||
<li>The Self-Hosted Service is provided under the MIT License</li>
|
||||
<li>You may modify, distribute, and use the software for any purpose</li>
|
||||
<li>Attribution to Donetick must be maintained in derivative works</li>
|
||||
<li>No warranty or support is provided for self-hosted deployments</li>
|
||||
</ul>
|
||||
|
||||
<h3>4.2 User Responsibility</h3>
|
||||
<ul>
|
||||
<li><strong>Infrastructure:</strong> You are solely responsible for hosting, maintenance, security, and backups</li>
|
||||
<li><strong>Data Protection:</strong> You are the data controller and responsible for compliance with applicable laws</li>
|
||||
<li><strong>Updates:</strong> You are responsible for applying security updates and patches</li>
|
||||
<li><strong>Support:</strong> No technical support is provided for self-hosted installations</li>
|
||||
</ul>
|
||||
|
||||
<h3>4.3 Limitation of Our Responsibility</h3>
|
||||
<ul>
|
||||
<li>We provide no guarantees about the performance or security of self-hosted deployments</li>
|
||||
<li>We are not responsible for any data loss, security breaches, or service interruptions in self-hosted environments</li>
|
||||
<li>Technical support is limited to community forums and documentation</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Acceptable Use Policy</h2>
|
||||
<h2>1. Agreement</h2>
|
||||
<p>
|
||||
<em>Applies to both Cloud and Self-Hosted Services</em>
|
||||
By creating an account or using Donetick Cloud at donetick.com (the
|
||||
"Service"), you agree to these Terms. If you don't agree, don't use the
|
||||
Service. Donetick is made by Favoro LLC ("we", "us", "our").
|
||||
</p>
|
||||
|
||||
<h3>You may not use our services to:</h3>
|
||||
<ul>
|
||||
<li>Violate any applicable laws or regulations</li>
|
||||
<li>Infringe on intellectual property rights</li>
|
||||
<li>Transmit malicious code or conduct security attacks</li>
|
||||
<li>Harass, abuse, or harm other users</li>
|
||||
<li>Distribute spam or unwanted communications</li>
|
||||
<li>Attempt to reverse engineer our proprietary systems (Cloud Service)</li>
|
||||
<li>Resell or redistribute the service without permission</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Content and Data</h2>
|
||||
|
||||
<h3>6.1 Your Content Rights</h3>
|
||||
<ul>
|
||||
<li>You retain ownership of all Content you create</li>
|
||||
<li>You grant us the necessary rights to operate the service (Cloud Service only)</li>
|
||||
<li>You are responsible for ensuring you have rights to any Content you upload</li>
|
||||
</ul>
|
||||
|
||||
<h3>6.2 Content Restrictions</h3>
|
||||
<ul>
|
||||
<li>Content must not violate any laws or third-party rights</li>
|
||||
<li>Content must not contain malicious code or harmful materials</li>
|
||||
<li>We may remove Content that violates these Terms (Cloud Service only)</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Privacy and Security</h2>
|
||||
<ul>
|
||||
<li>Your privacy is important to us - please review our Privacy Policy</li>
|
||||
<li>We implement industry-standard security measures (Cloud Service)</li>
|
||||
<li>You are responsible for security in self-hosted deployments</li>
|
||||
<li>Report security vulnerabilities to security@donetick.com</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Intellectual Property</h2>
|
||||
<ul>
|
||||
<li>The Donetick name, logo, and proprietary features are our intellectual property</li>
|
||||
<li>The open-source codebase is licensed under MIT License</li>
|
||||
<li>You may not use our trademarks without written permission</li>
|
||||
</ul>
|
||||
|
||||
<h2>9. Third-Party Integrations</h2>
|
||||
<ul>
|
||||
<li>Donetick integrates with third-party services (Telegram, Discord, webhooks, etc.)</li>
|
||||
<li>Your use of these integrations is subject to their respective terms</li>
|
||||
<li>We are not responsible for third-party service availability or functionality</li>
|
||||
</ul>
|
||||
|
||||
<h2>10. Liability and Warranties</h2>
|
||||
|
||||
<h3>10.1 Disclaimer of Warranties</h3>
|
||||
<h2>2. Cloud vs. self-hosted</h2>
|
||||
<p>Donetick comes in two flavors, and they're governed differently.</p>
|
||||
<p>
|
||||
Our services are provided "as is" and "as available" without any warranty of any kind,
|
||||
express or implied, including but not limited to merchantability, fitness for a particular purpose,
|
||||
or non-infringement.
|
||||
<strong>Donetick Cloud</strong> is the hosted service we run at
|
||||
donetick.com. These Terms cover it.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Self-hosted Donetick</strong> is the open-source software you
|
||||
run on your own server. It's licensed under the GNU Affero General
|
||||
Public License v3 (AGPLv3), and that license, not these Terms, governs
|
||||
your rights to use, modify, and share it. Nothing here is meant to
|
||||
restrict anything the AGPLv3 grants you.
|
||||
</p>
|
||||
<p>
|
||||
If you run Donetick yourself, hosting, security, backups, updates, and
|
||||
data-protection obligations are yours, not ours. We don't provide
|
||||
support guarantees for self-hosted installs unless we've agreed to that
|
||||
separately in writing.
|
||||
</p>
|
||||
|
||||
<h3>10.2 Cloud Service Liability</h3>
|
||||
<ul>
|
||||
<li>Our total liability for the Cloud Service is limited to the amount you paid in the 12 months preceding the claim</li>
|
||||
<li>We are not liable for indirect, incidental, special, or consequential damages</li>
|
||||
<li>We maintain appropriate insurance and implement security best practices</li>
|
||||
</ul>
|
||||
|
||||
<h3>10.3 Self-Hosted Service Liability</h3>
|
||||
<ul>
|
||||
<li>We provide no warranties or guarantees for self-hosted deployments</li>
|
||||
<li>Our liability is limited to the maximum extent permitted by law</li>
|
||||
<li>You assume all risks associated with self-hosting</li>
|
||||
</ul>
|
||||
|
||||
<h2>11. Indemnification</h2>
|
||||
<h2>3. What Donetick does</h2>
|
||||
<p>
|
||||
You agree to indemnify and hold us harmless from any claims, damages, or expenses
|
||||
arising from your use of our services, violation of these Terms, or infringement of any rights.
|
||||
Donetick helps you and the people in your circle track tasks, chores,
|
||||
and recurring schedules. It's a productivity tool, not professional,
|
||||
legal, financial, or medical advice.
|
||||
</p>
|
||||
|
||||
<h2>4. Your account</h2>
|
||||
<p>
|
||||
You're responsible for your account and for keeping your login secure.
|
||||
You must be at least 13 years old (or the age of digital consent where
|
||||
you live) to use the Service. Provide accurate information, and tell us
|
||||
promptly if you think someone else has gotten into your account.
|
||||
</p>
|
||||
|
||||
<h2>5. Acceptable use</h2>
|
||||
<p>
|
||||
Don't use Donetick to break the law, infringe anyone's rights, upload
|
||||
malware, harass other users, send spam, gain unauthorized access to our
|
||||
systems or anyone's account, circumvent usage limits, or overload the
|
||||
Service.
|
||||
</p>
|
||||
<p>
|
||||
Don't use Donetick Cloud to run a competing hosted service without our
|
||||
written permission. This applies only to our Cloud Service and takes
|
||||
nothing away from your AGPLv3 rights to the self-hosted software.
|
||||
</p>
|
||||
|
||||
<h2>6. Your content</h2>
|
||||
<p>
|
||||
You own everything you put into Donetick: your tasks, chores, labels,
|
||||
comments, and files. You grant us a limited license to host, store,
|
||||
process, and transmit it solely to operate, secure, troubleshoot, and
|
||||
improve the Service for you. We don't sell your content, and we don't
|
||||
use it to train AI models.
|
||||
</p>
|
||||
<p>
|
||||
You're responsible for having the right to upload what you upload, and
|
||||
for managing who you share circles and content with. We may remove
|
||||
content when reasonably necessary to enforce these Terms, comply with
|
||||
the law, or address a security issue, and we'll give you notice first
|
||||
where that's practical.
|
||||
</p>
|
||||
<p>
|
||||
How we handle personal information is covered in our{' '}
|
||||
<a href='/privacy'>Privacy Policy</a>, including what our analytics and
|
||||
error reporting do and don't collect, and how to turn them off.
|
||||
</p>
|
||||
|
||||
<h2>7. Availability</h2>
|
||||
<p>
|
||||
We aim for roughly 99% monthly availability on the Cloud Service, but
|
||||
that's a goal, not a service-level agreement, and there are no downtime
|
||||
credits. We may pause parts of the Service for maintenance, security, or
|
||||
emergencies, and we'll announce planned work ahead of time when we
|
||||
reasonably can. Storage limits, API rate limits, and fair-use limits may
|
||||
apply.
|
||||
</p>
|
||||
|
||||
<h2>8. Plans and billing</h2>
|
||||
<p>
|
||||
Donetick offers a free plan and paid plans billed monthly or annually in
|
||||
advance through Stripe. Paid plans renew automatically until you cancel.
|
||||
</p>
|
||||
<p>
|
||||
You can cancel any time from your settings. Cancelling stops future
|
||||
renewals, and you keep paid features through the end of the current
|
||||
billing period. If a payment fails we may retry it or limit paid
|
||||
features until it goes through. We'll give you reasonable notice before
|
||||
a price change applies to your subscription. Refunds are handled
|
||||
case-by-case at our discretion, except where the law requires one.
|
||||
Nothing here limits your consumer-protection rights.
|
||||
</p>
|
||||
|
||||
<h2>9. Your data and export</h2>
|
||||
<p>
|
||||
Where export tools are available, you can export your content; formats
|
||||
and availability may change over time. If we ever discontinue the Cloud
|
||||
Service, we'll give reasonable advance notice and, where possible, a
|
||||
chance to export first.
|
||||
</p>
|
||||
|
||||
<h2>10. Third-party integrations</h2>
|
||||
<p>
|
||||
Donetick connects to outside services like messaging platforms,
|
||||
authentication providers, and webhooks. Those are governed by their own
|
||||
terms and privacy policies, and we're not responsible for services we
|
||||
don't control. Make sure you're authorized to connect whatever you
|
||||
connect.
|
||||
</p>
|
||||
|
||||
<h2>11. Our brand</h2>
|
||||
<p>
|
||||
You own your content; we own the Donetick name, logo, branding, and the
|
||||
proprietary parts of the Cloud Service. The AGPLv3 covers the software,
|
||||
not our trademarks, so please don't use our name or logo in a way that
|
||||
suggests we endorse or sponsor you.
|
||||
</p>
|
||||
|
||||
<h2>12. Termination</h2>
|
||||
|
||||
<h3>12.1 Termination by You</h3>
|
||||
<ul>
|
||||
<li>You may terminate your account at any time</li>
|
||||
<li>Cloud Service: Access continues until the end of your billing period</li>
|
||||
<li>Self-Hosted Service: You may stop using the software at any time</li>
|
||||
</ul>
|
||||
|
||||
<h3>12.2 Termination by Us</h3>
|
||||
<ul>
|
||||
<li>We may terminate accounts that violate these Terms</li>
|
||||
<li>We may discontinue the Cloud Service with 90 days notice</li>
|
||||
<li>We will provide data export capabilities before termination when possible</li>
|
||||
</ul>
|
||||
|
||||
<h2>13. Changes to These Terms</h2>
|
||||
<ul>
|
||||
<li>We may update these Terms from time to time</li>
|
||||
<li>Material changes will be announced via email or in-app notification</li>
|
||||
<li>Continued use after changes constitutes acceptance of new Terms</li>
|
||||
<li>For significant changes, we may require explicit acceptance</li>
|
||||
</ul>
|
||||
|
||||
<h2>14. Dispute Resolution</h2>
|
||||
<ul>
|
||||
<li>We encourage resolving disputes informally by contacting us first</li>
|
||||
<li>These Terms are governed by the laws of Delaware, United States</li>
|
||||
<li>Any disputes will be resolved in the courts of Delaware</li>
|
||||
<li>You may pursue small claims court for eligible disputes</li>
|
||||
</ul>
|
||||
|
||||
<h2>15. Miscellaneous</h2>
|
||||
<ul>
|
||||
<li>If any provision is found unenforceable, the remainder remains in effect</li>
|
||||
<li>Our failure to enforce any right does not waive that right</li>
|
||||
<li>These Terms constitute the entire agreement between us</li>
|
||||
<li>We may assign these Terms; you may not without our consent</li>
|
||||
</ul>
|
||||
|
||||
<h2>16. Contact Information</h2>
|
||||
<p>
|
||||
If you have questions about these Terms, please contact us:
|
||||
You can stop using Donetick and delete your account at any time from
|
||||
Settings. We may suspend or end access if you violate these Terms, or
|
||||
where we need to address abuse, fraud, a security incident, a legal
|
||||
obligation, or unpaid fees. Where it's practical, we'll give you notice
|
||||
and a chance to fix the problem first. After termination we delete or
|
||||
anonymize your content per our retention practices, though limited
|
||||
records may stick around for legal, security, or accounting reasons.
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>Email:</strong> legal@donetick.com</li>
|
||||
<li><strong>Support:</strong> support@donetick.com</li>
|
||||
<li><strong>Address:</strong> Favoro LLC, [Address to be provided]</li>
|
||||
</ul>
|
||||
|
||||
<hr style={{ margin: '40px 0' }} />
|
||||
<p style={{ fontSize: '14px', color: '#666' }}>
|
||||
<strong>Last Updated:</strong> January 1, 2024<br />
|
||||
These Terms of Service are effective immediately for new users and will become effective
|
||||
for existing users 30 days after posting.
|
||||
<h2>13. Security reports</h2>
|
||||
<p>
|
||||
Found a vulnerability? Please email{' '}
|
||||
<a href='mailto:support@donetick.com'>support@donetick.com</a> and give
|
||||
us a reasonable chance to fix it before disclosing it publicly.
|
||||
</p>
|
||||
|
||||
<h2>14. Disclaimers and liability</h2>
|
||||
<p>
|
||||
<strong>
|
||||
THE SERVICE IS PROVIDED "AS IS" AND "AS AVAILABLE," WITHOUT WARRANTIES
|
||||
OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT. WE DO NOT
|
||||
WARRANT THAT THE SERVICE WILL BE UNINTERRUPTED, ERROR-FREE, OR SECURE,
|
||||
OR THAT DATA WILL NEVER BE LOST OR CORRUPTED.
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY LAW, FAVORO LLC IS NOT LIABLE FOR
|
||||
INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE
|
||||
DAMAGES, OR FOR LOST PROFITS, REVENUE, BUSINESS, GOODWILL, OR DATA.
|
||||
OUR TOTAL LIABILITY IS LIMITED TO THE GREATER OF WHAT YOU PAID US IN
|
||||
THE 12 MONTHS BEFORE THE CLAIM, OR $100.
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
Self-hosted installs are covered by the AGPLv3's warranty disclaimer.
|
||||
We're not responsible for problems arising from your own infrastructure,
|
||||
configuration, modifications, or hosting provider. Nothing in these
|
||||
Terms excludes liability that can't legally be excluded.
|
||||
</p>
|
||||
|
||||
<h2>15. Indemnification</h2>
|
||||
<p>
|
||||
To the extent the law allows, you agree to cover Favoro LLC against
|
||||
third-party claims arising from your violation of these Terms, your
|
||||
unlawful use of the Service, your infringement of someone else's rights,
|
||||
or content you submitted. We'll give you reasonable notice of any such
|
||||
claim and cooperate in the defense.
|
||||
</p>
|
||||
|
||||
<h2>16. Disputes and governing law</h2>
|
||||
<p>
|
||||
Before filing anything, please email{' '}
|
||||
<a href='mailto:support@donetick.com'>support@donetick.com</a> and give
|
||||
us a fair chance to sort it out. These Terms are governed by the laws of
|
||||
the State of Delaware, USA, without regard to conflict-of-laws rules,
|
||||
and disputes go to the state or federal courts in Delaware. Either of us
|
||||
can still bring an eligible claim in small claims court, and none of
|
||||
this waives rights that can't legally be waived.
|
||||
</p>
|
||||
|
||||
<h2>17. Changes</h2>
|
||||
<p>
|
||||
We may update these Terms. We'll post the new version with an updated
|
||||
date, and for material changes we'll give reasonable advance notice by
|
||||
email or in-app notice (and get your consent where the law requires it).
|
||||
Continuing to use the Service after non-material changes means you
|
||||
accept them. If you don't agree to a material change, cancel before it
|
||||
takes effect.
|
||||
</p>
|
||||
|
||||
<h2>18. The fine print</h2>
|
||||
<p>
|
||||
These Terms plus our Privacy Policy are the whole agreement between us.
|
||||
If one provision turns out to be unenforceable, it gets trimmed to the
|
||||
minimum necessary and the rest stands. Not enforcing something once
|
||||
doesn't waive it later. You can't transfer these Terms without our
|
||||
consent; we may transfer them in a merger, acquisition, or sale of
|
||||
assets. These Terms don't create a partnership, employment, or agency
|
||||
relationship. Neither of us is liable for delays caused by things
|
||||
outside our reasonable control. Provisions that should survive
|
||||
termination do, including those on content, intellectual property,
|
||||
disclaimers, liability, indemnification, and disputes.
|
||||
</p>
|
||||
|
||||
<h2>19. Contact</h2>
|
||||
<p>
|
||||
Questions about these Terms? Email{' '}
|
||||
<a href='mailto:support@donetick.com'>support@donetick.com</a>.
|
||||
</p>
|
||||
|
||||
<hr />
|
||||
<p>Favoro LLC</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
53
src/views/components/PolicyUpdatePrompt.jsx
Normal file
53
src/views/components/PolicyUpdatePrompt.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useUserProfile } from '../../queries/UserQueries'
|
||||
import {
|
||||
acknowledgePolicyUpdate,
|
||||
shouldShowPolicyUpdate,
|
||||
} from '../../service/PolicyUpdateService'
|
||||
import PolicyUpdateModal from '../Modals/PolicyUpdateModal'
|
||||
|
||||
// Let the screen settle before interrupting, and stay out of the way of the
|
||||
// feedback prompt, which uses a longer delay from the same screen.
|
||||
const OPEN_DELAY_MS = 1500
|
||||
|
||||
/**
|
||||
* Surfaces the policy-change notice once per revision. Mount once, near the
|
||||
* main task list.
|
||||
*/
|
||||
const PolicyUpdatePrompt = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { data: userProfile } = useUserProfile()
|
||||
|
||||
useEffect(() => {
|
||||
if (!userProfile) return
|
||||
|
||||
let timer = null
|
||||
let cancelled = false
|
||||
|
||||
shouldShowPolicyUpdate({ userProfile }).then(eligible => {
|
||||
if (!eligible || cancelled) return
|
||||
timer = setTimeout(() => {
|
||||
if (cancelled) return
|
||||
setOpen(true)
|
||||
}, OPEN_DELAY_MS)
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
if (timer) clearTimeout(timer)
|
||||
}
|
||||
}, [userProfile])
|
||||
|
||||
if (!open) return null
|
||||
|
||||
return (
|
||||
<PolicyUpdateModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onAcknowledge={acknowledgePolicyUpdate}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default PolicyUpdatePrompt
|
||||
Reference in New Issue
Block a user