- Redesign AccountPage with tabbed interface (Profile, Privacy & rCards, Settings) - Add RCardPrivacySettings component with granular privacy controls - Create RCardManagement component for creating and editing rCards - Extend notification types with privacy settings and rCard structures - Implement privacy levels: none, limited, moderate, intimate - Add location sharing controls with auto-deletion settings - Include data sharing sliders for articles, proposals, offers/wants, gratitude - Support re-sharing controls with hop limits - Enable trust settings (key recovery buddy, circles trusted connection) - Add default rCard categories with customized privacy defaults - Support custom rCard creation with icons and colors - Include comprehensive privacy preview and management interface Features: - Granular privacy control per relationship type (rCard) - Visual privacy level sliders matching the provided design - Custom rCard creation with icon and color selection - Default privacy templates optimized for relationship types - Real-time privacy setting updates - Professional UI matching app design patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>main
parent
142920b264
commit
570f6dba11
@ -0,0 +1,314 @@ |
||||
import { useState } from 'react'; |
||||
import { |
||||
Dialog, |
||||
DialogTitle, |
||||
DialogContent, |
||||
DialogActions, |
||||
Button, |
||||
TextField, |
||||
Box, |
||||
Typography, |
||||
Avatar, |
||||
Grid, |
||||
IconButton, |
||||
Card, |
||||
CardContent, |
||||
Chip, |
||||
} from '@mui/material'; |
||||
import { |
||||
Business, |
||||
PersonOutline, |
||||
Groups, |
||||
FamilyRestroom, |
||||
Favorite, |
||||
Home, |
||||
Work, |
||||
School, |
||||
LocalHospital, |
||||
Sports, |
||||
Close, |
||||
Edit, |
||||
Delete, |
||||
} from '@mui/icons-material'; |
||||
import type { RCardWithPrivacy } from '../../types/notification'; |
||||
import { DEFAULT_PRIVACY_SETTINGS } from '../../types/notification'; |
||||
|
||||
interface RCardManagementProps { |
||||
open: boolean; |
||||
onClose: () => void; |
||||
onSave: (rCard: RCardWithPrivacy) => void; |
||||
onDelete?: (rCardId: string) => void; |
||||
editingRCard?: RCardWithPrivacy; |
||||
} |
||||
|
||||
const AVAILABLE_ICONS = [ |
||||
{ name: 'Business', icon: <Business />, label: 'Business' }, |
||||
{ name: 'PersonOutline', icon: <PersonOutline />, label: 'Person' }, |
||||
{ name: 'Groups', icon: <Groups />, label: 'Groups' }, |
||||
{ name: 'FamilyRestroom', icon: <FamilyRestroom />, label: 'Family' }, |
||||
{ name: 'Favorite', icon: <Favorite />, label: 'Heart' }, |
||||
{ name: 'Home', icon: <Home />, label: 'Home' }, |
||||
{ name: 'Work', icon: <Work />, label: 'Work' }, |
||||
{ name: 'School', icon: <School />, label: 'School' }, |
||||
{ name: 'LocalHospital', icon: <LocalHospital />, label: 'Medical' }, |
||||
{ name: 'Sports', icon: <Sports />, label: 'Sports' }, |
||||
]; |
||||
|
||||
const AVAILABLE_COLORS = [ |
||||
'#2563eb', // Blue
|
||||
'#10b981', // Green
|
||||
'#8b5cf6', // Purple
|
||||
'#f59e0b', // Orange
|
||||
'#ef4444', // Red
|
||||
'#ec4899', // Pink
|
||||
'#06b6d4', // Cyan
|
||||
'#84cc16', // Lime
|
||||
'#f97316', // Orange-red
|
||||
'#6366f1', // Indigo
|
||||
]; |
||||
|
||||
const RCardManagement = ({
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
onDelete,
|
||||
editingRCard
|
||||
}: RCardManagementProps) => { |
||||
const [formData, setFormData] = useState({ |
||||
name: editingRCard?.name || '', |
||||
description: editingRCard?.description || '', |
||||
color: editingRCard?.color || AVAILABLE_COLORS[0], |
||||
icon: editingRCard?.icon || 'PersonOutline', |
||||
}); |
||||
|
||||
const [errors, setErrors] = useState<Record<string, string>>({}); |
||||
|
||||
const handleSubmit = () => { |
||||
const newErrors: Record<string, string> = {}; |
||||
|
||||
if (!formData.name.trim()) { |
||||
newErrors.name = 'Name is required'; |
||||
} |
||||
|
||||
if (!formData.description.trim()) { |
||||
newErrors.description = 'Description is required'; |
||||
} |
||||
|
||||
if (Object.keys(newErrors).length > 0) { |
||||
setErrors(newErrors); |
||||
return; |
||||
} |
||||
|
||||
const rCardData: RCardWithPrivacy = { |
||||
id: editingRCard?.id || `custom-${Date.now()}`, |
||||
name: formData.name.trim(), |
||||
description: formData.description.trim(), |
||||
color: formData.color, |
||||
icon: formData.icon, |
||||
isDefault: editingRCard?.isDefault || false, |
||||
createdAt: editingRCard?.createdAt || new Date(), |
||||
updatedAt: new Date(), |
||||
privacySettings: editingRCard?.privacySettings || { ...DEFAULT_PRIVACY_SETTINGS }, |
||||
}; |
||||
|
||||
onSave(rCardData); |
||||
handleClose(); |
||||
}; |
||||
|
||||
const handleClose = () => { |
||||
setFormData({ |
||||
name: '', |
||||
description: '', |
||||
color: AVAILABLE_COLORS[0], |
||||
icon: 'PersonOutline', |
||||
}); |
||||
setErrors({}); |
||||
onClose(); |
||||
}; |
||||
|
||||
const handleDelete = () => { |
||||
if (editingRCard && onDelete) { |
||||
onDelete(editingRCard.id); |
||||
handleClose(); |
||||
} |
||||
}; |
||||
|
||||
const getIconComponent = (iconName: string) => { |
||||
const iconData = AVAILABLE_ICONS.find(icon => icon.name === iconName); |
||||
return iconData?.icon || <PersonOutline />; |
||||
}; |
||||
|
||||
return ( |
||||
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth> |
||||
<DialogTitle> |
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
||||
<Typography variant="h6"> |
||||
{editingRCard ? 'Edit Relationship Card' : 'Create New Relationship Card'} |
||||
</Typography> |
||||
<IconButton onClick={handleClose} size="small"> |
||||
<Close /> |
||||
</IconButton> |
||||
</Box> |
||||
</DialogTitle> |
||||
|
||||
<DialogContent> |
||||
<Box sx={{ pt: 2 }}> |
||||
{/* Preview */} |
||||
<Card variant="outlined" sx={{ mb: 3, bgcolor: 'grey.50' }}> |
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}> |
||||
<Typography variant="subtitle2" color="text.secondary" sx={{ mb: 2 }}> |
||||
Preview |
||||
</Typography> |
||||
<Avatar |
||||
sx={{
|
||||
bgcolor: formData.color,
|
||||
width: 64,
|
||||
height: 64,
|
||||
mx: 'auto',
|
||||
mb: 2
|
||||
}} |
||||
> |
||||
{getIconComponent(formData.icon)} |
||||
</Avatar> |
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}> |
||||
{formData.name || 'rCard Name'} |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
{formData.description || 'rCard description'} |
||||
</Typography> |
||||
{editingRCard?.isDefault && ( |
||||
<Chip label="Default" size="small" variant="outlined" sx={{ mt: 1 }} /> |
||||
)} |
||||
</CardContent> |
||||
</Card> |
||||
|
||||
{/* Form Fields */} |
||||
<Grid container spacing={3}> |
||||
<Grid size={{ xs: 12 }}> |
||||
<TextField |
||||
fullWidth |
||||
label="rCard Name" |
||||
value={formData.name} |
||||
onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))} |
||||
error={!!errors.name} |
||||
helperText={errors.name} |
||||
placeholder="e.g., Close Friends, Work Colleagues, Gym Buddies" |
||||
/> |
||||
</Grid> |
||||
|
||||
<Grid size={{ xs: 12 }}> |
||||
<TextField |
||||
fullWidth |
||||
multiline |
||||
rows={3} |
||||
label="Description" |
||||
value={formData.description} |
||||
onChange={(e) => setFormData(prev => ({ ...prev, description: e.target.value }))} |
||||
error={!!errors.description} |
||||
helperText={errors.description} |
||||
placeholder="Describe the type of relationship and what you'll share with this group" |
||||
/> |
||||
</Grid> |
||||
|
||||
{/* Icon Selection */} |
||||
<Grid size={{ xs: 12 }}> |
||||
<Typography variant="subtitle2" sx={{ mb: 2 }}> |
||||
Choose an Icon |
||||
</Typography> |
||||
<Grid container spacing={1}> |
||||
{AVAILABLE_ICONS.map((iconData) => ( |
||||
<Grid size="auto" key={iconData.name}> |
||||
<Card |
||||
variant="outlined" |
||||
sx={{ |
||||
cursor: 'pointer', |
||||
border: formData.icon === iconData.name ? 2 : 1, |
||||
borderColor: formData.icon === iconData.name ? 'primary.main' : 'divider', |
||||
'&:hover': { borderColor: 'primary.main' }, |
||||
}} |
||||
onClick={() => setFormData(prev => ({ ...prev, icon: iconData.name }))} |
||||
> |
||||
<CardContent sx={{ p: 2, textAlign: 'center', minWidth: 80 }}> |
||||
<Box sx={{ color: formData.color, mb: 1 }}> |
||||
{iconData.icon} |
||||
</Box> |
||||
<Typography variant="caption"> |
||||
{iconData.label} |
||||
</Typography> |
||||
</CardContent> |
||||
</Card> |
||||
</Grid> |
||||
))} |
||||
</Grid> |
||||
</Grid> |
||||
|
||||
{/* Color Selection */} |
||||
<Grid size={{ xs: 12 }}> |
||||
<Typography variant="subtitle2" sx={{ mb: 2 }}> |
||||
Choose a Color |
||||
</Typography> |
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> |
||||
{AVAILABLE_COLORS.map((color) => ( |
||||
<Box |
||||
key={color} |
||||
sx={{ |
||||
width: 40, |
||||
height: 40, |
||||
borderRadius: '50%', |
||||
bgcolor: color, |
||||
cursor: 'pointer', |
||||
border: formData.color === color ? 3 : 2, |
||||
borderColor: formData.color === color ? 'grey.800' : 'grey.300', |
||||
'&:hover': { borderColor: 'grey.600' }, |
||||
}} |
||||
onClick={() => setFormData(prev => ({ ...prev, color }))} |
||||
/> |
||||
))} |
||||
</Box> |
||||
</Grid> |
||||
</Grid> |
||||
|
||||
{/* Default rCard Warning */} |
||||
{editingRCard?.isDefault && ( |
||||
<Box sx={{ mt: 3, p: 2, bgcolor: 'warning.50', borderRadius: 1, border: 1, borderColor: 'warning.200' }}> |
||||
<Typography variant="body2" color="warning.dark"> |
||||
<strong>Note:</strong> This is a default rCard. You can edit its name, description, and appearance,
|
||||
but it cannot be deleted. |
||||
</Typography> |
||||
</Box> |
||||
)} |
||||
</Box> |
||||
</DialogContent> |
||||
|
||||
<DialogActions sx={{ p: 3 }}> |
||||
<Box sx={{ display: 'flex', width: '100%', justifyContent: 'space-between' }}> |
||||
<Box> |
||||
{editingRCard && !editingRCard.isDefault && onDelete && ( |
||||
<Button |
||||
color="error" |
||||
startIcon={<Delete />} |
||||
onClick={handleDelete} |
||||
> |
||||
Delete rCard |
||||
</Button> |
||||
)} |
||||
</Box> |
||||
<Box sx={{ display: 'flex', gap: 1 }}> |
||||
<Button onClick={handleClose}> |
||||
Cancel |
||||
</Button> |
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit} |
||||
startIcon={editingRCard ? <Edit /> : undefined} |
||||
> |
||||
{editingRCard ? 'Save Changes' : 'Create rCard'} |
||||
</Button> |
||||
</Box> |
||||
</Box> |
||||
</DialogActions> |
||||
</Dialog> |
||||
); |
||||
}; |
||||
|
||||
export default RCardManagement; |
@ -0,0 +1,349 @@ |
||||
import { useState } from 'react'; |
||||
import { |
||||
Card, |
||||
CardContent, |
||||
Typography, |
||||
Box, |
||||
Switch, |
||||
FormControlLabel, |
||||
Select, |
||||
MenuItem, |
||||
FormControl, |
||||
InputLabel, |
||||
Slider, |
||||
Divider, |
||||
Chip, |
||||
alpha, |
||||
} from '@mui/material'; |
||||
import { |
||||
Security, |
||||
LocationOn, |
||||
Share, |
||||
Refresh, |
||||
VpnKey, |
||||
} from '@mui/icons-material'; |
||||
import type { RCardWithPrivacy, PrivacyLevel, LocationSharingLevel } from '../../types/notification'; |
||||
|
||||
interface RCardPrivacySettingsProps { |
||||
rCard: RCardWithPrivacy; |
||||
onUpdate: (updatedRCard: RCardWithPrivacy) => void; |
||||
} |
||||
|
||||
const RCardPrivacySettings = ({ rCard, onUpdate }: RCardPrivacySettingsProps) => { |
||||
const [settings, setSettings] = useState(rCard.privacySettings); |
||||
|
||||
const handleSettingChange = ( |
||||
category: string, |
||||
field: string, |
||||
value: any |
||||
) => { |
||||
const newSettings = { ...settings }; |
||||
|
||||
if (category === 'dataSharing') { |
||||
(newSettings.dataSharing as any)[field] = value; |
||||
} else if (category === 'reSharing') { |
||||
(newSettings.reSharing as any)[field] = value; |
||||
} else { |
||||
(newSettings as any)[field] = value; |
||||
} |
||||
|
||||
setSettings(newSettings); |
||||
|
||||
const updatedRCard = { |
||||
...rCard, |
||||
privacySettings: newSettings, |
||||
updatedAt: new Date(), |
||||
}; |
||||
|
||||
onUpdate(updatedRCard); |
||||
}; |
||||
|
||||
const getPrivacyLevelColor = (level: PrivacyLevel) => { |
||||
switch (level) { |
||||
case 'none': return '#94a3b8'; |
||||
case 'limited': return '#3b82f6'; |
||||
case 'moderate': return '#f59e0b'; |
||||
case 'intimate': return '#ef4444'; |
||||
default: return '#3b82f6'; |
||||
} |
||||
}; |
||||
|
||||
const getPrivacyLevelLabel = (level: PrivacyLevel) => { |
||||
switch (level) { |
||||
case 'none': return 'None'; |
||||
case 'limited': return 'Limited'; |
||||
case 'moderate': return 'Moderate'; |
||||
case 'intimate': return 'Intimate'; |
||||
default: return 'Limited'; |
||||
} |
||||
}; |
||||
|
||||
const PrivacySlider = ({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
description
|
||||
}: {
|
||||
label: string;
|
||||
value: PrivacyLevel;
|
||||
onChange: (value: PrivacyLevel) => void; |
||||
description?: string; |
||||
}) => { |
||||
const levels: PrivacyLevel[] = ['none', 'limited', 'moderate', 'intimate']; |
||||
const currentIndex = levels.indexOf(value); |
||||
|
||||
return ( |
||||
<Box sx={{ mb: 4 }}> |
||||
<Typography variant="subtitle1" sx={{ fontWeight: 600, mb: 1 }}> |
||||
{label} |
||||
</Typography> |
||||
{description && ( |
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}> |
||||
{description} |
||||
</Typography> |
||||
)} |
||||
<Box sx={{ px: 2 }}> |
||||
<Slider |
||||
value={currentIndex} |
||||
onChange={(_, newValue) => onChange(levels[newValue as number])} |
||||
min={0} |
||||
max={3} |
||||
step={1} |
||||
marks={levels.map((level, index) => ({ |
||||
value: index, |
||||
label: getPrivacyLevelLabel(level), |
||||
}))} |
||||
sx={{ |
||||
color: getPrivacyLevelColor(value), |
||||
'& .MuiSlider-markLabel': { |
||||
fontSize: '0.75rem', |
||||
fontWeight: currentIndex === levels.indexOf(value) ? 600 : 400, |
||||
color: currentIndex === levels.indexOf(value) ? getPrivacyLevelColor(value) : 'text.secondary', |
||||
}, |
||||
'& .MuiSlider-track': { |
||||
border: 'none', |
||||
height: 8, |
||||
}, |
||||
'& .MuiSlider-thumb': { |
||||
height: 20, |
||||
width: 20, |
||||
backgroundColor: getPrivacyLevelColor(value), |
||||
'&:focus, &:hover, &.Mui-active, &.Mui-focusVisible': { |
||||
boxShadow: `0px 0px 0px 8px ${alpha(getPrivacyLevelColor(value), 0.16)}`, |
||||
}, |
||||
}, |
||||
}} |
||||
/> |
||||
</Box> |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
return ( |
||||
<Card> |
||||
<CardContent sx={{ p: 3 }}> |
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 3 }}> |
||||
<Security color="primary" /> |
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}> |
||||
Privacy Settings for {rCard.name} |
||||
</Typography> |
||||
<Chip
|
||||
label={rCard.isDefault ? 'Default' : 'Custom'}
|
||||
size="small"
|
||||
variant="outlined" |
||||
color={rCard.isDefault ? 'default' : 'primary'} |
||||
/> |
||||
</Box> |
||||
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 4 }}> |
||||
Configure what information is shared with contacts assigned to this rCard category. |
||||
</Typography> |
||||
|
||||
{/* Key Recovery & Trust Settings */} |
||||
<Box sx={{ mb: 4 }}> |
||||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}> |
||||
<VpnKey fontSize="small" /> |
||||
Trust & Recovery |
||||
</Typography> |
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> |
||||
<FormControlLabel |
||||
control={ |
||||
<Switch |
||||
checked={settings.keyRecoveryBuddy} |
||||
onChange={(e) => handleSettingChange('general', 'keyRecoveryBuddy', e.target.checked)} |
||||
/> |
||||
} |
||||
label={ |
||||
<Box> |
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}> |
||||
Key Recovery Buddy |
||||
</Typography> |
||||
<Typography variant="caption" color="text.secondary"> |
||||
Allow contacts in this category to help recover your account |
||||
</Typography> |
||||
</Box> |
||||
} |
||||
/> |
||||
|
||||
<FormControlLabel |
||||
control={ |
||||
<Switch |
||||
checked={settings.circlesTrustedConnection} |
||||
onChange={(e) => handleSettingChange('general', 'circlesTrustedConnection', e.target.checked)} |
||||
/> |
||||
} |
||||
label={ |
||||
<Box> |
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}> |
||||
Circles Trusted Connection |
||||
</Typography> |
||||
<Typography variant="caption" color="text.secondary"> |
||||
Include these contacts in your trusted circle |
||||
</Typography> |
||||
</Box> |
||||
} |
||||
/> |
||||
</Box> |
||||
</Box> |
||||
|
||||
<Divider sx={{ my: 3 }} /> |
||||
|
||||
{/* Location Sharing */} |
||||
<Box sx={{ mb: 4 }}> |
||||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}> |
||||
<LocationOn fontSize="small" /> |
||||
Location Sharing |
||||
</Typography> |
||||
|
||||
<FormControl fullWidth sx={{ mb: 2 }}> |
||||
<InputLabel>Location Sharing Level</InputLabel> |
||||
<Select |
||||
value={settings.locationSharing} |
||||
label="Location Sharing Level" |
||||
onChange={(e) => handleSettingChange('general', 'locationSharing', e.target.value as LocationSharingLevel)} |
||||
> |
||||
<MenuItem value="never">Never</MenuItem> |
||||
<MenuItem value="limited">Limited (On Request)</MenuItem> |
||||
<MenuItem value="always">Always</MenuItem> |
||||
</Select> |
||||
</FormControl> |
||||
|
||||
{settings.locationSharing !== 'never' && ( |
||||
<Box> |
||||
<Typography variant="body2" sx={{ mb: 1 }}> |
||||
Auto-delete location after: {settings.locationDeletionHours} hours |
||||
</Typography> |
||||
<Slider |
||||
value={settings.locationDeletionHours} |
||||
onChange={(_, value) => handleSettingChange('general', 'locationDeletionHours', value)} |
||||
min={1} |
||||
max={48} |
||||
step={1} |
||||
marks={[ |
||||
{ value: 1, label: '1h' }, |
||||
{ value: 8, label: '8h' }, |
||||
{ value: 24, label: '24h' }, |
||||
{ value: 48, label: '48h' }, |
||||
]} |
||||
sx={{ color: 'primary.main', mt: 2 }} |
||||
/> |
||||
</Box> |
||||
)} |
||||
</Box> |
||||
|
||||
<Divider sx={{ my: 3 }} /> |
||||
|
||||
{/* Data Sharing */} |
||||
<Box sx={{ mb: 4 }}> |
||||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 3, display: 'flex', alignItems: 'center', gap: 1 }}> |
||||
<Share fontSize="small" /> |
||||
Data Sharing |
||||
</Typography> |
||||
|
||||
<PrivacySlider |
||||
label="Articles" |
||||
value={settings.dataSharing.articles} |
||||
onChange={(value) => handleSettingChange('dataSharing', 'articles', value)} |
||||
description="Control who can see your shared articles and reading preferences" |
||||
/> |
||||
|
||||
<PrivacySlider |
||||
label="Proposals" |
||||
value={settings.dataSharing.proposals} |
||||
onChange={(value) => handleSettingChange('dataSharing', 'proposals', value)} |
||||
description="Manage visibility of your project proposals and business ideas" |
||||
/> |
||||
|
||||
<PrivacySlider |
||||
label="Offers & Wants" |
||||
value={settings.dataSharing.offersAndWants} |
||||
onChange={(value) => handleSettingChange('dataSharing', 'offersAndWants', value)} |
||||
description="Set who can see what you're offering or looking for" |
||||
/> |
||||
|
||||
<PrivacySlider |
||||
label="Gratitude" |
||||
value={settings.dataSharing.gratitude} |
||||
onChange={(value) => handleSettingChange('dataSharing', 'gratitude', value)} |
||||
description="Control sharing of appreciation and gratitude posts" |
||||
/> |
||||
</Box> |
||||
|
||||
<Divider sx={{ my: 3 }} /> |
||||
|
||||
{/* Re-sharing Settings */} |
||||
<Box sx={{ mb: 2 }}> |
||||
<Typography variant="h6" sx={{ fontWeight: 600, mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}> |
||||
<Refresh fontSize="small" /> |
||||
Re-sharing |
||||
</Typography> |
||||
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}> |
||||
Allow your shared content to be forwarded through your network |
||||
</Typography> |
||||
|
||||
<FormControlLabel |
||||
control={ |
||||
<Switch |
||||
checked={settings.reSharing.enabled} |
||||
onChange={(e) => handleSettingChange('reSharing', 'enabled', e.target.checked)} |
||||
/> |
||||
} |
||||
label="Enable re-sharing of aggregated data" |
||||
sx={{ mb: 3 }} |
||||
/> |
||||
|
||||
{settings.reSharing.enabled && ( |
||||
<Box> |
||||
<Typography variant="body2" sx={{ mb: 2 }}> |
||||
Maximum sharing hops: {settings.reSharing.maxHops} |
||||
</Typography> |
||||
<Slider |
||||
value={settings.reSharing.maxHops} |
||||
onChange={(_, value) => handleSettingChange('reSharing', 'maxHops', value)} |
||||
min={0} |
||||
max={5} |
||||
step={1} |
||||
marks={[ |
||||
{ value: 0, label: '0' }, |
||||
{ value: 1, label: '1' }, |
||||
{ value: 2, label: '2' }, |
||||
{ value: 3, label: '3' }, |
||||
{ value: 4, label: '4' }, |
||||
{ value: 5, label: '5' }, |
||||
]} |
||||
sx={{ color: 'primary.main' }} |
||||
/> |
||||
<Typography variant="caption" color="text.secondary"> |
||||
Your data can be shared up to {settings.reSharing.maxHops} connections away from you |
||||
</Typography> |
||||
</Box> |
||||
)} |
||||
</Box> |
||||
</CardContent> |
||||
</Card> |
||||
); |
||||
}; |
||||
|
||||
export default RCardPrivacySettings; |
Loading…
Reference in new issue