feat: Implement AI response feedback UI and complete high-priority features
Frontend Features: - Add MessageFeedback component with thumbs up/down buttons - Positive feedback submits immediately with success toast - Negative feedback opens dialog for optional text input - Integrate feedback buttons on all AI assistant messages - Add success Snackbar confirmation message - Translation keys added to ai.json (feedback section) Backend Features: - Add POST /api/v1/ai/feedback endpoint - Create FeedbackDto with conversation ID validation - Implement submitFeedback service method - Store feedback in conversation metadata with timestamps - Add audit logging for feedback submissions - Fix conversationId regex validation to support nanoid format Legal & Compliance: - Implement complete EULA acceptance flow with modal - Create reusable legal content components (Terms, Privacy, EULA) - Add LegalDocumentViewer for nested modal viewing - Cookie Consent Banner with GDPR compliance - Legal pages with AppShell navigation - EULA acceptance tracking in user entity Branding Updates: - Rebrand from "Maternal App" to "ParentFlow" - Update all icons (72px to 512px) from high-res source - PWA manifest updated with ParentFlow branding - Contact email: hello@parentflow.com - Address: Serbota 3, Bucharest, Romania Bug Fixes: - Fix chat endpoint validation (support nanoid conversation IDs) - Fix EULA acceptance API call (use apiClient vs hardcoded localhost) - Fix icon loading errors with proper PNG generation Documentation: - Mark 11 high-priority features as complete in REMAINING_FEATURES.md - Update feature statistics: 73/139 complete (53%) - All high-priority features now complete! 🎉 Files Changed: Frontend: 21 files (components, pages, locales, icons) Backend: 6 files (controller, service, DTOs, migrations) Docs: 1 file (REMAINING_FEATURES.md) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
283
maternal-web/components/common/banners/CookieConsent.tsx
Normal file
283
maternal-web/components/common/banners/CookieConsent.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
Button,
|
||||
IconButton,
|
||||
Collapse,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Link,
|
||||
Slide,
|
||||
} from '@mui/material';
|
||||
import { Close, Settings as SettingsIcon, Cookie } from '@mui/icons-material';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface CookiePreferences {
|
||||
essential: boolean; // Always true, cannot be disabled
|
||||
analytics: boolean;
|
||||
marketing: boolean;
|
||||
}
|
||||
|
||||
const COOKIE_CONSENT_KEY = 'parentflow_cookie_consent';
|
||||
const COOKIE_PREFERENCES_KEY = 'parentflow_cookie_preferences';
|
||||
|
||||
export function CookieConsent() {
|
||||
const router = useRouter();
|
||||
const [showBanner, setShowBanner] = useState(false);
|
||||
const [showCustomize, setShowCustomize] = useState(false);
|
||||
const [preferences, setPreferences] = useState<CookiePreferences>({
|
||||
essential: true,
|
||||
analytics: false,
|
||||
marketing: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user has already given consent
|
||||
const consent = localStorage.getItem(COOKIE_CONSENT_KEY);
|
||||
if (!consent) {
|
||||
// Show banner after a short delay
|
||||
const timer = setTimeout(() => setShowBanner(true), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
} else {
|
||||
// Load saved preferences
|
||||
const savedPrefs = localStorage.getItem(COOKIE_PREFERENCES_KEY);
|
||||
if (savedPrefs) {
|
||||
setPreferences(JSON.parse(savedPrefs));
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const savePreferences = (prefs: CookiePreferences) => {
|
||||
localStorage.setItem(COOKIE_CONSENT_KEY, 'true');
|
||||
localStorage.setItem(COOKIE_PREFERENCES_KEY, JSON.stringify(prefs));
|
||||
|
||||
// Apply analytics based on user choice
|
||||
if (prefs.analytics) {
|
||||
console.log('📊 Analytics enabled');
|
||||
// TODO: Initialize analytics (Google Analytics, etc.)
|
||||
} else {
|
||||
console.log('📊 Analytics disabled');
|
||||
// TODO: Disable analytics
|
||||
}
|
||||
|
||||
setShowBanner(false);
|
||||
};
|
||||
|
||||
const handleAcceptAll = () => {
|
||||
const allPrefs: CookiePreferences = {
|
||||
essential: true,
|
||||
analytics: true,
|
||||
marketing: true,
|
||||
};
|
||||
setPreferences(allPrefs);
|
||||
savePreferences(allPrefs);
|
||||
};
|
||||
|
||||
const handleRejectAll = () => {
|
||||
const essentialOnly: CookiePreferences = {
|
||||
essential: true,
|
||||
analytics: false,
|
||||
marketing: false,
|
||||
};
|
||||
setPreferences(essentialOnly);
|
||||
savePreferences(essentialOnly);
|
||||
};
|
||||
|
||||
const handleSaveCustom = () => {
|
||||
savePreferences(preferences);
|
||||
};
|
||||
|
||||
const handleToggleCustomize = () => {
|
||||
setShowCustomize(!showCustomize);
|
||||
};
|
||||
|
||||
if (!showBanner) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Slide direction="up" in={showBanner} mountOnEnter unmountOnExit>
|
||||
<Paper
|
||||
elevation={8}
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: (theme) => theme.zIndex.snackbar,
|
||||
borderRadius: '16px 16px 0 0',
|
||||
maxWidth: { xs: '100%', md: 600 },
|
||||
mx: 'auto',
|
||||
mb: 0,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ p: 3, position: 'relative' }}>
|
||||
{/* Close button */}
|
||||
<IconButton
|
||||
onClick={handleRejectAll}
|
||||
sx={{ position: 'absolute', top: 8, right: 8 }}
|
||||
size="small"
|
||||
aria-label="Reject all cookies"
|
||||
>
|
||||
<Close />
|
||||
</IconButton>
|
||||
|
||||
{/* Cookie icon and title */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
|
||||
<Cookie sx={{ color: 'primary.main', fontSize: 28 }} />
|
||||
<Typography variant="h6" fontWeight="600">
|
||||
Cookie Preferences
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Description */}
|
||||
<Typography variant="body2" color="text.secondary" paragraph>
|
||||
We use cookies to enhance your experience, analyze site usage, and assist in our marketing efforts.
|
||||
Essential cookies are required for the app to function.{' '}
|
||||
<Link
|
||||
href="/legal/cookies"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
router.push('/legal/cookies');
|
||||
}}
|
||||
sx={{ fontWeight: 'bold', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
{/* Customize section */}
|
||||
<Collapse in={showCustomize}>
|
||||
<Box sx={{ my: 2, p: 2, bgcolor: 'background.default', borderRadius: 2 }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={preferences.essential}
|
||||
disabled
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight="600">
|
||||
Essential Cookies
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Required for the app to function. Cannot be disabled.
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
sx={{ mb: 1, alignItems: 'flex-start' }}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={preferences.analytics}
|
||||
onChange={(e) => setPreferences({ ...preferences, analytics: e.target.checked })}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight="600">
|
||||
Analytics Cookies
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Help us understand how you use the app (anonymized data).
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
sx={{ mb: 1, alignItems: 'flex-start' }}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={preferences.marketing}
|
||||
onChange={(e) => setPreferences({ ...preferences, marketing: e.target.checked })}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight="600">
|
||||
Marketing Cookies
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Used to deliver relevant content and track campaign performance.
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
sx={{ alignItems: 'flex-start' }}
|
||||
/>
|
||||
</Box>
|
||||
</Collapse>
|
||||
|
||||
{/* Action buttons */}
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: 1,
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
mt: 2,
|
||||
}}
|
||||
>
|
||||
{showCustomize ? (
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleToggleCustomize}
|
||||
fullWidth
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSaveCustom}
|
||||
fullWidth
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
Save Preferences
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={handleRejectAll}
|
||||
fullWidth
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
Reject All
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<SettingsIcon />}
|
||||
onClick={handleToggleCustomize}
|
||||
fullWidth
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
Customize
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleAcceptAll}
|
||||
fullWidth
|
||||
sx={{ textTransform: 'none' }}
|
||||
>
|
||||
Accept All
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Slide>
|
||||
);
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useStreamingChat } from '@/hooks/useStreamingChat';
|
||||
import { MessageFeedback } from './MessageFeedback';
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
@@ -742,39 +743,57 @@ export const AIChatInterface: React.FC = () => {
|
||||
}}
|
||||
>
|
||||
{message.role === 'assistant' ? (
|
||||
<Box
|
||||
sx={{
|
||||
'& p': { mb: 1 },
|
||||
'& strong': { fontWeight: 600 },
|
||||
'& ul, & ol': { pl: 2, mb: 1 },
|
||||
'& li': { mb: 0.5 },
|
||||
'& hr': { my: 2, borderColor: 'divider' },
|
||||
'& h1, & h2, & h3, & h4, & h5, & h6': {
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
mt: 1.5
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
</Box>
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
'& p': { mb: 1 },
|
||||
'& strong': { fontWeight: 600 },
|
||||
'& ul, & ol': { pl: 2, mb: 1 },
|
||||
'& li': { mb: 0.5 },
|
||||
'& hr': { my: 2, borderColor: 'divider' },
|
||||
'& h1, & h2, & h3, & h4, & h5, & h6': {
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
mt: 1.5
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
</Box>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
mt: 1,
|
||||
display: 'block',
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
{message.timestamp.toLocaleTimeString()}
|
||||
</Typography>
|
||||
<MessageFeedback
|
||||
messageId={message.id}
|
||||
conversationId={currentConversationId}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Typography variant="body1" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{message.content}
|
||||
</Typography>
|
||||
<>
|
||||
<Typography variant="body1" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{message.content}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
mt: 1,
|
||||
display: 'block',
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
{message.timestamp.toLocaleTimeString()}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
mt: 1,
|
||||
display: 'block',
|
||||
opacity: 0.7,
|
||||
}}
|
||||
>
|
||||
{message.timestamp.toLocaleTimeString()}
|
||||
</Typography>
|
||||
</Paper>
|
||||
{message.role === 'user' && (
|
||||
<Avatar sx={{ bgcolor: 'secondary.main', mt: 1 }}>
|
||||
|
||||
193
maternal-web/components/features/ai-chat/MessageFeedback.tsx
Normal file
193
maternal-web/components/features/ai-chat/MessageFeedback.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
TextField,
|
||||
Typography,
|
||||
Tooltip,
|
||||
Snackbar,
|
||||
Alert,
|
||||
} from '@mui/material';
|
||||
import { ThumbUp, ThumbDown, ThumbUpOutlined, ThumbDownOutlined } from '@mui/icons-material';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
interface MessageFeedbackProps {
|
||||
messageId: string;
|
||||
conversationId: string | null;
|
||||
}
|
||||
|
||||
export function MessageFeedback({ messageId, conversationId }: MessageFeedbackProps) {
|
||||
const { t } = useTranslation('ai');
|
||||
const [feedbackType, setFeedbackType] = useState<'positive' | 'negative' | null>(null);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [feedbackText, setFeedbackText] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||
|
||||
const handleFeedback = async (type: 'positive' | 'negative') => {
|
||||
// If already submitted this type, ignore
|
||||
if (feedbackType === type) return;
|
||||
|
||||
setFeedbackType(type);
|
||||
|
||||
// For negative feedback, open dialog for additional comments
|
||||
if (type === 'negative') {
|
||||
setDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// For positive feedback, submit immediately
|
||||
await submitFeedback(type, '');
|
||||
};
|
||||
|
||||
const submitFeedback = async (type: 'positive' | 'negative', text: string) => {
|
||||
if (!conversationId) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
await apiClient.post('/api/v1/ai/feedback', {
|
||||
conversationId,
|
||||
messageId,
|
||||
feedbackType: type,
|
||||
feedbackText: text || undefined,
|
||||
});
|
||||
|
||||
console.log(`✅ Feedback submitted: ${type}`);
|
||||
|
||||
// Show success snackbar
|
||||
setSnackbarOpen(true);
|
||||
|
||||
// Close dialog if open
|
||||
if (dialogOpen) {
|
||||
setDialogOpen(false);
|
||||
setFeedbackText('');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to submit feedback:', error);
|
||||
// Reset feedback type on error
|
||||
setFeedbackType(null);
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDialogSubmit = async () => {
|
||||
if (feedbackType) {
|
||||
await submitFeedback(feedbackType, feedbackText);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDialogClose = () => {
|
||||
setDialogOpen(false);
|
||||
setFeedbackText('');
|
||||
// Reset feedback type if closing without submitting
|
||||
if (!feedbackType || feedbackType === 'negative') {
|
||||
setFeedbackType(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', gap: 0.5, mt: 1 }}>
|
||||
<Tooltip title={t('feedback.helpful') || 'Helpful'}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => handleFeedback('positive')}
|
||||
disabled={isSubmitting}
|
||||
sx={{
|
||||
color: feedbackType === 'positive' ? 'success.main' : 'text.secondary',
|
||||
'&:hover': { color: 'success.main' },
|
||||
}}
|
||||
>
|
||||
{feedbackType === 'positive' ? (
|
||||
<ThumbUp fontSize="small" />
|
||||
) : (
|
||||
<ThumbUpOutlined fontSize="small" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={t('feedback.notHelpful') || 'Not helpful'}>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => handleFeedback('negative')}
|
||||
disabled={isSubmitting}
|
||||
sx={{
|
||||
color: feedbackType === 'negative' ? 'error.main' : 'text.secondary',
|
||||
'&:hover': { color: 'error.main' },
|
||||
}}
|
||||
>
|
||||
{feedbackType === 'negative' ? (
|
||||
<ThumbDown fontSize="small" />
|
||||
) : (
|
||||
<ThumbDownOutlined fontSize="small" />
|
||||
)}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
{/* Feedback Dialog for negative feedback */}
|
||||
<Dialog
|
||||
open={dialogOpen}
|
||||
onClose={handleDialogClose}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
{t('feedback.dialogTitle') || 'Help us improve'}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
{t('feedback.dialogMessage') || 'What could have been better about this response?'}
|
||||
</Typography>
|
||||
<TextField
|
||||
autoFocus
|
||||
multiline
|
||||
rows={4}
|
||||
fullWidth
|
||||
placeholder={t('feedback.placeholder') || 'Your feedback (optional)'}
|
||||
value={feedbackText}
|
||||
onChange={(e) => setFeedbackText(e.target.value)}
|
||||
variant="outlined"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleDialogClose}>
|
||||
{t('feedback.cancel') || 'Cancel'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDialogSubmit}
|
||||
variant="contained"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{t('feedback.submit') || 'Submit'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Success Snackbar */}
|
||||
<Snackbar
|
||||
open={snackbarOpen}
|
||||
autoHideDuration={3000}
|
||||
onClose={() => setSnackbarOpen(false)}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||||
>
|
||||
<Alert
|
||||
onClose={() => setSnackbarOpen(false)}
|
||||
severity="success"
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{t('feedback.thankYou') || 'Thank you for your feedback!'}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import { TabBar } from '../TabBar/TabBar';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { ReactNode } from 'react';
|
||||
import { useWebSocket } from '@/hooks/useWebSocket';
|
||||
import { Wifi, WifiOff, People, AccountCircle, Settings, ChildCare, Group, Logout } from '@mui/icons-material';
|
||||
import { Wifi, WifiOff, People, AccountCircle, Settings, ChildCare, Group, Logout, Gavel } from '@mui/icons-material';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
@@ -192,6 +192,13 @@ export const AppShell = ({ children }: AppShellProps) => {
|
||||
<ListItemText>{t('navigation.family')}</ListItemText>
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={() => handleNavigate('/legal/privacy')}>
|
||||
<ListItemIcon>
|
||||
<Gavel fontSize="small" />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Legal & Privacy</ListItemText>
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleLogout}>
|
||||
<ListItemIcon>
|
||||
<Logout fontSize="small" color="error" />
|
||||
|
||||
72
maternal-web/components/legal/EULACheck.tsx
Normal file
72
maternal-web/components/legal/EULACheck.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import { EULADialog } from './EULADialog';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import apiClient from '@/lib/api/client';
|
||||
|
||||
export function EULACheck() {
|
||||
const { user, logout } = useAuth();
|
||||
const router = useRouter();
|
||||
const [showDialog, setShowDialog] = useState(false);
|
||||
const [checking, setChecking] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// Only check EULA acceptance for authenticated users
|
||||
if (user) {
|
||||
console.log('🔍 Checking EULA acceptance:', {
|
||||
userId: user.id,
|
||||
eulaAcceptedAt: user.eulaAcceptedAt,
|
||||
eulaVersion: user.eulaVersion,
|
||||
});
|
||||
|
||||
// Show dialog if user hasn't accepted EULA
|
||||
if (!user.eulaAcceptedAt) {
|
||||
console.log('⚠️ User has not accepted EULA, showing dialog');
|
||||
setShowDialog(true);
|
||||
} else {
|
||||
console.log('✅ EULA already accepted on', user.eulaAcceptedAt);
|
||||
}
|
||||
}
|
||||
setChecking(false);
|
||||
}, [user]);
|
||||
|
||||
const handleAccept = async () => {
|
||||
try {
|
||||
console.log('✅ User accepted EULA, calling API...');
|
||||
|
||||
const response = await apiClient.post('/api/v1/auth/eula/accept', {
|
||||
version: '2025-10-04',
|
||||
});
|
||||
|
||||
console.log('✅ EULA acceptance recorded:', response.data);
|
||||
|
||||
// Reload user data to get updated EULA acceptance
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to accept EULA:', error);
|
||||
alert('Failed to accept EULA. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDecline = async () => {
|
||||
console.log('❌ User declined EULA, logging out...');
|
||||
alert('You must accept the Terms of Service, Privacy Policy, and EULA to use ParentFlow.');
|
||||
await logout();
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
// Don't render anything while checking or if user hasn't loaded
|
||||
if (checking || !user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EULADialog
|
||||
open={showDialog}
|
||||
onAccept={handleAccept}
|
||||
onDecline={handleDecline}
|
||||
/>
|
||||
);
|
||||
}
|
||||
217
maternal-web/components/legal/EULAContent.tsx
Normal file
217
maternal-web/components/legal/EULAContent.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
export function EULAContent() {
|
||||
return (
|
||||
<Box sx={{ '& h5': { mt: 3, mb: 1, fontWeight: 'bold' }, '& h6': { mt: 2, mb: 1, fontWeight: 'bold' }, '& p': { mb: 2 }, '& ul': { mb: 2 } }}>
|
||||
<Typography variant="h5">1. License Grant</Typography>
|
||||
<Typography paragraph>
|
||||
Subject to your compliance with this End User License Agreement ("EULA"), ParentFlow grants you a limited,
|
||||
non-exclusive, non-transferable, revocable license to use the ParentFlow mobile application (the "App")
|
||||
for your personal, non-commercial use.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">2. License Restrictions</Typography>
|
||||
<Typography paragraph>You agree NOT to:</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Copy, modify, or create derivative works of the App</li>
|
||||
<li>Reverse engineer, decompile, or disassemble the App</li>
|
||||
<li>Remove or alter any copyright, trademark, or proprietary notices</li>
|
||||
<li>Rent, lease, loan, sell, or sublicense the App</li>
|
||||
<li>Use the App for any commercial purpose without authorization</li>
|
||||
<li>Use the App in any way that violates applicable laws or regulations</li>
|
||||
<li>Use automated tools or bots to access the App</li>
|
||||
<li>Interfere with or disrupt the App's servers or networks</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">3. Intellectual Property Rights</Typography>
|
||||
<Typography paragraph>
|
||||
The App and all its components, including but not limited to software code, design, graphics, text, and user interface,
|
||||
are owned by ParentFlow and are protected by copyright, trademark, and other intellectual property laws.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
This EULA does not grant you any ownership rights to the App. All rights not expressly granted are reserved by ParentFlow.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">4. User Data and Privacy</Typography>
|
||||
<Typography paragraph>
|
||||
Your use of the App is subject to our Privacy Policy, which explains how we collect, use, and protect your information.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
You retain ownership of all data you input into the App, including activity logs, photos, and personal information.
|
||||
By using the App, you grant us a license to process your data to provide the Service.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">5. Updates and Modifications</Typography>
|
||||
<Typography paragraph>
|
||||
We may release updates, patches, or new versions of the App from time to time. These updates may:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Add new features or functionality</li>
|
||||
<li>Fix bugs or security vulnerabilities</li>
|
||||
<li>Improve performance</li>
|
||||
<li>Remove or modify existing features</li>
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
By continuing to use the App after an update, you accept the updated version and any changes to this EULA.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">6. Medical Disclaimer</Typography>
|
||||
<Typography paragraph sx={{ fontWeight: 'bold', color: 'error.main' }}>
|
||||
THE APP IS NOT A MEDICAL DEVICE AND DOES NOT PROVIDE MEDICAL ADVICE.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
The App's tracking features and AI assistant provide general information and insights only. They are not a substitute
|
||||
for professional medical advice, diagnosis, or treatment. Always seek the advice of qualified healthcare providers
|
||||
with questions regarding your child's health.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
<strong>In medical emergencies, call your local emergency number immediately.</strong>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">7. AI Features and Limitations</Typography>
|
||||
<Typography paragraph>
|
||||
The App includes AI-powered features (such as the parenting assistant) that use machine learning models.
|
||||
You acknowledge that:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>AI responses may not always be accurate or complete</li>
|
||||
<li>AI cannot replace professional judgment or expertise</li>
|
||||
<li>You use AI features at your own risk</li>
|
||||
<li>We are not liable for decisions made based on AI recommendations</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">8. Third-Party Services</Typography>
|
||||
<Typography paragraph>
|
||||
The App may integrate with or link to third-party services (e.g., cloud storage, analytics, payment processors).
|
||||
Your use of these third-party services is governed by their own terms and privacy policies.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
We are not responsible for the availability, content, or practices of third-party services.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">9. Termination</Typography>
|
||||
<Typography variant="h6">9.1 Termination by You</Typography>
|
||||
<Typography paragraph>You may terminate this EULA at any time by:</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Deleting your account through the App settings</li>
|
||||
<li>Uninstalling the App from all your devices</li>
|
||||
<li>Ceasing all use of the App</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">9.2 Termination by Us</Typography>
|
||||
<Typography paragraph>
|
||||
We may terminate or suspend your license to use the App immediately if you:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Violate any terms of this EULA</li>
|
||||
<li>Engage in illegal or harmful activities</li>
|
||||
<li>Fail to pay subscription fees (if applicable)</li>
|
||||
<li>Pose a security risk to the App or other users</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">9.3 Effect of Termination</Typography>
|
||||
<Typography paragraph>Upon termination:</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Your license to use the App ends immediately</li>
|
||||
<li>You must uninstall the App from all devices</li>
|
||||
<li>Your data will be deleted in accordance with our Privacy Policy</li>
|
||||
<li>Provisions of this EULA that should survive termination will remain in effect</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">10. Disclaimers and Warranties</Typography>
|
||||
<Typography paragraph sx={{ fontWeight: 'bold' }}>
|
||||
THE APP IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTIES OF ANY KIND.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY LAW, WE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Warranties of merchantability</li>
|
||||
<li>Warranties of fitness for a particular purpose</li>
|
||||
<li>Warranties of non-infringement</li>
|
||||
<li>Warranties that the App will be error-free or uninterrupted</li>
|
||||
<li>Warranties regarding data accuracy or completeness</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">11. Limitation of Liability</Typography>
|
||||
<Typography paragraph sx={{ fontWeight: 'bold' }}>
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY LAW:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>WE SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, CONSEQUENTIAL, SPECIAL, OR PUNITIVE DAMAGES</li>
|
||||
<li>WE SHALL NOT BE LIABLE FOR ANY LOSS OF DATA, REVENUE, PROFITS, OR BUSINESS OPPORTUNITIES</li>
|
||||
<li>OUR TOTAL LIABILITY SHALL NOT EXCEED THE AMOUNT YOU PAID US IN THE 12 MONTHS PRECEDING THE CLAIM, OR $100, WHICHEVER IS GREATER</li>
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
Some jurisdictions do not allow limitations on liability, so these limitations may not apply to you.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">12. Indemnification</Typography>
|
||||
<Typography paragraph>
|
||||
You agree to indemnify, defend, and hold harmless ParentFlow, its officers, directors, employees, contractors,
|
||||
and agents from any claims, damages, losses, or expenses (including attorney's fees) arising from:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Your use or misuse of the App</li>
|
||||
<li>Your violation of this EULA</li>
|
||||
<li>Your violation of any laws or regulations</li>
|
||||
<li>Your violation of third-party rights</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">13. Export Compliance</Typography>
|
||||
<Typography paragraph>
|
||||
The App may be subject to export control laws and regulations. You agree to comply with all applicable export
|
||||
and import laws and not to export, re-export, or transfer the App in violation of such laws.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">14. Governing Law and Dispute Resolution</Typography>
|
||||
<Typography paragraph>
|
||||
This EULA is governed by the laws of [Your Jurisdiction], without regard to conflict of law principles.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
Any disputes arising from this EULA will be resolved through binding arbitration in accordance with the
|
||||
American Arbitration Association rules, except where prohibited by law.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">15. Severability</Typography>
|
||||
<Typography paragraph>
|
||||
If any provision of this EULA is found to be invalid or unenforceable, that provision will be limited or eliminated
|
||||
to the minimum extent necessary, and the remaining provisions will remain in full force and effect.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">16. Entire Agreement</Typography>
|
||||
<Typography paragraph>
|
||||
This EULA, together with our Terms of Service and Privacy Policy, constitutes the entire agreement between you and ParentFlow regarding the App.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">17. Changes to This EULA</Typography>
|
||||
<Typography paragraph>
|
||||
We may update this EULA from time to time to reflect changes in the App or legal requirements. We will notify you
|
||||
of material changes by email or through the App. Your continued use of the App after changes constitutes acceptance
|
||||
of the updated EULA.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">18. Contact Us</Typography>
|
||||
<Typography paragraph>
|
||||
If you have questions about this EULA, please contact us:
|
||||
</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
<strong>Email:</strong> hello@parentflow.com<br />
|
||||
<strong>Address:</strong> Serbota 3, Bucharest, Romania
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ mt: 4, pt: 3, borderTop: 1, borderColor: 'divider' }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Acceptance of EULA
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
BY CLICKING "I ACCEPT" DURING APP SETUP, CREATING AN ACCOUNT, OR CONTINUING TO USE THE APP, YOU ACKNOWLEDGE
|
||||
THAT YOU HAVE READ, UNDERSTOOD, AND AGREE TO BE BOUND BY THIS EULA.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
IF YOU DO NOT AGREE TO THIS EULA, DO NOT USE THE APP.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
236
maternal-web/components/legal/EULADialog.tsx
Normal file
236
maternal-web/components/legal/EULADialog.tsx
Normal file
@@ -0,0 +1,236 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Link,
|
||||
Alert,
|
||||
Divider,
|
||||
} from '@mui/material';
|
||||
import { Gavel, Warning } from '@mui/icons-material';
|
||||
import { LegalDocumentViewer } from './LegalDocumentViewer';
|
||||
|
||||
interface EULADialogProps {
|
||||
open: boolean;
|
||||
onAccept: () => void;
|
||||
onDecline: () => void;
|
||||
}
|
||||
|
||||
export function EULADialog({ open, onAccept, onDecline }: EULADialogProps) {
|
||||
const [agreedToTerms, setAgreedToTerms] = useState(false);
|
||||
const [agreedToPrivacy, setAgreedToPrivacy] = useState(false);
|
||||
const [agreedToEULA, setAgreedToEULA] = useState(false);
|
||||
const [viewingDocument, setViewingDocument] = useState<{
|
||||
type: 'terms' | 'privacy' | 'eula' | null;
|
||||
title: string;
|
||||
}>({ type: null, title: '' });
|
||||
|
||||
const canAccept = agreedToTerms && agreedToPrivacy && agreedToEULA;
|
||||
|
||||
const handleAccept = () => {
|
||||
if (canAccept) {
|
||||
onAccept();
|
||||
}
|
||||
};
|
||||
|
||||
const openDocument = (type: 'terms' | 'privacy' | 'eula', title: string) => (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setViewingDocument({ type, title });
|
||||
};
|
||||
|
||||
const closeDocumentViewer = () => {
|
||||
setViewingDocument({ type: null, title: '' });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
open={open}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
disableEscapeKeyDown
|
||||
onClose={(event, reason) => {
|
||||
// Prevent closing by clicking outside or pressing ESC
|
||||
if (reason === 'backdropClick' || reason === 'escapeKeyDown') {
|
||||
return;
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
borderRadius: 2,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{ pb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Gavel sx={{ fontSize: 32, color: 'primary.main' }} />
|
||||
<Box>
|
||||
<Typography variant="h5" component="div">
|
||||
Welcome to ParentFlow
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Please review and accept our legal agreements
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
|
||||
<Divider />
|
||||
|
||||
<DialogContent sx={{ pt: 3 }}>
|
||||
<Alert severity="info" icon={<Warning />} sx={{ mb: 3 }}>
|
||||
<Typography variant="body2">
|
||||
To use ParentFlow, you must read and accept our Terms of Service, Privacy Policy, and End User License Agreement (EULA).
|
||||
Click on each link below to read the full documents.
|
||||
</Typography>
|
||||
</Alert>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Important Highlights
|
||||
</Typography>
|
||||
<Typography component="div" variant="body2" color="text.secondary">
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Medical Disclaimer:</strong> This app is NOT a medical device and does not provide medical advice.
|
||||
Always consult qualified healthcare professionals for medical concerns.
|
||||
</li>
|
||||
<li>
|
||||
<strong>AI Features:</strong> AI responses may not always be accurate. You use AI features at your own risk.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Children's Privacy:</strong> We comply with COPPA and GDPR. You control your child's data and can delete it anytime.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Data Collection:</strong> We collect activity data, photos, and AI chat messages to provide the service.
|
||||
We do NOT sell your data.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Your Rights:</strong> You can access, export, or delete your data at any time through app settings.
|
||||
</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Divider sx={{ my: 3 }} />
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={agreedToTerms}
|
||||
onChange={(e) => setAgreedToTerms(e.target.checked)}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2">
|
||||
I have read and agree to the{' '}
|
||||
<Link
|
||||
href="#"
|
||||
onClick={openDocument('terms', 'Terms of Service')}
|
||||
sx={{ fontWeight: 'bold', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
Terms of Service
|
||||
</Link>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={agreedToPrivacy}
|
||||
onChange={(e) => setAgreedToPrivacy(e.target.checked)}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2">
|
||||
I have read and agree to the{' '}
|
||||
<Link
|
||||
href="#"
|
||||
onClick={openDocument('privacy', 'Privacy Policy')}
|
||||
sx={{ fontWeight: 'bold', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={agreedToEULA}
|
||||
onChange={(e) => setAgreedToEULA(e.target.checked)}
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Typography variant="body2">
|
||||
I have read and agree to the{' '}
|
||||
<Link
|
||||
href="#"
|
||||
onClick={openDocument('eula', 'End User License Agreement (EULA)')}
|
||||
sx={{ fontWeight: 'bold', textDecoration: 'underline', cursor: 'pointer' }}
|
||||
>
|
||||
End User License Agreement (EULA)
|
||||
</Link>
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Alert severity="warning" sx={{ mt: 3 }}>
|
||||
<Typography variant="body2">
|
||||
<strong>Emergency Disclaimer:</strong> In case of medical emergencies, call your local emergency number immediately (911 in the US).
|
||||
This app is not for emergency situations.
|
||||
</Typography>
|
||||
</Alert>
|
||||
</DialogContent>
|
||||
|
||||
<Divider />
|
||||
|
||||
<DialogActions sx={{ p: 3, gap: 1 }}>
|
||||
<Button
|
||||
onClick={onDecline}
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
size="large"
|
||||
>
|
||||
Decline & Exit
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleAccept}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={!canAccept}
|
||||
sx={{ minWidth: 120 }}
|
||||
>
|
||||
I Accept
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Legal Document Viewer - appears on top of EULA dialog */}
|
||||
{viewingDocument.type && (
|
||||
<LegalDocumentViewer
|
||||
open={true}
|
||||
onClose={closeDocumentViewer}
|
||||
documentType={viewingDocument.type}
|
||||
title={viewingDocument.title}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
80
maternal-web/components/legal/LegalDocumentViewer.tsx
Normal file
80
maternal-web/components/legal/LegalDocumentViewer.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
IconButton,
|
||||
} from '@mui/material';
|
||||
import { Close } from '@mui/icons-material';
|
||||
import { TermsContent } from './TermsContent';
|
||||
import { PrivacyContent } from './PrivacyContent';
|
||||
import { EULAContent } from './EULAContent';
|
||||
|
||||
interface LegalDocumentViewerProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
documentType: 'terms' | 'privacy' | 'eula' | 'cookies';
|
||||
title: string;
|
||||
}
|
||||
|
||||
export function LegalDocumentViewer({ open, onClose, documentType, title }: LegalDocumentViewerProps) {
|
||||
const lastUpdated = 'October 4, 2025';
|
||||
|
||||
const getContent = () => {
|
||||
switch (documentType) {
|
||||
case 'terms':
|
||||
return <TermsContent />;
|
||||
case 'privacy':
|
||||
return <PrivacyContent />;
|
||||
case 'eula':
|
||||
return <EULAContent />;
|
||||
default:
|
||||
return (
|
||||
<Typography color="error">
|
||||
Document content not available. Please visit the{' '}
|
||||
<a href={`/legal/${documentType}`} target="_blank" rel="noopener">
|
||||
full page
|
||||
</a>
|
||||
.
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
sx={{
|
||||
zIndex: (theme) => theme.zIndex.modal + 1, // Ensure this dialog appears above the EULA dialog
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Typography variant="h6">{title}</Typography>
|
||||
<IconButton onClick={onClose} size="small" aria-label="close">
|
||||
<Close />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent dividers sx={{ maxHeight: '60vh', overflowY: 'auto' }}>
|
||||
<Typography variant="body2" color="text.secondary" paragraph>
|
||||
Last Updated: {lastUpdated}
|
||||
</Typography>
|
||||
{getContent()}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ p: 2 }}>
|
||||
<Button onClick={onClose} variant="contained" color="primary">
|
||||
Close
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
148
maternal-web/components/legal/PrivacyContent.tsx
Normal file
148
maternal-web/components/legal/PrivacyContent.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
export function PrivacyContent() {
|
||||
return (
|
||||
<Box sx={{ '& h5': { mt: 3, mb: 1, fontWeight: 'bold' }, '& h6': { mt: 2, mb: 1, fontWeight: 'bold' }, '& p': { mb: 2 }, '& ul': { mb: 2 } }}>
|
||||
<Typography variant="h5">1. Introduction</Typography>
|
||||
<Typography paragraph>
|
||||
Welcome to ParentFlow ("we," "our," or "us"). We are committed to protecting your privacy and the privacy of your children.
|
||||
This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our mobile application
|
||||
and related services (collectively, the "Service").
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
Because our Service is designed for parents and caregivers tracking information about children aged 0-6 years, we take extra
|
||||
precautions to comply with the Children's Online Privacy Protection Act (COPPA) and the General Data Protection Regulation (GDPR).
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">2. Information We Collect</Typography>
|
||||
<Typography variant="h6">2.1 Personal Information You Provide</Typography>
|
||||
<Typography component="ul">
|
||||
<li><strong>Account Information:</strong> Name, email address, date of birth (for COPPA age verification)</li>
|
||||
<li><strong>Profile Information:</strong> Profile photo, timezone, language preferences</li>
|
||||
<li><strong>Child Information:</strong> Child's name, date of birth, gender, photo (optional)</li>
|
||||
<li><strong>Activity Data:</strong> Feeding times, sleep schedules, diaper changes, medication records, milestones</li>
|
||||
<li><strong>AI Chat Messages:</strong> Questions and conversations with our AI assistant</li>
|
||||
<li><strong>Photos and Media:</strong> Photos of children and milestones (optional)</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">2.2 Automatically Collected Information</Typography>
|
||||
<Typography component="ul">
|
||||
<li><strong>Device Information:</strong> Device type, operating system, unique device identifiers</li>
|
||||
<li><strong>Usage Data:</strong> App features used, session duration, error logs</li>
|
||||
<li><strong>Technical Data:</strong> IP address, browser type, time zone settings</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">3. How We Use Your Information</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
We use the collected information for:
|
||||
<ul>
|
||||
<li>Providing and maintaining the Service</li>
|
||||
<li>Tracking your child's activities and patterns</li>
|
||||
<li>Generating insights and analytics about your child's development</li>
|
||||
<li>Providing AI-powered parenting support and answers</li>
|
||||
<li>Syncing data across family members' devices</li>
|
||||
<li>Sending notifications and reminders</li>
|
||||
<li>Improving our Service and developing new features</li>
|
||||
<li>Detecting and preventing fraud and security issues</li>
|
||||
<li>Complying with legal obligations</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">4. Children's Privacy (COPPA Compliance)</Typography>
|
||||
<Typography paragraph>
|
||||
Our Service is designed for parents and caregivers to track information about their children. We do not knowingly collect
|
||||
personal information directly from children under 13 years of age.
|
||||
</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
<strong>Parental Rights:</strong>
|
||||
<ul>
|
||||
<li>Review your child's information</li>
|
||||
<li>Request deletion of your child's information</li>
|
||||
<li>Refuse further collection or use of your child's information</li>
|
||||
<li>Export your child's data in a portable format</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
To exercise these rights, please contact us at <strong>hello@parentflow.com</strong>.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">5. Data Sharing and Disclosure</Typography>
|
||||
<Typography paragraph>
|
||||
<strong>We do NOT sell your personal information or your child's information.</strong>
|
||||
</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
We may share information with:
|
||||
<ul>
|
||||
<li><strong>Family Members:</strong> Data is shared with family members you invite to your family group</li>
|
||||
<li><strong>Service Providers:</strong> Cloud hosting (AWS/Azure), analytics (anonymized), customer support</li>
|
||||
<li><strong>AI Providers:</strong> OpenAI or Anthropic (for AI chat, with no PII in training data)</li>
|
||||
<li><strong>Legal Compliance:</strong> When required by law or to protect rights and safety</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">6. Data Security</Typography>
|
||||
<Typography paragraph>
|
||||
We implement industry-standard security measures to protect your information:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>End-to-end encryption for sensitive child data</li>
|
||||
<li>Secure HTTPS connections for all communications</li>
|
||||
<li>Regular security audits and penetration testing</li>
|
||||
<li>Access controls and authentication mechanisms</li>
|
||||
<li>Encrypted database storage</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">7. Your Rights (GDPR Compliance)</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
Under GDPR, you have the following rights:
|
||||
<ul>
|
||||
<li><strong>Right to Access:</strong> Request a copy of your personal data</li>
|
||||
<li><strong>Right to Rectification:</strong> Correct inaccurate information</li>
|
||||
<li><strong>Right to Erasure:</strong> Request deletion of your data ("right to be forgotten")</li>
|
||||
<li><strong>Right to Data Portability:</strong> Export your data in a machine-readable format</li>
|
||||
<li><strong>Right to Restrict Processing:</strong> Limit how we use your data</li>
|
||||
<li><strong>Right to Object:</strong> Opt-out of certain data processing</li>
|
||||
<li><strong>Right to Withdraw Consent:</strong> Revoke consent at any time</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
To exercise your rights, visit Settings → Privacy → Data Rights or email <strong>hello@parentflow.com</strong>.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">8. Data Retention</Typography>
|
||||
<Typography paragraph>
|
||||
We retain your information for as long as your account is active or as needed to provide the Service.
|
||||
When you delete your account, we will delete your personal information within 30 days, except where we are
|
||||
required to retain it for legal compliance or dispute resolution.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">9. International Data Transfers</Typography>
|
||||
<Typography paragraph>
|
||||
Your information may be transferred to and processed in countries other than your country of residence.
|
||||
We ensure appropriate safeguards are in place to protect your information in accordance with this Privacy Policy
|
||||
and applicable laws.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">10. Third-Party Links</Typography>
|
||||
<Typography paragraph>
|
||||
Our Service may contain links to third-party websites. We are not responsible for the privacy practices of these
|
||||
external sites. We encourage you to review their privacy policies.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">11. Changes to This Privacy Policy</Typography>
|
||||
<Typography paragraph>
|
||||
We may update this Privacy Policy from time to time. We will notify you of significant changes by email or through
|
||||
the app. Your continued use of the Service after changes constitutes acceptance of the updated policy.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">12. Contact Us</Typography>
|
||||
<Typography paragraph>
|
||||
If you have questions about this Privacy Policy or our data practices, please contact us:
|
||||
</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
<strong>Email:</strong> hello@parentflow.com<br />
|
||||
<strong>Address:</strong> Serbota 3, Bucharest, Romania
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
199
maternal-web/components/legal/TermsContent.tsx
Normal file
199
maternal-web/components/legal/TermsContent.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import Link from 'next/link';
|
||||
|
||||
export function TermsContent() {
|
||||
return (
|
||||
<Box sx={{ '& h5': { mt: 3, mb: 1, fontWeight: 'bold' }, '& h6': { mt: 2, mb: 1, fontWeight: 'bold' }, '& p': { mb: 2 }, '& ul': { mb: 2 } }}>
|
||||
<Typography variant="h5">1. Acceptance of Terms</Typography>
|
||||
<Typography paragraph>
|
||||
By accessing or using ParentFlow (the "Service"), you agree to be bound by these Terms of Service ("Terms").
|
||||
If you do not agree to these Terms, do not use the Service.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
These Terms constitute a legally binding agreement between you and ParentFlow ("we," "us," or "our").
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">2. Description of Service</Typography>
|
||||
<Typography paragraph>
|
||||
ParentFlow is a parenting organization and tracking application designed to help parents and caregivers manage
|
||||
childcare for children aged 0-6 years. The Service includes:
|
||||
</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Activity tracking (feeding, sleep, diapers, medicine, milestones)</li>
|
||||
<li>AI-powered parenting support and guidance</li>
|
||||
<li>Family synchronization and collaboration tools</li>
|
||||
<li>Analytics and insights about your child's patterns</li>
|
||||
<li>Voice input capabilities</li>
|
||||
<li>Photo storage and milestone tracking</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">3. User Accounts</Typography>
|
||||
<Typography variant="h6">3.1 Account Creation</Typography>
|
||||
<Typography paragraph>To use the Service, you must create an account. You agree to:</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Provide accurate and complete information</li>
|
||||
<li>Maintain the security of your account credentials</li>
|
||||
<li>Notify us immediately of any unauthorized access</li>
|
||||
<li>Be responsible for all activities under your account</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">3.2 Age Requirements</Typography>
|
||||
<Typography paragraph>
|
||||
You must be at least 18 years old (or the age of majority in your jurisdiction) to create an account.
|
||||
Users between 13-17 years old may only use the Service with parental consent.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">4. Acceptable Use</Typography>
|
||||
<Typography paragraph>You agree NOT to:</Typography>
|
||||
<Typography component="ul">
|
||||
<li>Use the Service for any illegal purpose</li>
|
||||
<li>Violate any laws or regulations</li>
|
||||
<li>Infringe on intellectual property rights</li>
|
||||
<li>Upload malicious code or viruses</li>
|
||||
<li>Attempt to gain unauthorized access to our systems</li>
|
||||
<li>Harass, abuse, or harm other users</li>
|
||||
<li>Share inappropriate content involving minors</li>
|
||||
<li>Use automated tools to access the Service (bots, scrapers)</li>
|
||||
<li>Reverse engineer or decompile the Service</li>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">5. Medical Disclaimer</Typography>
|
||||
<Typography paragraph sx={{ fontWeight: 'bold', color: 'error.main' }}>
|
||||
THE SERVICE IS NOT A SUBSTITUTE FOR PROFESSIONAL MEDICAL ADVICE.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
Our AI assistant and tracking features provide general information and insights only. They do not constitute
|
||||
medical advice, diagnosis, or treatment. Always consult with qualified healthcare professionals for medical concerns.
|
||||
</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
<strong>In case of emergency, call your local emergency services immediately.</strong>
|
||||
<ul>
|
||||
<li>United States: 911</li>
|
||||
<li>United Kingdom: 999</li>
|
||||
<li>European Union: 112</li>
|
||||
</ul>
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">6. User Content</Typography>
|
||||
<Typography variant="h6">6.1 Your Content</Typography>
|
||||
<Typography paragraph>
|
||||
You retain ownership of all content you upload to the Service (photos, data, messages).
|
||||
By uploading content, you grant us a license to use, store, and process it to provide the Service.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">6.2 Content Responsibility</Typography>
|
||||
<Typography paragraph>
|
||||
You are solely responsible for your content. You represent that you have the necessary rights and permissions
|
||||
to upload and share your content, including photos of your children.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">7. Intellectual Property</Typography>
|
||||
<Typography paragraph>
|
||||
The Service, including its design, features, code, and content (excluding user content), is owned by ParentFlow
|
||||
and protected by copyright, trademark, and other intellectual property laws.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
You may not copy, modify, distribute, sell, or create derivative works based on the Service without our written permission.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">8. Subscription and Payment</Typography>
|
||||
<Typography variant="h6">8.1 Free and Premium Features</Typography>
|
||||
<Typography paragraph>
|
||||
We offer both free and premium subscription tiers. Premium features may include unlimited AI queries, advanced analytics,
|
||||
and priority support.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">8.2 Billing</Typography>
|
||||
<Typography paragraph>
|
||||
Premium subscriptions are billed monthly or annually. Payments are processed through third-party payment processors.
|
||||
By subscribing, you authorize us to charge your payment method.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">8.3 Cancellation and Refunds</Typography>
|
||||
<Typography paragraph>
|
||||
You may cancel your subscription at any time. Cancellations take effect at the end of the current billing period.
|
||||
We do not offer refunds for partial subscription periods, except as required by law.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">9. Privacy</Typography>
|
||||
<Typography paragraph>
|
||||
Your use of the Service is also governed by our Privacy Policy. Please review it to understand how we collect, use, and protect your information.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">10. Termination</Typography>
|
||||
<Typography variant="h6">10.1 Termination by You</Typography>
|
||||
<Typography paragraph>
|
||||
You may terminate your account at any time through the app settings. Upon termination, your data will be deleted
|
||||
in accordance with our Privacy Policy.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">10.2 Termination by Us</Typography>
|
||||
<Typography paragraph>
|
||||
We reserve the right to suspend or terminate your account if you violate these Terms or engage in harmful behavior.
|
||||
We will provide notice where reasonably possible.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">11. Disclaimers</Typography>
|
||||
<Typography paragraph sx={{ fontWeight: 'bold' }}>
|
||||
THE SERVICE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTIES OF ANY KIND.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
We disclaim all warranties, express or implied, including warranties of merchantability, fitness for a particular purpose,
|
||||
and non-infringement. We do not guarantee that the Service will be error-free, secure, or uninterrupted.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">12. Limitation of Liability</Typography>
|
||||
<Typography paragraph>
|
||||
TO THE MAXIMUM EXTENT PERMITTED BY LAW, WE SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, CONSEQUENTIAL, OR PUNITIVE
|
||||
DAMAGES ARISING FROM YOUR USE OF THE SERVICE.
|
||||
</Typography>
|
||||
<Typography paragraph>
|
||||
Our total liability shall not exceed the amount you paid us in the 12 months preceding the claim, or $100, whichever is greater.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">13. Indemnification</Typography>
|
||||
<Typography paragraph>
|
||||
You agree to indemnify and hold harmless ParentFlow and its officers, directors, employees, and agents from any claims,
|
||||
damages, or expenses arising from your use of the Service or violation of these Terms.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">14. Dispute Resolution</Typography>
|
||||
<Typography variant="h6">14.1 Informal Resolution</Typography>
|
||||
<Typography paragraph>
|
||||
Before filing a legal claim, you agree to contact us at <strong>hello@parentflow.com</strong> to attempt to resolve
|
||||
the dispute informally.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6">14.2 Arbitration</Typography>
|
||||
<Typography paragraph>
|
||||
Any disputes that cannot be resolved informally will be settled by binding arbitration in accordance with the
|
||||
American Arbitration Association rules, except where prohibited by law.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">15. Governing Law</Typography>
|
||||
<Typography paragraph>
|
||||
These Terms are governed by the laws of [Your Jurisdiction], without regard to conflict of law principles.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">16. Changes to Terms</Typography>
|
||||
<Typography paragraph>
|
||||
We may update these Terms from time to time. We will notify you of material changes by email or through the app.
|
||||
Your continued use of the Service after changes constitutes acceptance of the updated Terms.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">17. Severability</Typography>
|
||||
<Typography paragraph>
|
||||
If any provision of these Terms is found to be invalid or unenforceable, the remaining provisions will continue in full force.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h5">18. Contact Us</Typography>
|
||||
<Typography paragraph>
|
||||
If you have questions about these Terms, please contact us:
|
||||
</Typography>
|
||||
<Typography component="div" paragraph>
|
||||
<strong>Email:</strong> hello@parentflow.com<br />
|
||||
<strong>Address:</strong> Serbota 3, Bucharest, Romania
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user