feat: update notification preferences to include granular security alerts
This commit is contained in:
@@ -30,7 +30,7 @@ function BellIcon({ className }: { className?: string }) {
|
|||||||
// --- Types ---
|
// --- Types ---
|
||||||
|
|
||||||
type ProfileSubTab = 'profile' | 'security' | 'preferences'
|
type ProfileSubTab = 'profile' | 'security' | 'preferences'
|
||||||
type NotificationSubTab = 'email' | 'center'
|
type NotificationSubTab = 'security' | 'center'
|
||||||
|
|
||||||
type ActiveSelection =
|
type ActiveSelection =
|
||||||
| { section: 'profile'; subTab: ProfileSubTab }
|
| { section: 'profile'; subTab: ProfileSubTab }
|
||||||
@@ -139,12 +139,14 @@ function ExpandableSubItems({ expanded, children }: { expanded: boolean; childre
|
|||||||
|
|
||||||
// --- Content Components ---
|
// --- Content Components ---
|
||||||
|
|
||||||
// Email Notification Preferences Card
|
// Security Alerts Card (granular security toggles)
|
||||||
const PULSE_NOTIFICATION_OPTIONS = [
|
const SECURITY_ALERT_OPTIONS = [
|
||||||
{ key: 'security_alerts', label: 'Security Alerts', description: 'Important security events like new logins, password changes, and 2FA updates.' },
|
{ key: 'login_alerts', label: 'Login Activity', description: 'New device sign-ins and suspicious login attempts.' },
|
||||||
|
{ key: 'password_alerts', label: 'Password Changes', description: 'Password changes and session revocations.' },
|
||||||
|
{ key: 'two_factor_alerts', label: 'Two-Factor Authentication', description: '2FA enabled/disabled and recovery code changes.' },
|
||||||
]
|
]
|
||||||
|
|
||||||
function EmailNotificationPreferencesCard() {
|
function SecurityAlertsCard() {
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
const [emailNotifications, setEmailNotifications] = useState<Record<string, boolean>>({})
|
const [emailNotifications, setEmailNotifications] = useState<Record<string, boolean>>({})
|
||||||
|
|
||||||
@@ -152,7 +154,7 @@ function EmailNotificationPreferencesCard() {
|
|||||||
if (user?.preferences?.email_notifications) {
|
if (user?.preferences?.email_notifications) {
|
||||||
setEmailNotifications(user.preferences.email_notifications)
|
setEmailNotifications(user.preferences.email_notifications)
|
||||||
} else {
|
} else {
|
||||||
const defaults = PULSE_NOTIFICATION_OPTIONS.reduce((acc, option) => ({
|
const defaults = SECURITY_ALERT_OPTIONS.reduce((acc, option) => ({
|
||||||
...acc,
|
...acc,
|
||||||
[option.key]: true
|
[option.key]: true
|
||||||
}), {} as Record<string, boolean>)
|
}), {} as Record<string, boolean>)
|
||||||
@@ -168,7 +170,7 @@ function EmailNotificationPreferencesCard() {
|
|||||||
setEmailNotifications(newState)
|
setEmailNotifications(newState)
|
||||||
try {
|
try {
|
||||||
await updateUserPreferences({
|
await updateUserPreferences({
|
||||||
email_notifications: newState as { new_file_received: boolean; file_downloaded: boolean; security_alerts: boolean }
|
email_notifications: newState as { new_file_received: boolean; file_downloaded: boolean; login_alerts: boolean; password_alerts: boolean; two_factor_alerts: boolean }
|
||||||
})
|
})
|
||||||
} catch {
|
} catch {
|
||||||
setEmailNotifications(prev => ({
|
setEmailNotifications(prev => ({
|
||||||
@@ -185,13 +187,13 @@ function EmailNotificationPreferencesCard() {
|
|||||||
<BellIcon className="w-5 h-5 text-brand-orange" />
|
<BellIcon className="w-5 h-5 text-brand-orange" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-semibold text-neutral-900 dark:text-white">Email Notifications</h2>
|
<h2 className="text-lg font-semibold text-neutral-900 dark:text-white">Security Alerts</h2>
|
||||||
<p className="text-sm text-neutral-500">Choose which email notifications you receive</p>
|
<p className="text-sm text-neutral-500">Choose which security events trigger email alerts</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{PULSE_NOTIFICATION_OPTIONS.map((item) => (
|
{SECURITY_ALERT_OPTIONS.map((item) => (
|
||||||
<div
|
<div
|
||||||
key={item.key}
|
key={item.key}
|
||||||
className={`flex items-center justify-between p-4 border rounded-xl transition-all duration-200 ${
|
className={`flex items-center justify-between p-4 border rounded-xl transition-all duration-200 ${
|
||||||
@@ -328,7 +330,7 @@ function AppSettingsSection() {
|
|||||||
case 'profile':
|
case 'profile':
|
||||||
return <ProfileSettings activeTab={active.subTab} />
|
return <ProfileSettings activeTab={active.subTab} />
|
||||||
case 'notifications':
|
case 'notifications':
|
||||||
if (active.subTab === 'email') return <EmailNotificationPreferencesCard />
|
if (active.subTab === 'security') return <SecurityAlertsCard />
|
||||||
if (active.subTab === 'center') return (
|
if (active.subTab === 'center') return (
|
||||||
<div className="bg-white dark:bg-neutral-900 rounded-2xl border border-neutral-200 dark:border-neutral-800 p-8 shadow-sm">
|
<div className="bg-white dark:bg-neutral-900 rounded-2xl border border-neutral-200 dark:border-neutral-800 p-8 shadow-sm">
|
||||||
<div className="text-center max-w-md mx-auto">
|
<div className="text-center max-w-md mx-auto">
|
||||||
@@ -410,7 +412,7 @@ function AppSettingsSection() {
|
|||||||
onToggle={() => {
|
onToggle={() => {
|
||||||
toggleSection('notifications')
|
toggleSection('notifications')
|
||||||
if (!expanded.has('notifications')) {
|
if (!expanded.has('notifications')) {
|
||||||
selectSubTab({ section: 'notifications', subTab: 'email' })
|
selectSubTab({ section: 'notifications', subTab: 'security' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
icon={BellIcon}
|
icon={BellIcon}
|
||||||
@@ -419,9 +421,9 @@ function AppSettingsSection() {
|
|||||||
/>
|
/>
|
||||||
<ExpandableSubItems expanded={expanded.has('notifications')}>
|
<ExpandableSubItems expanded={expanded.has('notifications')}>
|
||||||
<SubItem
|
<SubItem
|
||||||
active={active.section === 'notifications' && active.subTab === 'email'}
|
active={active.section === 'notifications' && active.subTab === 'security'}
|
||||||
onClick={() => selectSubTab({ section: 'notifications', subTab: 'email' })}
|
onClick={() => selectSubTab({ section: 'notifications', subTab: 'security' })}
|
||||||
label="Email Preferences"
|
label="Security Alerts"
|
||||||
/>
|
/>
|
||||||
<SubItem
|
<SubItem
|
||||||
active={active.section === 'notifications' && active.subTab === 'center'}
|
active={active.section === 'notifications' && active.subTab === 'center'}
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ export interface UserPreferences {
|
|||||||
email_notifications: {
|
email_notifications: {
|
||||||
new_file_received: boolean
|
new_file_received: boolean
|
||||||
file_downloaded: boolean
|
file_downloaded: boolean
|
||||||
security_alerts: boolean
|
login_alerts: boolean
|
||||||
|
password_alerts: boolean
|
||||||
|
two_factor_alerts: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ interface User {
|
|||||||
email_notifications?: {
|
email_notifications?: {
|
||||||
new_file_received: boolean
|
new_file_received: boolean
|
||||||
file_downloaded: boolean
|
file_downloaded: boolean
|
||||||
security_alerts: boolean
|
login_alerts: boolean
|
||||||
|
password_alerts: boolean
|
||||||
|
two_factor_alerts: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -8,7 +8,7 @@
|
|||||||
"name": "pulse-frontend",
|
"name": "pulse-frontend",
|
||||||
"version": "0.11.1-alpha",
|
"version": "0.11.1-alpha",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ciphera-net/ui": "^0.0.76",
|
"@ciphera-net/ui": "^0.0.77",
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@simplewebauthn/browser": "^13.2.2",
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
@@ -1543,9 +1543,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ciphera-net/ui": {
|
"node_modules/@ciphera-net/ui": {
|
||||||
"version": "0.0.76",
|
"version": "0.0.77",
|
||||||
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.76/f584def8ea9ac4bccc52abdd281212f2e28959c0",
|
"resolved": "https://npm.pkg.github.com/download/@ciphera-net/ui/0.0.77/c243fce3b29ee4b3d90dd5b6b5a7d93ff3a955b9",
|
||||||
"integrity": "sha512-Bw7KOSUXQajfMAmgU39XiJGFudxx7Gj7Tnjr2tHHL+2oLOoBuDCNRRIC+A6Uo5WmxDBitw6voipoOfRcFo5XAA==",
|
"integrity": "sha512-Z+aLef5843t9TIvaSV+ecC4sQi2VzX+hE6/7A/4/Y49CT91vg0x9QuQuQiV7B4j93Ui1yv+aZyWN21NY3mhKbA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"type-check": "tsc --noEmit"
|
"type-check": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ciphera-net/ui": "^0.0.76",
|
"@ciphera-net/ui": "^0.0.77",
|
||||||
"@ducanh2912/next-pwa": "^10.2.9",
|
"@ducanh2912/next-pwa": "^10.2.9",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@simplewebauthn/browser": "^13.2.2",
|
"@simplewebauthn/browser": "^13.2.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user