feat(mobile): add complete consent flow (banner, health modal, privacy, i18n)
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal } from 'react-native';
|
||||
import { useThemeContext } from './ThemeProvider';
|
||||
import { useTranslation } from '../src/i18n';
|
||||
import { borderRadius } from '../constants/theme';
|
||||
|
||||
interface CookieBannerProps {
|
||||
onConsent: (consents: { essential: boolean; analytics: boolean; preferences: boolean; health_data: boolean }) => void;
|
||||
onPrivacyPress: () => void;
|
||||
}
|
||||
|
||||
export default function CookieBanner({ onConsent, onPrivacyPress }: CookieBannerProps) {
|
||||
const { colors } = useThemeContext();
|
||||
const { t } = useTranslation();
|
||||
const [categories, setCategories] = useState({ analytics: false, preferences: false, health_data: false });
|
||||
|
||||
const toggleCategory = (cat: keyof typeof categories) => {
|
||||
setCategories(prev => ({ ...prev, [cat]: !prev[cat] }));
|
||||
};
|
||||
|
||||
const categoryKeys = ['analytics', 'preferences', 'health_data'] as const;
|
||||
|
||||
return (
|
||||
<Modal transparent animationType="slide" visible>
|
||||
<View style={styles.overlay}>
|
||||
<View style={[styles.container, { backgroundColor: colors.card }]}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
<Text style={[styles.title, { color: colors.text }]}>{t('cookie_banner.title')}</Text>
|
||||
<Text style={[styles.desc, { color: colors.textSecondary }]}>{t('cookie_banner.description')}</Text>
|
||||
<View style={styles.categories}>
|
||||
<View style={[styles.category, { backgroundColor: colors.background }]}>
|
||||
<View style={styles.categoryInfo}>
|
||||
<Text style={[styles.categoryName, { color: colors.text }]}>{t('cookie_banner.category.essential')}</Text>
|
||||
<Text style={[styles.categoryDesc, { color: colors.textSecondary }]}>{t('cookie_banner.category.essential_desc')}</Text>
|
||||
</View>
|
||||
<View style={[styles.toggle, styles.toggleLocked, { backgroundColor: colors.primary }]}>
|
||||
<Text style={styles.toggleLabel}>ON</Text>
|
||||
</View>
|
||||
</View>
|
||||
{categoryKeys.map(cat => (
|
||||
<View key={cat} style={[styles.category, { backgroundColor: colors.background }]}>
|
||||
<View style={styles.categoryInfo}>
|
||||
<Text style={[styles.categoryName, { color: colors.text }]}>{t(`cookie_banner.category.${cat}`)}</Text>
|
||||
<Text style={[styles.categoryDesc, { color: colors.textSecondary }]}>{t(`cookie_banner.category.${cat}_desc`)}</Text>
|
||||
</View>
|
||||
<TouchableOpacity style={[styles.toggle, categories[cat] && { backgroundColor: colors.primary }]} onPress={() => toggleCategory(cat)} activeOpacity={0.7}>
|
||||
<View style={[styles.toggleThumb, categories[cat] && styles.toggleThumbOn]} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
<View style={styles.actions}>
|
||||
<TouchableOpacity style={[styles.btn, { backgroundColor: colors.primary }]} onPress={() => onConsent({ essential: true, analytics: true, preferences: true, health_data: true })}>
|
||||
<Text style={[styles.btnText, { color: colors.background }]}>{t('cookie_banner.accept_all')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.btn, { backgroundColor: colors.background, borderColor: colors.border, borderWidth: 1 }]} onPress={() => onConsent({ essential: true, analytics: false, preferences: false, health_data: false })}>
|
||||
<Text style={[styles.btnText, { color: colors.text }]}>{t('cookie_banner.reject_optional')}</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={[styles.btn, { borderColor: colors.primary, borderWidth: 1 }]} onPress={() => onConsent({ essential: true, ...categories })}>
|
||||
<Text style={[styles.btnText, { color: colors.primary }]}>{t('cookie_banner.save')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity onPress={onPrivacyPress}>
|
||||
<Text style={[styles.moreInfo, { color: colors.textSecondary }]}>{t('cookie_banner.more_info')} →</Text>
|
||||
</TouchableOpacity>
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' },
|
||||
container: { borderTopLeftRadius: borderRadius.lg, borderTopRightRadius: borderRadius.lg, padding: 20, maxHeight: '85%' },
|
||||
title: { fontSize: 18, fontWeight: '700', marginBottom: 8 },
|
||||
desc: { fontSize: 14, lineHeight: 20, marginBottom: 16 },
|
||||
categories: { gap: 10, marginBottom: 16 },
|
||||
category: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: 12, borderRadius: borderRadius.md, gap: 12 },
|
||||
categoryInfo: { flex: 1, gap: 2 },
|
||||
categoryName: { fontSize: 14, fontWeight: '600' },
|
||||
categoryDesc: { fontSize: 12, lineHeight: 16 },
|
||||
toggle: { width: 44, height: 24, borderRadius: 12, backgroundColor: '#ccc', justifyContent: 'center', alignItems: 'center', padding: 2 },
|
||||
toggleLocked: { opacity: 0.6 },
|
||||
toggleLabel: { fontSize: 9, fontWeight: '700', color: 'white' },
|
||||
toggleThumb: { width: 20, height: 20, borderRadius: 10, backgroundColor: 'white', alignSelf: 'flex-start' },
|
||||
toggleThumbOn: { alignSelf: 'flex-end' },
|
||||
actions: { gap: 8, marginBottom: 12 },
|
||||
btn: { paddingVertical: 12, borderRadius: 999, alignItems: 'center' },
|
||||
btnText: { fontSize: 14, fontWeight: '600' },
|
||||
moreInfo: { textAlign: 'center', fontSize: 12, textDecorationLine: 'underline' },
|
||||
});
|
||||
Reference in New Issue
Block a user