mirror of
https://github.com/narrrl/holy-flavor-rater.git
synced 2026-11-04 17:07:01 +01:00
Merge branch 'main' into narl.io
This commit is contained in:
commit
53687d3ca8
3 files changed
+142
-141
No files matched your search
+100
-101
@@ -26,7 +26,8 @@ import {
|
||||
ListItemAvatar,
|
||||
ListSubheader,
|
||||
Badge,
|
||||
alpha
|
||||
alpha,
|
||||
useMediaQuery
|
||||
} from '@mui/material';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
@@ -82,6 +83,7 @@ const GlobalSearch = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const isMobile = useMediaQuery('(max-width:900px)');
|
||||
|
||||
// Debounced search
|
||||
useEffect(() => {
|
||||
@@ -122,14 +124,14 @@ const GlobalSearch = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1, mx: { xs: 1, sm: 2, md: 4 } }}>
|
||||
<Box sx={{ flexGrow: 1, mx: isMobile ? 1 : 4 }}>
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
freeSolo
|
||||
size="small"
|
||||
loading={loading}
|
||||
options={options}
|
||||
filterOptions={(x) => x} // Disable local filtering as we do it on server
|
||||
filterOptions={(x) => x}
|
||||
getOptionLabel={(option) => {
|
||||
if (typeof option === 'string') return option;
|
||||
return option.name || '';
|
||||
@@ -209,6 +211,7 @@ const App: React.FC = () => {
|
||||
const [loadingUser, setLoadingUser] = useState(true);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [catOpen, setCatOpen] = useState(false);
|
||||
const isMobile = useMediaQuery('(max-width:900px)');
|
||||
|
||||
const fetchNotifications = async () => {
|
||||
try {
|
||||
@@ -234,7 +237,6 @@ const App: React.FC = () => {
|
||||
if (userRes.data.theme) setThemeName(userRes.data.theme);
|
||||
if (userRes.data.language) i18n.changeLanguage(userRes.data.language);
|
||||
|
||||
// Also fetch following list for the mobile sidebar
|
||||
try {
|
||||
const followRes = await api.get('users/following_list/');
|
||||
const followData = Array.isArray(followRes.data) ? followRes.data : (followRes.data.results || []);
|
||||
@@ -257,11 +259,9 @@ const App: React.FC = () => {
|
||||
};
|
||||
fetchData();
|
||||
|
||||
// Poll for notifications every 60 seconds
|
||||
const interval = setInterval(() => {
|
||||
if (localStorage.getItem('token')) {
|
||||
fetchNotifications();
|
||||
// Also refresh user data for the count
|
||||
api.get('users/me/').then(res => setUser(res.data)).catch(() => {});
|
||||
}
|
||||
}, 60000);
|
||||
@@ -364,14 +364,6 @@ const App: React.FC = () => {
|
||||
|
||||
{user ? (
|
||||
<>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton onClick={(e) => setNotifAnchorEl(e.currentTarget)}>
|
||||
<ListItemText
|
||||
primary="Notifications"
|
||||
secondary={user.unread_notifications_count > 0 ? `${user.unread_notifications_count} unread` : null}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
<ListItem disablePadding>
|
||||
<ListItemButton component={Link} to={`/profile/${user.username}`} onClick={() => setDrawerOpen(false)}>
|
||||
<ListItemText
|
||||
@@ -436,6 +428,66 @@ const App: React.FC = () => {
|
||||
</Box>
|
||||
);
|
||||
|
||||
const notificationsMenu = (
|
||||
<>
|
||||
<IconButton color="inherit" onClick={(e) => setNotifAnchorEl(e.currentTarget)} sx={{ ml: 1 }}>
|
||||
<Badge badgeContent={user?.unread_notifications_count || 0} color="error">
|
||||
<NotificationsIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={notifAnchorEl}
|
||||
open={Boolean(notifAnchorEl)}
|
||||
onClose={() => setNotifAnchorEl(null)}
|
||||
elevation={3}
|
||||
sx={{ mt: 1 }}
|
||||
PaperProps={{ sx: { width: 320, maxHeight: 400 } }}
|
||||
>
|
||||
<Box sx={{ p: 1.5, display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid', borderColor: 'divider' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>Notifications</Typography>
|
||||
{(user?.unread_notifications_count || 0) > 0 && (
|
||||
<Button size="small" onClick={handleMarkAllRead}>Mark all read</Button>
|
||||
)}
|
||||
</Box>
|
||||
{notifications.length === 0 ? (
|
||||
<Box sx={{ p: 4, textAlign: 'center' }}>
|
||||
<Typography variant="body2" color="text.secondary">No notifications yet</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
notifications.map(n => (
|
||||
<MenuItem
|
||||
key={n.id}
|
||||
onClick={() => handleNotificationClick(n)}
|
||||
sx={{
|
||||
py: 1.5,
|
||||
px: 2,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
bgcolor: n.is_read ? 'transparent' : alpha(theme.palette.primary.main, 0.05),
|
||||
whiteSpace: 'normal',
|
||||
display: 'flex',
|
||||
gap: 2,
|
||||
alignItems: 'flex-start'
|
||||
}}
|
||||
>
|
||||
<Avatar src={n.actor_avatar || undefined} sx={{ width: 32, height: 32 }}>
|
||||
{n.actor_username.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" sx={{ lineHeight: 1.4 }}>
|
||||
<strong>{n.actor_username}</strong> {n.notification_type === 'reply' ? 'replied' : 'mentioned you'} on <strong>{n.flavor_name}</strong>
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{formatDate(n.created_at)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
@@ -453,14 +505,14 @@ const App: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
<Toolbar sx={{ minHeight: { xs: 56, sm: 64 }, px: { xs: 1, sm: 4, md: 6 } }}>
|
||||
<Typography variant="h6" component="div" sx={{ fontWeight: 'bold' }}>
|
||||
<Link to="/" style={{ color: 'inherit', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
<Box component="img" src="/favicon.svg" sx={{ width: { xs: 32, sm: 24 }, height: { xs: 32, sm: 24 } }} />
|
||||
<Typography variant="h6" component="div" sx={{ fontWeight: 'bold', mr: 2 }}>
|
||||
<Link to="/" style={{ color: 'inherit', textDecoration: 'none' }}>
|
||||
<Box component="span" sx={{ display: { xs: 'none', sm: 'inline' } }}>Holy Flavors Archive</Box>
|
||||
<Box component="span" sx={{ display: { xs: 'inline', sm: 'none' } }}>HFA</Box>
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: { xs: 'none', md: 'flex' }, ml: 4 }}>
|
||||
<Box sx={{ display: isMobile ? 'none' : 'flex', ml: 2 }}>
|
||||
{user && (
|
||||
<Button
|
||||
color="inherit"
|
||||
@@ -503,100 +555,47 @@ const App: React.FC = () => {
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
{!loadingUser && (
|
||||
user ? (
|
||||
<Box sx={{ display: { xs: 'none', md: 'flex' }, alignItems: 'center' }}>
|
||||
<IconButton color="inherit" onClick={(e) => setNotifAnchorEl(e.currentTarget)} sx={{ ml: 1 }}>
|
||||
<Badge badgeContent={user.unread_notifications_count} color="error">
|
||||
<NotificationsIcon />
|
||||
</Badge>
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={notifAnchorEl}
|
||||
open={Boolean(notifAnchorEl)}
|
||||
onClose={() => setNotifAnchorEl(null)}
|
||||
<>
|
||||
{user && notificationsMenu}
|
||||
|
||||
{user ? (
|
||||
<Box sx={{ display: isMobile ? 'none' : 'flex', ml: 1 }}>
|
||||
<IconButton color="inherit" onClick={(e) => setAnchorEl(e.currentTarget)}>
|
||||
<Avatar src={user.avatar || undefined} sx={{ width: 36, height: 36, border: '2px solid', borderColor: 'primary.main' }}>
|
||||
{user.username.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
elevation={3}
|
||||
sx={{ mt: 1 }}
|
||||
PaperProps={{ sx: { width: 320, maxHeight: 400 } }}
|
||||
>
|
||||
<Box sx={{ p: 1.5, display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderBottom: '1px solid', borderColor: 'divider' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold' }}>Notifications</Typography>
|
||||
{user.unread_notifications_count > 0 && (
|
||||
<Button size="small" onClick={handleMarkAllRead}>Mark all read</Button>
|
||||
)}
|
||||
</Box>
|
||||
{notifications.length === 0 ? (
|
||||
<Box sx={{ p: 4, textAlign: 'center' }}>
|
||||
<Typography variant="body2" color="text.secondary">No notifications yet</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
notifications.map(n => (
|
||||
<MenuItem
|
||||
key={n.id}
|
||||
onClick={() => handleNotificationClick(n)}
|
||||
sx={{
|
||||
py: 1.5,
|
||||
px: 2,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
bgcolor: n.is_read ? 'transparent' : alpha(theme.palette.primary.main, 0.05),
|
||||
whiteSpace: 'normal',
|
||||
display: 'flex',
|
||||
gap: 2,
|
||||
alignItems: 'flex-start'
|
||||
}}
|
||||
>
|
||||
<Avatar src={n.actor_avatar || undefined} sx={{ width: 32, height: 32 }}>
|
||||
{n.actor_username.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" sx={{ lineHeight: 1.4 }}>
|
||||
<strong>{n.actor_username}</strong> {n.notification_type === 'reply' ? 'replied to your review' : 'mentioned you'} on <strong>{n.flavor_name}</strong>
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
{formatDate(n.created_at)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))
|
||||
)}
|
||||
</Menu>
|
||||
|
||||
<IconButton color="inherit" onClick={(e) => setAnchorEl(e.currentTarget)} sx={{ ml: 1 }}>
|
||||
<Avatar src={user.avatar || undefined} sx={{ width: 36, height: 36, border: '2px solid', borderColor: 'primary.main' }}>
|
||||
{user.username ? user.username.charAt(0).toUpperCase() : '?'}
|
||||
</Avatar>
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={() => setAnchorEl(null)}
|
||||
elevation={3}
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
<MenuItem component={Link} to={`/profile/${user.username}`} onClick={() => setAnchorEl(null)}>{t('nav.profile')}</MenuItem>
|
||||
<MenuItem component={Link} to="/dashboard" onClick={() => setAnchorEl(null)}>{t('nav.dashboard')}</MenuItem>
|
||||
<MenuItem component={Link} to="/settings" onClick={() => setAnchorEl(null)}>{t('nav.settings')}</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleLogout} sx={{ color: 'error.main' }}>{t('nav.logout')}</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: { xs: 'none', md: 'block' } }}>
|
||||
<Button variant="contained" component={Link} to="/login" sx={{ borderRadius: 2 }}>Login</Button>
|
||||
</Box>
|
||||
)
|
||||
>
|
||||
<MenuItem component={Link} to={`/profile/${user.username}`} onClick={() => setAnchorEl(null)}>{t('nav.profile')}</MenuItem>
|
||||
<MenuItem component={Link} to="/dashboard" onClick={() => setAnchorEl(null)}>{t('nav.dashboard')}</MenuItem>
|
||||
<MenuItem component={Link} to="/settings" onClick={() => setAnchorEl(null)}>{t('nav.settings')}</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleLogout} sx={{ color: 'error.main' }}>{t('nav.logout')}</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: isMobile ? 'none' : 'block' }}>
|
||||
<Button variant="contained" component={Link} to="/login" sx={{ borderRadius: 2 }}>Login</Button>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Box sx={{ display: { xs: 'flex', md: 'none' } }}>
|
||||
{isMobile && (
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
onClick={() => setDrawerOpen(true)}
|
||||
sx={{ ml: 1 }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Typography, Box, Container, Paper, Divider, Button } from '@mui/material';
|
||||
import { Typography, Box, Container, Paper, Divider, Button, useMediaQuery } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -8,9 +8,10 @@ import { useTitle } from '../hooks/useTitle';
|
||||
const About: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
useTitle(t('nav.about'));
|
||||
const isMobile = useMediaQuery('(max-width:600px)');
|
||||
|
||||
return (
|
||||
<Container maxWidth="md" sx={{ py: 8 }}>
|
||||
<Container maxWidth="md" sx={{ py: { xs: 4, md: 8 }, px: { xs: 2, sm: 3 } }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
component={Link}
|
||||
@@ -21,15 +22,15 @@ const About: React.FC = () => {
|
||||
{t('common.backToArchive')}
|
||||
</Button>
|
||||
|
||||
<Paper sx={{ p: { xs: 3, md: 6 }, borderRadius: 4 }}>
|
||||
<Typography variant="h3" gutterBottom sx={{ fontWeight: 'bold' }}>{t('about.title')}</Typography>
|
||||
<Typography variant="body1" paragraph sx={{ fontSize: '1.1rem', lineHeight: 1.8 }}>
|
||||
<Paper sx={{ p: { xs: 2.5, sm: 4, md: 6 }, borderRadius: 4, overflow: 'hidden' }}>
|
||||
<Typography variant={isMobile ? "h4" : "h3"} gutterBottom sx={{ fontWeight: 'bold' }}>{t('about.title')}</Typography>
|
||||
<Typography variant="body1" paragraph sx={{ fontSize: { xs: '1rem', md: '1.1rem' }, lineHeight: 1.8 }}>
|
||||
{t('about.mission')}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ my: 4 }}>
|
||||
<Typography variant="h5" gutterBottom sx={{ fontWeight: 'bold' }}>{t('about.canDoTitle')}</Typography>
|
||||
<Typography component="ul" sx={{ lineHeight: 2 }}>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ fontWeight: 'bold' }}>{t('about.canDoTitle')}</Typography>
|
||||
<Typography component="ul" sx={{ lineHeight: 2, pl: 2 }}>
|
||||
<li>{t('about.featureExplore')}</li>
|
||||
<li>{t('about.featureRate')}</li>
|
||||
<li>{t('about.featureCommunity')}</li>
|
||||
@@ -41,9 +42,9 @@ const About: React.FC = () => {
|
||||
{t('about.disclaimer')}
|
||||
</Typography>
|
||||
|
||||
<Divider sx={{ my: 6 }} />
|
||||
<Divider sx={{ my: { xs: 4, md: 6 } }} />
|
||||
|
||||
<Typography variant="h4" gutterBottom sx={{ fontWeight: 'bold' }}>{t('about.impressumTitle')}</Typography>
|
||||
<Typography variant={isMobile ? "h5" : "h4"} gutterBottom sx={{ fontWeight: 'bold' }}>{t('about.impressumTitle')}</Typography>
|
||||
<Typography variant="body2" sx={{ lineHeight: 2 }}>
|
||||
<strong>{t('about.legalInfo')}</strong><br />
|
||||
{import.meta.env.VITE_IMPRESSUM_NAME || '[Your Name or Project Name]'}<br />
|
||||
@@ -74,12 +75,12 @@ const About: React.FC = () => {
|
||||
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Typography variant="h6" gutterBottom sx={{ fontWeight: 'bold' }}>{t('about.disclaimerTitle')}</Typography>
|
||||
<Typography variant="caption" color="text.secondary" component="p" sx={{ mb: 2 }}>
|
||||
<Typography variant="caption" color="text.secondary" component="p" sx={{ mb: 2, lineHeight: 1.6 }}>
|
||||
<strong>Haftung für Inhalte:</strong> Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte
|
||||
auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter
|
||||
jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen.
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" component="p">
|
||||
<Typography variant="caption" color="text.secondary" component="p" sx={{ lineHeight: 1.6 }}>
|
||||
<strong>Urheberrecht:</strong> Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten
|
||||
unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der
|
||||
Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors.
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React from 'react';
|
||||
import { Typography, Box, Container, Paper, Button, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { Typography, Box, Container, Paper, Button, ToggleButton, ToggleButtonGroup, useMediaQuery } from '@mui/material';
|
||||
import { Link } from 'react-router-dom';
|
||||
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||
import { useTitle } from '../hooks/useTitle';
|
||||
|
||||
const Privacy: React.FC = () => {
|
||||
useTitle('Privacy Policy / Datenschutzerklärung');
|
||||
const [lang, setLang] = React.useState<'de' | 'en'>('de');
|
||||
const [lang, setLang] = useState<'de' | 'en'>('de');
|
||||
const isMobile = useMediaQuery('(max-width:600px)');
|
||||
|
||||
return (
|
||||
<Container maxWidth="md" sx={{ py: 8 }}>
|
||||
<Container maxWidth="md" sx={{ py: { xs: 4, md: 8 }, px: { xs: 2, sm: 3 } }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4, flexWrap: 'wrap', gap: 2 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
@@ -32,46 +33,46 @@ const Privacy: React.FC = () => {
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
|
||||
<Paper sx={{ p: { xs: 3, md: 6 }, borderRadius: 4 }}>
|
||||
<Paper sx={{ p: { xs: 2.5, sm: 4, md: 6 }, borderRadius: 4, overflow: 'hidden' }}>
|
||||
{lang === 'de' ? (
|
||||
<Box>
|
||||
<Typography variant="h3" gutterBottom sx={{ fontWeight: 'bold' }}>Datenschutzerklärung</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
<Typography variant={isMobile ? "h4" : "h3"} gutterBottom sx={{ fontWeight: 'bold' }}>Datenschutzerklärung</Typography>
|
||||
<Typography variant="body1" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
Der Schutz Ihrer persönlichen Daten ist uns ein wichtiges Anliegen. Nachfolgend informieren wir Sie
|
||||
über die Verarbeitung Ihrer Daten auf unserer Webseite gemäß den Anforderungen der DSGVO.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>1. Verantwortlicher</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>1. Verantwortlicher</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
Verantwortlich für die Datenverarbeitung auf dieser Webseite ist:<br />
|
||||
{import.meta.env.VITE_IMPRESSUM_NAME || '[Ihr Name]'}<br />
|
||||
{import.meta.env.VITE_IMPRESSUM_STREET || '[Ihre Straße]'}<br />
|
||||
{import.meta.env.VITE_IMPRESSUM_CITY || '[Ihre Stadt]'}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>2. Erfassung und Speicherung personenbezogener Daten</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>2. Erfassung und Speicherung personenbezogener Daten</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
<strong>Registrierung:</strong> Wenn Sie ein Konto erstellen, speichern wir Ihren Benutzernamen,
|
||||
Ihre E-Mail-Adresse und Ihr Passwort (verschlüsselt). Diese Daten werden zur Bereitstellung der
|
||||
Community-Funktionen (Bewertungen, Folgen von Nutzern) verwendet. Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO.
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
<strong>Öffentliches Profil:</strong> Ihr Benutzername, Ihr Avatar und Ihre Bewertungen sind für
|
||||
andere Nutzer öffentlich sichtbar. Sie können diese Daten jederzeit in den Einstellungen ändern oder löschen.
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
<strong>Kontolöschung:</strong> Sie können Ihr Konto und alle damit verbundenen Daten jederzeit
|
||||
in den Kontoeinstellungen dauerhaft löschen. Zur Sicherheit ist hierfür eine Verifizierung per E-Mail-Code erforderlich.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>3. Datenübermittlung an Dritte</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>3. Datenübermittlung an Dritte</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
Wir geben Ihre Daten nicht ohne Ihre ausdrückliche Einwilligung an Dritte weiter, es sei denn,
|
||||
dies ist zur Vertragserfüllung oder zur Erfüllung gesetzlicher Verpflichtungen erforderlich.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>4. Ihre Rechte</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>4. Ihre Rechte</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
Sie haben das Recht auf Auskunft (Art. 15 DSGVO), Berichtigung (Art. 16 DSGVO), Löschung (Art. 17 DSGVO)
|
||||
und Einschränkung der Verarbeitung Ihrer Daten. Bitte kontaktieren Sie uns hierzu unter:
|
||||
{import.meta.env.VITE_IMPRESSUM_EMAIL}
|
||||
@@ -79,43 +80,43 @@ const Privacy: React.FC = () => {
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
<Typography variant="h3" gutterBottom sx={{ fontWeight: 'bold' }}>Privacy Policy</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
<Typography variant={isMobile ? "h4" : "h3"} gutterBottom sx={{ fontWeight: 'bold' }}>Privacy Policy</Typography>
|
||||
<Typography variant="body1" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
Protecting your personal data is a top priority for us. Below we inform you about the processing
|
||||
of your data on our website in accordance with the requirements of the GDPR.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>1. Controller</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>1. Controller</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
The party responsible for data processing on this website is:<br />
|
||||
{import.meta.env.VITE_IMPRESSUM_NAME || '[Your Name]'}<br />
|
||||
{import.meta.env.VITE_IMPRESSUM_STREET || '[Your Street]'}<br />
|
||||
{import.meta.env.VITE_IMPRESSUM_CITY || '[Your City]'}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>2. Collection and Storage of Personal Data</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>2. Collection and Storage of Personal Data</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
<strong>Registration:</strong> When you create an account, we store your username, email address,
|
||||
and password (encrypted). This data is used to provide community features (ratings, following users).
|
||||
The legal basis is Art. 6 Para. 1 lit. b GDPR.
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
<strong>Public Profile:</strong> Your username, avatar, and ratings are publicly visible to other users.
|
||||
You can change or delete this data at any time in your settings.
|
||||
</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
<strong>Account Deletion:</strong> You can permanently delete your account and all associated data
|
||||
at any time in your account settings. For security reasons, this requires verification via an email code.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>3. Data Transfer to Third Parties</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>3. Data Transfer to Third Parties</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
We do not pass your data on to third parties without your express consent, unless this is necessary
|
||||
to fulfill a contract or a legal obligation.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5" gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>4. Your Rights</Typography>
|
||||
<Typography variant="body2" paragraph>
|
||||
<Typography variant={isMobile ? "h6" : "h5"} gutterBottom sx={{ mt: 4, fontWeight: 'bold' }}>4. Your Rights</Typography>
|
||||
<Typography variant="body2" paragraph sx={{ lineHeight: 1.7 }}>
|
||||
You have the right to information (Art. 15 GDPR), correction (Art. 16 GDPR), deletion (Art. 17 GDPR),
|
||||
and restriction of the processing of your data. Please contact us at:
|
||||
{import.meta.env.VITE_IMPRESSUM_EMAIL}
|
||||
|
||||
Reference in new issue
Block a user