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:
Mo Tarbin
2026-08-13 22:31:21 -04:00
parent 97a7b12d87
commit e2b41c85f6
9 changed files with 625 additions and 289 deletions

View File

@@ -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": {

View 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'

View 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 })

View File

@@ -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

View 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

View File

@@ -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 &amp; 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>
)

View File

@@ -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. &quot;Force Next Prompt&quot; 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>
)

View File

@@ -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>
)
}

View 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