Delete Account API + UI + Store assets (icon, graphic, screenshot)

- DELETE /api/v1/auth/delete-account (GDPR + Google Play req)
- Settings: red Delete Account section with confirmation
- Store assets: icon-512, feature-graphic, screenshot-1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 23:56:20 +00:00
parent 3f04637550
commit 03d7bd8de6
6 changed files with 69 additions and 43 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -101,6 +101,18 @@ async function authRoutes(app) {
return { status: 'not_implemented', message: 'WebAuthn biometric auth coming soon.' }; return { status: 'not_implemented', message: 'WebAuthn biometric auth coming soon.' };
}); });
// Delete account
app.delete('/auth/account', { preHandler: [async (req) => { await req.jwtVerify(); }] }, async (req, reply) => {
const uid = req.user.id;
await app.db.query('DELETE FROM task_assignments WHERE user_id=$1 OR assigned_by=$1', [uid]);
await app.db.query('DELETE FROM tasks WHERE user_id=$1', [uid]);
await app.db.query('DELETE FROM task_groups WHERE user_id=$1', [uid]);
await app.db.query('DELETE FROM goals WHERE user_id=$1', [uid]);
await app.db.query('DELETE FROM sessions WHERE user_id=$1', [uid]);
await app.db.query('DELETE FROM users WHERE id=$1', [uid]);
return reply.send({ data: { deleted: true } });
});
// OAuth initiate routes moved to ./oauth.js // OAuth initiate routes moved to ./oauth.js
// Search users by name or email (for collaboration) // Search users by name or email (for collaboration)
@@ -117,3 +129,22 @@ async function authRoutes(app) {
} }
module.exports = authRoutes; module.exports = authRoutes;
// Delete account (GDPR + Google Play requirement)
app.delete("/auth/delete-account", { preHandler: [async (req) => { await req.jwtVerify(); }] }, async (req) => {
const userId = req.user.id;
// Delete all user data in order (foreign keys)
await app.db.query("DELETE FROM task_comments WHERE user_id = $1", [userId]);
await app.db.query("DELETE FROM subtasks WHERE assigned_to = $1", [userId]);
await app.db.query("DELETE FROM task_collaboration WHERE from_user_id = $1 OR to_user_id = $1", [userId]);
await app.db.query("DELETE FROM task_assignments WHERE user_id = $1", [userId]);
await app.db.query("DELETE FROM push_subscriptions WHERE user_id = $1", [userId]);
await app.db.query("DELETE FROM goals WHERE user_id = $1", [userId]);
await app.db.query("DELETE FROM connectors WHERE user_id = $1", [userId]);
await app.db.query("DELETE FROM tasks WHERE user_id = $1", [userId]);
await app.db.query("DELETE FROM task_groups WHERE user_id = $1", [userId]);
await app.db.query("DELETE FROM users WHERE id = $1", [userId]);
return { status: "deleted", message: "Account and all data permanently deleted" };
});

View File

@@ -1,54 +1,34 @@
export default function PrivacyPage() { export default function PrivacyPage() {
return ( return (
<div style={{ maxWidth: 800, margin: "0 auto", padding: "40px 24px", fontFamily: "system-ui" }}> <div style={{ maxWidth: 800, margin: "0 auto", padding: "40px 24px", fontFamily: "system-ui", lineHeight: 1.8 }}>
<h1 style={{ fontSize: 28, fontWeight: "bold", marginBottom: 24 }}>Privacy Policy</h1> <h1 style={{ fontSize: 28, fontWeight: "bold", marginBottom: 24 }}>Privacy Policy - Task Team</h1>
<p style={{ color: "#666", marginBottom: 16 }}>Last updated: March 29, 2026</p> <p style={{ color: "#666", marginBottom: 16 }}>Last updated: March 30, 2026</p>
<h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>1. Information We Collect</h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>Data We Collect</h2>
<p>Task Team collects the following information when you create an account:</p> <p>Account info (name, email), task data, GPS location (optional), audio for voice-to-text (optional).</p>
<ul style={{ paddingLeft: 24, marginTop: 8 }}>
<li>Email address</li>
<li>Name</li>
<li>Phone number (optional)</li>
</ul>
<p style={{ marginTop: 8 }}>We also collect task data, goals, and settings you create within the app.</p>
<h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>2. How We Use Your Data</h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>How We Use It</h2>
<p>Your data is used exclusively to provide the Task Team service:</p> <p>Task management, sync across devices, reminders, GPS auto-verify task completion.</p>
<ul style={{ paddingLeft: 24, marginTop: 8 }}>
<li>Task management and organization</li>
<li>AI-powered planning assistance</li>
<li>Calendar and goal tracking</li>
<li>Team collaboration features</li>
<li>Push notifications for task reminders</li>
</ul>
<h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>3. Data Storage</h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>Data Storage</h2>
<p>Your data is stored on secure servers in the European Union (Hetzner, Germany). We use PostgreSQL databases with encrypted connections and regular backups.</p> <p>Your data is stored on secure servers in the European Union (Hetzner Cloud, Germany). We use PostgreSQL databases with encrypted connections and regular backups.</p>
<h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>4. Third-Party Services</h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>Data Sharing</h2>
<ul style={{ paddingLeft: 24, marginTop: 8 }}> <p>We do NOT sell data. Services used: Hetzner Cloud Germany, Anthropic Claude AI.</p>
<li><strong>Anthropic Claude AI</strong> for AI chat and planning features (messages are processed but not stored by Anthropic)</li>
<li><strong>Google OAuth</strong> for optional Google login</li>
<li><strong>Expo</strong> for push notifications</li>
</ul>
<h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>5. Data Sharing</h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>Your Rights (GDPR)</h2>
<p>We do not sell, trade, or share your personal data with third parties. Data is only shared with team members you explicitly invite to collaborate.</p> <p>You have the right to access, correct, and delete your data. Contact: privacy@it-enterprise.cz</p>
<h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>6. Your Rights (GDPR)</h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>Permissions</h2>
<p>You have the right to:</p> <p>Location and microphone are optional, revocable anytime in your device settings.</p>
<ul style={{ paddingLeft: 24, marginTop: 8 }}>
<li>Access your data</li>
<li>Export your data</li>
<li>Delete your account and all data</li>
<li>Modify your personal information</li>
</ul>
<h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>7. Contact</h2> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>Children</h2>
<p>IT Enterprise Solution s.r.o.<br/>Email: apps@it-enterprise.cz<br/>Web: https://it-enterprise.cz</p> <p>Not directed to children under 13.</p>
<p style={{ marginTop: 32, color: "#999", fontSize: 14 }}>© 2026 IT Enterprise Solution s.r.o. All rights reserved.</p> <h2 style={{ fontSize: 20, fontWeight: 600, marginTop: 32, marginBottom: 12 }}>Contact</h2>
<p>IT Enterprise s.r.o. | privacy@it-enterprise.cz | tasks.hasdo.info</p>
<p style={{ marginTop: 32, color: "#999", fontSize: 14 }}>© 2026 IT Enterprise s.r.o. All rights reserved.</p>
</div> </div>
); );
} }

View File

@@ -217,6 +217,21 @@ export default function SettingsPage() {
<div className="text-center text-xs text-muted py-4"> <div className="text-center text-xs text-muted py-4">
<p>{t("common.appName")} {t("common.appVersion")}</p> <p>{t("common.appName")} {t("common.appVersion")}</p>
</div> </div>
<section className="bg-red-50 dark:bg-red-900/20 rounded-xl p-4 border border-red-200 dark:border-red-800 mt-6">
<h2 className="font-semibold text-red-600 dark:text-red-400 mb-2">Smazat ucet</h2>
<p className="text-sm text-red-500 mb-3">Trvale smazat ucet a vsechna data. Tuto akci nelze vratit.</p>
<button onClick={() => {
if (confirm("Opravdu chcete trvale smazat svuj ucet a vsechna data?")) {
const token = localStorage.getItem("taskteam_token");
fetch("/api/v1/auth/delete-account", { method: "DELETE", headers: { Authorization: "Bearer " + token } })
.then(r => r.json())
.then(() => { localStorage.clear(); window.location.href = "/login"; });
}
}} className="px-4 py-2 bg-red-600 text-white rounded-lg text-sm font-medium">
Smazat ucet
</button>
</section>
</div> </div>
); );
} }