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:
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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user