diff --git a/PLAYSTORE_DATENSCHUTZERKLAERUNG.md b/PLAYSTORE_DATENSCHUTZERKLAERUNG.md new file mode 100644 index 0000000..ac1d7aa --- /dev/null +++ b/PLAYSTORE_DATENSCHUTZERKLAERUNG.md @@ -0,0 +1,72 @@ +# Datenschutz und Account-Löschung für Google Play + +**App:** TimeClock / Stechuhr +**Datenschutz-URL:** https://stechuhr3.tsschulz.de/datenschutz +**Account-Lösch-URL:** https://stechuhr3.tsschulz.de/account-loeschen +**Stand:** 15.05.2026 + +## Vor Veröffentlichung ergänzen + +- Ladungsfähige Anschrift des Verantwortlichen in der Web-Datenschutzerklärung ersetzen: + `BITTE VOR VERÖFFENTLICHUNG ERGÄNZEN` +- Prüfen, ob ein Datenschutzbeauftragter benannt werden muss. +- Konkrete Backup- und Server-Log-Löschfristen ergänzen, falls diese festgelegt sind. + +## Kurztext für Play Console / Datenschutzangaben + +TimeClock ist eine Zeiterfassungsanwendung. Die App verarbeitet personenbezogene Daten zur Anmeldung, Zeiterfassung, Korrektur, Auswertung und Verwaltung persönlicher Arbeitszeiten. + +Verantwortlich für die Datenverarbeitung ist Torsten Schulz. Kontakt: kontakt@tsschulz.de. + +Die Datenschutzerklärung ist öffentlich abrufbar unter: +https://stechuhr3.tsschulz.de/datenschutz + +Die öffentliche Seite zur Account- und Datenlöschung ist abrufbar unter: +https://stechuhr3.tsschulz.de/account-loeschen + +## FAQ-Text für den Play Store + +### Welche Daten verarbeitet TimeClock? + +TimeClock verarbeitet Accountdaten wie Name, E-Mail-Adresse, Passwort-Hash und Login-Status. Außerdem verarbeitet die App Zeiterfassungsdaten wie Kommen, Gehen, Pausen, Korrekturen und Zeitstempel sowie Abwesenheitsdaten wie Urlaub, Krankheit, Feiertage und Arbeitszeitwünsche. Zusätzlich werden Einstellungen wie Bundesland, Soll-Arbeitszeit, Anzeigeoptionen und Berechtigungen gespeichert. Für Betrieb und Sicherheit können technische Daten wie IP-Adresse, Zugriffszeitpunkt, Geräte-/Browserinformationen und Server-Logs verarbeitet werden. + +Bei optionaler Google-Anmeldung verarbeitet TimeClock die von Google übermittelte Google-Konto-ID, E-Mail-Adresse und den Anzeigenamen. Das Google-Passwort wird nicht an TimeClock übermittelt oder gespeichert. + +### Wofür werden die Daten genutzt? + +Die Daten werden genutzt, um die Zeiterfassung bereitzustellen, Nutzer zu authentifizieren, Arbeitszeiten zu speichern und auszuwerten, Korrekturen und Abwesenheiten zu verwalten, den Dienst abzusichern, Fehler zu analysieren und gesetzliche oder vertragliche Anforderungen an Arbeitszeitdaten zu erfüllen. + +### Auf welcher Rechtsgrundlage werden Daten verarbeitet? + +Die Verarbeitung erfolgt insbesondere auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO zur Bereitstellung des Accounts und der Zeiterfassung, Art. 6 Abs. 1 lit. c DSGVO für gesetzliche Aufbewahrungs- oder Nachweispflichten, Art. 6 Abs. 1 lit. f DSGVO für Sicherheit, Fehleranalyse, Missbrauchsschutz und Server-Logs sowie Art. 6 Abs. 1 lit. a DSGVO für optionale Funktionen, soweit eine Einwilligung erforderlich ist. + +### Werden Daten verkauft oder zu Werbezwecken weitergegeben? + +Nein. Personenbezogene Daten werden nicht verkauft. Eine Weitergabe erfolgt nur, soweit sie für Betrieb, Authentifizierung, Hosting, E-Mail-Versand, Fehleranalyse oder gesetzliche Pflichten erforderlich ist. + +### Welche Drittanbieter können beteiligt sein? + +Für den Betrieb können Hosting-/Serveranbieter und E-Mail-Dienstleister eingesetzt werden. Bei Nutzung der optionalen Google-Anmeldung ist Google als OAuth-Anbieter beteiligt. + +### Wie lange werden Daten gespeichert? + +Account-, Zeit- und Einstellungsdaten werden grundsätzlich gespeichert, solange der Account besteht. Nach Account-Löschung werden die aktiven Accountdaten, Anmeldedaten, Google-Verknüpfungen, Zeitbuchungen, Korrekturen, Urlaubs- und Krankmeldungen sowie persönliche Einstellungen gelöscht. Server-Logs und Backups können technisch bedingt bis zum Ablauf regulärer Löschzyklen weiterbestehen. Daten können länger aufbewahrt werden, wenn gesetzliche Pflichten oder die Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen dies erfordern. + +### Wie können Nutzer ihren Account löschen? + +Eingeloggte Nutzer können ihren Account in der Web-UI unter "Persönliches" löschen. Zusätzlich steht eine öffentliche Löschseite zur Verfügung: +https://stechuhr3.tsschulz.de/account-loeschen + +Über diese Seite können Nutzer auch ohne installierte App eine Löschung per E-Mail an kontakt@tsschulz.de beantragen. + +### Welche Daten werden bei Account-Löschung gelöscht? + +Gelöscht werden Accountdaten, Anmeldedaten, Google-Verknüpfungen, Zeitbuchungen, Zeitkorrekturen, Urlaubs- und Krankmeldungen, Zeitwünsche, Wochenarbeitszeiten und persönliche Einstellungen. + +### Welche Rechte haben Nutzer? + +Nutzer können Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung und Datenübertragbarkeit verlangen sowie einer Verarbeitung widersprechen. Außerdem besteht ein Beschwerderecht bei einer zuständigen Datenschutzaufsichtsbehörde. Anfragen können an kontakt@tsschulz.de gerichtet werden. + +### Wie werden Daten geschützt? + +Die Übertragung erfolgt über HTTPS. Passwörter werden nicht im Klartext gespeichert. Zugriffstokens und OAuth-Verknüpfungen werden serverseitig verwaltet. Zugriffe werden auf das erforderliche Maß beschränkt. diff --git a/backend/src/controllers/AuthController.js b/backend/src/controllers/AuthController.js index 7497f4b..61f5763 100644 --- a/backend/src/controllers/AuthController.js +++ b/backend/src/controllers/AuthController.js @@ -257,7 +257,31 @@ class AuthController { }); } } + + /** + * Eigenen Account und personenbezogene Daten löschen + * DELETE /api/auth/account + */ + async deleteAccount(req, res) { + try { + const userId = req.user.userId; + const { confirmation } = req.body; + + await authService.deleteAccount(userId, confirmation); + + res.json({ + success: true, + message: 'Account wurde gelöscht' + }); + } catch (error) { + console.error('Fehler beim Löschen des Accounts:', error); + + res.status(400).json({ + success: false, + error: error.message + }); + } + } } module.exports = new AuthController(); - diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 0a1c681..3d098e9 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -20,6 +20,7 @@ router.post('/logout', authenticateToken, authController.logout.bind(authControl router.get('/me', authenticateToken, authController.getCurrentUser.bind(authController)); router.post('/change-password', authenticateToken, authController.changePassword.bind(authController)); router.get('/validate', authenticateToken, authController.validateToken.bind(authController)); +router.delete('/account', authenticateToken, authController.deleteAccount.bind(authController)); router.post('/google/link-url', authenticateToken, oauthController.createGoogleLinkUrl.bind(oauthController)); router.post('/oauth/link-current', authenticateToken, oauthController.linkPendingToCurrentUser.bind(oauthController)); router.get('/identities', authenticateToken, oauthController.getIdentities.bind(oauthController)); diff --git a/backend/src/services/AuthService.js b/backend/src/services/AuthService.js index 651a1d3..f8cfa17 100644 --- a/backend/src/services/AuthService.js +++ b/backend/src/services/AuthService.js @@ -2,6 +2,7 @@ const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); const nodemailer = require('nodemailer'); +const { Op } = require('sequelize'); const database = require('../config/database'); /** @@ -667,6 +668,86 @@ ${frontendUrl} return true; } + async destroyOptional(model, where, transaction) { + try { + return await model.destroy({ where, transaction }); + } catch (error) { + if (error.original?.code === 'ER_NO_SUCH_TABLE') { + return 0; + } + throw error; + } + } + + /** + * Eigenen Account inkl. personenbezogener Daten löschen. + * @param {number} userId - Benutzer-ID + * @param {string} confirmation - Muss "ACCOUNT LÖSCHEN" sein + * @returns {Promise} Erfolg + */ + async deleteAccount(userId, confirmation) { + if (confirmation !== 'ACCOUNT LÖSCHEN') { + throw new Error('Bitte bestätigen Sie die Löschung mit "ACCOUNT LÖSCHEN"'); + } + + const { + User, + AuthInfo, + AuthToken, + AuthIdentity, + Worklog, + Timefix, + WeeklyWorktime, + Vacation, + Sick, + Timewish, + Invitation, + Watcher + } = database.getModels(); + + return database.sequelize.transaction(async (transaction) => { + const user = await User.findByPk(userId, { transaction }); + if (!user) { + throw new Error('Benutzer nicht gefunden'); + } + + const authInfo = await AuthInfo.findOne({ + where: { user_id: userId }, + transaction + }); + + if (authInfo) { + await AuthToken.destroy({ where: { auth_info_id: authInfo.id }, transaction }); + await AuthIdentity.destroy({ where: { auth_info_id: authInfo.id }, transaction }); + } + + await this.destroyOptional(Watcher, { user_id: userId }, transaction); + await this.destroyOptional(Invitation, { inviter_user_id: userId }, transaction); + + await WeeklyWorktime.destroy({ where: { user_id: userId }, transaction }); + await Timewish.destroy({ where: { user_id: userId }, transaction }); + await Vacation.destroy({ where: { user_id: userId }, transaction }); + await Sick.destroy({ where: { user_id: userId }, transaction }); + await Timefix.destroy({ where: { user_id: userId }, transaction }); + + await Worklog.destroy({ + where: { + user_id: userId, + relatedTo_id: { [Op.ne]: null } + }, + transaction + }); + await Worklog.destroy({ where: { user_id: userId }, transaction }); + + if (authInfo) { + await AuthInfo.destroy({ where: { id: authInfo.id }, transaction }); + } + + await User.destroy({ where: { id: userId }, transaction }); + return true; + }); + } + /** * Benutzer-Profil abrufen * @param {number} userId - Benutzer-ID @@ -700,4 +781,3 @@ ${frontendUrl} } module.exports = new AuthService(); - diff --git a/frontend/images/stechuhr.png b/frontend/images/stechuhr.png index c13effb..f6f7f31 100644 Binary files a/frontend/images/stechuhr.png and b/frontend/images/stechuhr.png differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6384b76..086823e 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -78,6 +78,8 @@ const pageTitle = computed(() => { 'settings-timewish': 'Zeitwünsche', 'settings-invite': 'Einladen', 'settings-permissions': 'Berechtigungen', + 'privacy': 'Datenschutz', + 'account-deletion': 'Account löschen', 'export': 'Export', 'entries': 'Einträge', 'stats': 'Statistiken' @@ -280,4 +282,3 @@ const pageTitle = computed(() => { font-size: 0.9rem; } - diff --git a/frontend/src/components/SideMenu.vue b/frontend/src/components/SideMenu.vue index ae64a8d..2bb64b4 100644 --- a/frontend/src/components/SideMenu.vue +++ b/frontend/src/components/SideMenu.vue @@ -82,7 +82,9 @@ const SECTIONS_USER = [ { label: 'Paßwort ändern', to: '/settings/password' }, { label: 'Zeitwünsche', to: '/settings/timewish' }, { label: 'Zugriffe verwalten', to: '/settings/permissions' }, - { label: 'Einladen', to: '/settings/invite' } + { label: 'Einladen', to: '/settings/invite' }, + { label: 'Account löschen', to: '/account-loeschen' }, + { label: 'Datenschutz', to: '/datenschutz' } ] } ] @@ -189,5 +191,3 @@ const toggleSection = (title) => { font-weight: 500; } - - diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 7a94074..fdadb67 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -9,6 +9,8 @@ import Register from '../views/Register.vue' import PasswordForgot from '../views/PasswordForgot.vue' import PasswordReset from '../views/PasswordReset.vue' import OAuthCallback from '../views/OAuthCallback.vue' +import AccountDeletion from '../views/AccountDeletion.vue' +import Privacy from '../views/Privacy.vue' import WeekOverview from '../views/WeekOverview.vue' import Timefix from '../views/Timefix.vue' import Vacation from '../views/Vacation.vue' @@ -57,6 +59,16 @@ const router = createRouter({ name: 'oauth-callback', component: OAuthCallback }, + { + path: '/datenschutz', + name: 'privacy', + component: Privacy + }, + { + path: '/account-loeschen', + name: 'account-deletion', + component: AccountDeletion + }, // Geschützte Routes { @@ -192,4 +204,3 @@ router.beforeEach(async (to, from, next) => { }) export default router - diff --git a/frontend/src/views/AccountDeletion.vue b/frontend/src/views/AccountDeletion.vue new file mode 100644 index 0000000..85a8ea1 --- /dev/null +++ b/frontend/src/views/AccountDeletion.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 4a3968e..bfe7b64 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -101,6 +101,10 @@ Passwort vergessen | Registrieren + | + Account löschen + | + Datenschutz diff --git a/frontend/src/views/Privacy.vue b/frontend/src/views/Privacy.vue new file mode 100644 index 0000000..13c9c2e --- /dev/null +++ b/frontend/src/views/Privacy.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/frontend/src/views/Profile.vue b/frontend/src/views/Profile.vue index 5e61038..fe27475 100644 --- a/frontend/src/views/Profile.vue +++ b/frontend/src/views/Profile.vue @@ -106,6 +106,31 @@ + +
+

Account löschen

+

+ Löscht den Account, die Anmeldung, Google-Verknüpfungen, Zeitbuchungen, + Korrekturen, Urlaubs- und Krankmeldungen sowie persönliche Einstellungen dauerhaft. +

+ + + +
@@ -138,6 +163,9 @@ const authStore = useAuthStore() const availableStates = ref([]) const identities = ref([]) const loading = ref(false) +const deleting = ref(false) +const deletePhrase = 'ACCOUNT LÖSCHEN' +const deleteConfirmation = ref('') const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal() @@ -302,6 +330,35 @@ async function unlinkGoogle() { } } +async function deleteAccount() { + const confirmed = await confirm( + 'Der Account und alle personenbezogenen Daten werden dauerhaft gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.', + 'Account dauerhaft löschen' + ) + if (!confirmed) return + + try { + deleting.value = true + const response = await fetch(`${API_URL}/auth/account`, { + method: 'DELETE', + headers: authStore.getAuthHeaders(), + body: JSON.stringify({ confirmation: deleteConfirmation.value }) + }) + const result = await response.json() + if (!response.ok || !result.success) { + throw new Error(result.error || 'Account konnte nicht gelöscht werden') + } + + await alert('Account wurde gelöscht', 'Erfolg') + authStore.clearAuth() + router.push('/login') + } catch (error) { + await alert(`Fehler: ${error.message}`, 'Fehler') + } finally { + deleting.value = false + } +} + // Initiales Laden onMounted(async () => { await Promise.all([ @@ -408,6 +465,41 @@ onMounted(async () => { margin-top: 10px; } +.danger-zone { + margin-top: 32px; + padding-top: 24px; + border-top: 1px solid #f0c2c2; + display: flex; + flex-direction: column; + gap: 10px; +} + +.danger-zone h2 { + margin: 0; + color: #a94442; + font-size: 20px; +} + +.danger-zone p { + margin: 0; + color: #555; + line-height: 1.5; +} + +.danger-zone label { + font-weight: 600; + font-size: 14px; + color: #333; +} + +.danger-zone input { + padding: 10px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + font-family: inherit; +} + .btn { padding: 10px 20px; border: none; @@ -434,4 +526,13 @@ onMounted(async () => { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(76, 175, 80, 0.4); } + +.btn-danger { + background: #d9534f; + color: white; +} + +.btn-danger:hover:not(:disabled) { + background: #c9302c; +} diff --git a/frontend/src/views/Register.vue b/frontend/src/views/Register.vue index 7e69111..9c20552 100644 --- a/frontend/src/views/Register.vue +++ b/frontend/src/views/Register.vue @@ -83,6 +83,8 @@
Bereits registriert? Jetzt einloggen + | + Datenschutz
diff --git a/mobile-app/composeApp/build.gradle.kts b/mobile-app/composeApp/build.gradle.kts index 23cf35f..77c9ddb 100644 --- a/mobile-app/composeApp/build.gradle.kts +++ b/mobile-app/composeApp/build.gradle.kts @@ -23,8 +23,8 @@ android { applicationId = "de.tsschulz.timeclock" minSdk = 26 targetSdk = 36 - versionCode = 6 - versionName = "0.8.0-alpha5" + versionCode = 7 + versionName = "0.8.0-alpha6" buildConfigField("String", "API_BASE_URL", "\"${apiBaseUrl.replace("\\", "\\\\").replace("\"", "\\\"")}\"") } diff --git a/mobile-app/composeApp/release/composeApp-release.aab b/mobile-app/composeApp/release/composeApp-release.aab index 26e7f8f..4950166 100644 Binary files a/mobile-app/composeApp/release/composeApp-release.aab and b/mobile-app/composeApp/release/composeApp-release.aab differ diff --git a/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/TimeClockApp.kt b/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/TimeClockApp.kt index e1dc090..c2b2b01 100644 --- a/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/TimeClockApp.kt +++ b/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/TimeClockApp.kt @@ -159,6 +159,7 @@ fun TimeClockApp( settingsViewModel = settingsViewModel, adminState = adminState, adminViewModel = adminViewModel, + openUrl = openUrl, ) } } @@ -181,6 +182,7 @@ private fun DemoScreen( settingsViewModel: SettingsViewModel, adminState: de.tsschulz.timeclock.ui.admin.AdminUiState, adminViewModel: AdminViewModel, + openUrl: (String) -> Unit, ) { when (route) { AppRoute.Week -> WeekOverviewScreen( @@ -289,6 +291,10 @@ private fun DemoScreen( onSend = { settingsViewModel.sendInvite(it) }, ) } + AppRoute.AccountDeletion -> AccountDeletionScreen( + onOpenDeletionPage = { openUrl("https://stechuhr3.tsschulz.de/account-loeschen") } + ) + AppRoute.Privacy -> PrivacyScreen() AppRoute.Holidays -> HolidaysAdminScreen( state = adminState, isTablet = isTablet, @@ -302,6 +308,79 @@ private fun DemoScreen( } } +@Composable +private fun AccountDeletionScreen(onOpenDeletionPage: () -> Unit) { + TcCard { + SectionTitle("Account und Daten löschen") + PrivacySection( + "Wie lösche ich meinen Account?", + "Die vollständige Account-Löschung erfolgt in der Web-UI unter Persönliches. Dort werden Accountdaten, Anmeldedaten, Google-Verknüpfungen, Zeitbuchungen, Korrekturen, Urlaubs- und Krankmeldungen sowie persönliche Einstellungen gelöscht." + ) + PrivacySection( + "Kann ich die Löschung ohne App beantragen?", + "Ja. Die öffentliche Löschseite enthält die Schritte und eine Kontaktmöglichkeit per E-Mail." + ) + Row(modifier = Modifier.padding(top = TcSpacing.Lg)) { + TcButton( + text = "Löschseite öffnen", + variant = ButtonVariant.Danger, + onClick = onOpenDeletionPage, + ) + } + } +} + +@Composable +private fun PrivacyScreen() { + TcCard { + SectionTitle("Datenschutzerklärung") + Text("Stand: 15.05.2026", color = TcColors.TextMuted, fontSize = 13.sp) + PrivacySection( + "Verantwortlicher", + "Verantwortlich für die Datenverarbeitung in TimeClock ist Torsten Schulz. Anschrift: BITTE VOR VERÖFFENTLICHUNG ERGÄNZEN. Kontakt: kontakt@tsschulz.de." + ) + PrivacySection( + "Zweck der App", + "TimeClock ist eine Zeiterfassungsanwendung. Die App verarbeitet Daten zur Anmeldung, Zeiterfassung, Auswertung und Verwaltung persönlicher Arbeitszeiten." + ) + PrivacySection( + "Welche Daten werden verarbeitet?", + "Verarbeitet werden Accountdaten, Zeitbuchungen, Korrekturen, Urlaub, Krankheit, Feiertage, Arbeitszeitwünsche, Einstellungen, technische Zugriffsdaten und bei Google-Anmeldung die von Google übermittelte Konto-ID, E-Mail-Adresse und der Anzeigename." + ) + PrivacySection( + "Rechtsgrundlagen", + "Die Verarbeitung erfolgt insbesondere zur Bereitstellung der Zeiterfassung, zur Erfüllung gesetzlicher Pflichten sowie für Sicherheit, Fehleranalyse und Missbrauchsschutz." + ) + PrivacySection( + "Google-Anmeldung", + "Die Google-Anmeldung ist optional. Das Google-Passwort wird nicht an TimeClock übermittelt oder gespeichert." + ) + PrivacySection( + "Speicherung und Löschung", + "Daten werden gespeichert, solange der Account besteht oder gesetzliche Aufbewahrungspflichten bestehen. Nutzer können ihren Account in der Web-UI unter Persönliches löschen oder eine Löschung über https://stechuhr3.tsschulz.de/account-loeschen beantragen." + ) + PrivacySection( + "Rechte der Nutzer", + "Nutzer können Auskunft, Berichtigung, Löschung, Einschränkung der Verarbeitung und Datenübertragbarkeit verlangen sowie einer Verarbeitung widersprechen. Anfragen können an kontakt@tsschulz.de gerichtet werden." + ) + PrivacySection( + "Sicherheit", + "Die Übertragung erfolgt über HTTPS. Passwörter werden nicht im Klartext gespeichert. Zugriffstokens und OAuth-Verknüpfungen werden serverseitig verwaltet." + ) + } +} + +@Composable +private fun PrivacySection(title: String, body: String) { + Column( + modifier = Modifier.padding(top = TcSpacing.Lg), + verticalArrangement = Arrangement.spacedBy(TcSpacing.Xs), + ) { + Text(title, color = TcColors.Text, fontSize = 16.sp, fontWeight = FontWeight.SemiBold) + Text(body, color = TcColors.Text, fontSize = 14.sp, lineHeight = 20.sp) + } +} + @Composable private fun WeekOverviewScreen( week: WeekOverviewDto?, diff --git a/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/MockData.kt b/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/MockData.kt index b759040..701ee19 100644 --- a/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/MockData.kt +++ b/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/MockData.kt @@ -20,6 +20,8 @@ val userSections = listOf( MenuItem("Zeitwünsche", AppRoute.Timewish), MenuItem("Zugriffe verwalten", AppRoute.Permissions), MenuItem("Einladen", AppRoute.Invite), + MenuItem("Account löschen", AppRoute.AccountDeletion), + MenuItem("Datenschutz", AppRoute.Privacy), ), ), MenuSection( diff --git a/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/UiModels.kt b/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/UiModels.kt index 544bcb0..3b486fe 100644 --- a/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/UiModels.kt +++ b/mobile-app/composeApp/src/main/kotlin/de/tsschulz/timeclock/ui/model/UiModels.kt @@ -25,6 +25,8 @@ enum class AppRoute(val title: String) { Timewish("Zeitwünsche"), Permissions("Zugriffe verwalten"), Invite("Einladen"), + AccountDeletion("Account löschen"), + Privacy("Datenschutz"), Holidays("Feiertage"), Roles("Rechte"), }