feat(web): add HealthConsentModal and PrivacyView components

This commit is contained in:
Antoni Nuñez Romeu
2026-08-26 15:15:45 +02:00
parent c116a9f525
commit 66055a5e19
4 changed files with 82 additions and 0 deletions
@@ -0,0 +1,8 @@
.health-consent-modal { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 2rem 2.25rem 1.75rem; width: 100%; max-width: 360px; box-shadow: 0 20px 60px rgba(28, 25, 23, 0.18); animation: slideUp 0.18s ease; }
.health-consent-title { font-size: 1.15rem; font-weight: 700; color: var(--text-main); margin: 0 0 0.75rem; }
.health-consent-desc { font-size: 0.9rem; color: var(--text-muted); line-height: 1.6; margin: 0 0 1.5rem; }
.health-consent-actions { display: flex; flex-direction: column; gap: 0.5rem; }
.health-consent-btn { width: 100%; padding: 0.65rem 1rem; border-radius: 999px; border: none; font-size: 0.9rem; font-weight: 600; cursor: pointer; transition: opacity 0.15s; }
.health-consent-btn:hover { opacity: 0.85; }
.health-consent-btn--accept { background: var(--primary); color: var(--on-primary); }
.health-consent-btn--cancel { background: var(--surface-muted); color: var(--text-main); border: 1px solid var(--border); }
@@ -0,0 +1,28 @@
import React, { useEffect } from 'react';
import { useTranslation } from '../i18n';
import './HealthConsentModal.css';
function HealthConsentModal({ onAccept, onCancel }) {
const { t } = useTranslation();
useEffect(() => {
function handleKey(e) { if (e.key === 'Escape') onCancel(); }
document.addEventListener('keydown', handleKey);
return () => document.removeEventListener('keydown', handleKey);
}, [onCancel]);
return (
<div className="modal-overlay" onClick={onCancel}>
<div className="health-consent-modal" onClick={e => e.stopPropagation()} role="dialog" aria-modal="true" aria-label={t('health_consent.title')}>
<h2 className="health-consent-title">{t('health_consent.title')}</h2>
<p className="health-consent-desc">{t('health_consent.description')}</p>
<div className="health-consent-actions">
<button type="button" className="health-consent-btn health-consent-btn--accept" onClick={onAccept}>{t('health_consent.accept')}</button>
<button type="button" className="health-consent-btn health-consent-btn--cancel" onClick={onCancel}>{t('health_consent.cancel')}</button>
</div>
</div>
</div>
);
}
export default HealthConsentModal;
+14
View File
@@ -0,0 +1,14 @@
.privacy-view { width: 100%; max-width: 640px; margin: 0 auto; padding: 1rem 1.25rem 2rem; animation: fadeInUp 0.3s ease-out; }
.privacy-header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1.5rem; }
.privacy-back { background: var(--surface-muted); border: 1px solid var(--border); border-radius: var(--radius); width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 1.1rem; color: var(--text-main); flex-shrink: 0; transition: background 0.15s; }
.privacy-back:hover { background: var(--border); }
.privacy-title { font-size: 1.3rem; font-weight: 700; color: var(--text-main); margin: 0; }
.privacy-updated { font-size: 0.8rem; color: var(--text-muted); margin: 0 0 1.5rem; }
.privacy-content { display: flex; flex-direction: column; gap: 1.5rem; }
.privacy-section { background: var(--surface-muted); border-radius: var(--radius); padding: 1.25rem; }
.privacy-section-title { font-size: 1rem; font-weight: 700; color: var(--text-main); margin: 0 0 0.75rem; }
.privacy-section-content { font-size: 0.88rem; color: var(--text-muted); line-height: 1.6; }
.privacy-section-content p { margin: 0 0 0.5rem; }
.privacy-section-content p:last-child { margin-bottom: 0; }
@keyframes fadeInUp { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 768px) { .privacy-view { padding: 0.75rem 1rem 2rem; } }
+32
View File
@@ -0,0 +1,32 @@
import React from 'react';
import { useTranslation } from '../i18n';
import './PrivacyView.css';
function PrivacyView({ onBack }) {
const { t } = useTranslation();
const sections = ['controller', 'data_collected', 'purpose', 'legal_basis', 'external_services', 'retention', 'rights', 'contact', 'cookies'];
return (
<div className="privacy-view">
<div className="privacy-header">
<button type="button" className="privacy-back" onClick={onBack}>←</button>
<h1 className="privacy-title">{t('privacy.title')}</h1>
</div>
<div className="privacy-content">
<p className="privacy-updated">{t('privacy.last_updated')}</p>
{sections.map(section => (
<section key={section} className="privacy-section">
<h2 className="privacy-section-title">{t(`privacy.section.${section}.title`)}</h2>
<div className="privacy-section-content">
{t(`privacy.section.${section}.content`).split('\n').map((line, i) => (
<p key={i}>{line}</p>
))}
</div>
</section>
))}
</div>
</div>
);
}
export default PrivacyView;