NotificationSettings.tsx
1 import { useState } from 'react' 2 import type { NotificationPreferences, NotificationType } from '../../types/notification' 3 import * as notificationService from '../../services/notification' 4 5 export default function NotificationSettings() { 6 const [prefs, setPrefs] = useState<NotificationPreferences>( 7 notificationService.getPreferences() 8 ) 9 const [saved, setSaved] = useState(false) 10 11 const handleSave = () => { 12 notificationService.savePreferences(prefs) 13 setSaved(true) 14 setTimeout(() => setSaved(false), 2000) 15 } 16 17 const toggleType = (type: NotificationType) => { 18 const enabled = prefs.enabledTypes.includes(type) 19 setPrefs({ 20 ...prefs, 21 enabledTypes: enabled 22 ? prefs.enabledTypes.filter((t) => t !== type) 23 : [...prefs.enabledTypes, type], 24 }) 25 } 26 27 const notificationTypes: { type: NotificationType; label: string; description: string }[] = [ 28 { type: 'vote_started', label: 'Vote Started', description: 'When a PR enters voting phase' }, 29 { type: 'vote_ending', label: 'Vote Ending Soon', description: 'Votes ending in 24 hours' }, 30 { type: 'vote_passed', label: 'Vote Passed', description: 'When a vote passes' }, 31 { type: 'vote_failed', label: 'Vote Failed', description: 'When a vote fails' }, 32 { type: 'pr_sponsored', label: 'PR Sponsored', description: 'When your PR gets sponsored' }, 33 { type: 'pr_merged', label: 'PR Merged', description: 'When a PR is merged' }, 34 { type: 'comment_reply', label: 'Comment Replies', description: 'Replies to your comments' }, 35 { type: 'mention', label: 'Mentions', description: 'When you are mentioned' }, 36 { type: 'emergency_action', label: 'Emergency Actions', description: 'Emergency system alerts' }, 37 { type: 'governor_registered', label: 'New Governors', description: 'New governor registrations' }, 38 { type: 'stake_warning', label: 'Stake Warnings', description: 'Stake below threshold alerts' }, 39 { type: 'missed_vote', label: 'Missed Votes', description: 'When you miss a vote' }, 40 ] 41 42 return ( 43 <div className="bg-white border rounded-lg"> 44 <div className="p-4 border-b"> 45 <h3 className="font-semibold">Notification Settings</h3> 46 <p className="text-sm text-gray-600 mt-1"> 47 Configure how and when you receive notifications 48 </p> 49 </div> 50 51 {/* Delivery Methods */} 52 <div className="p-4 border-b"> 53 <h4 className="font-medium mb-3">Delivery Methods</h4> 54 <div className="space-y-3"> 55 <label className="flex items-center gap-3 cursor-pointer"> 56 <input 57 type="checkbox" 58 checked={prefs.inApp} 59 onChange={(e) => setPrefs({ ...prefs, inApp: e.target.checked })} 60 className="w-4 h-4 rounded border-gray-300" 61 /> 62 <div> 63 <div className="text-sm font-medium">In-App Notifications</div> 64 <div className="text-xs text-gray-500">Show in notification center</div> 65 </div> 66 </label> 67 68 <label className="flex items-center gap-3 cursor-pointer"> 69 <input 70 type="checkbox" 71 checked={prefs.push} 72 onChange={(e) => setPrefs({ ...prefs, push: e.target.checked })} 73 className="w-4 h-4 rounded border-gray-300" 74 /> 75 <div> 76 <div className="text-sm font-medium">Push Notifications</div> 77 <div className="text-xs text-gray-500">Mobile and browser push</div> 78 </div> 79 </label> 80 81 <label className="flex items-center gap-3 cursor-pointer"> 82 <input 83 type="checkbox" 84 checked={prefs.email} 85 onChange={(e) => setPrefs({ ...prefs, email: e.target.checked })} 86 className="w-4 h-4 rounded border-gray-300" 87 /> 88 <div> 89 <div className="text-sm font-medium">Email Notifications</div> 90 <div className="text-xs text-gray-500">Daily digest emails</div> 91 </div> 92 </label> 93 </div> 94 </div> 95 96 {/* Chain Filters */} 97 <div className="p-4 border-b"> 98 <h4 className="font-medium mb-3">Chain Filters</h4> 99 <div className="flex gap-4"> 100 <label className="flex items-center gap-2 cursor-pointer"> 101 <input 102 type="checkbox" 103 checked={prefs.chains.includes('alpha')} 104 onChange={(e) => 105 setPrefs({ 106 ...prefs, 107 chains: e.target.checked 108 ? [...prefs.chains, 'alpha'] 109 : prefs.chains.filter((c) => c !== 'alpha'), 110 }) 111 } 112 className="w-4 h-4 rounded border-gray-300" 113 /> 114 <span className="text-sm">Alpha Chain</span> 115 </label> 116 117 <label className="flex items-center gap-2 cursor-pointer"> 118 <input 119 type="checkbox" 120 checked={prefs.chains.includes('delta')} 121 onChange={(e) => 122 setPrefs({ 123 ...prefs, 124 chains: e.target.checked 125 ? [...prefs.chains, 'delta'] 126 : prefs.chains.filter((c) => c !== 'delta'), 127 }) 128 } 129 className="w-4 h-4 rounded border-gray-300" 130 /> 131 <span className="text-sm">Delta Chain</span> 132 </label> 133 </div> 134 </div> 135 136 {/* Quiet Hours */} 137 <div className="p-4 border-b"> 138 <h4 className="font-medium mb-3">Quiet Hours</h4> 139 <label className="flex items-center gap-3 cursor-pointer mb-3"> 140 <input 141 type="checkbox" 142 checked={prefs.quietHoursEnabled} 143 onChange={(e) => setPrefs({ ...prefs, quietHoursEnabled: e.target.checked })} 144 className="w-4 h-4 rounded border-gray-300" 145 /> 146 <div> 147 <div className="text-sm font-medium">Enable Quiet Hours</div> 148 <div className="text-xs text-gray-500"> 149 Only urgent notifications during quiet hours 150 </div> 151 </div> 152 </label> 153 154 {prefs.quietHoursEnabled && ( 155 <div className="flex items-center gap-3 ml-7"> 156 <input 157 type="time" 158 value={prefs.quietHoursStart} 159 onChange={(e) => setPrefs({ ...prefs, quietHoursStart: e.target.value })} 160 className="px-2 py-1 border rounded text-sm" 161 /> 162 <span className="text-sm text-gray-500">to</span> 163 <input 164 type="time" 165 value={prefs.quietHoursEnd} 166 onChange={(e) => setPrefs({ ...prefs, quietHoursEnd: e.target.value })} 167 className="px-2 py-1 border rounded text-sm" 168 /> 169 </div> 170 )} 171 </div> 172 173 {/* Notification Types */} 174 <div className="p-4 border-b"> 175 <h4 className="font-medium mb-3">Notification Types</h4> 176 <div className="space-y-2"> 177 {notificationTypes.map(({ type, label, description }) => ( 178 <label key={type} className="flex items-center gap-3 cursor-pointer p-2 hover:bg-gray-50 rounded"> 179 <input 180 type="checkbox" 181 checked={prefs.enabledTypes.includes(type)} 182 onChange={() => toggleType(type)} 183 className="w-4 h-4 rounded border-gray-300" 184 /> 185 <div className="flex-1"> 186 <div className="text-sm font-medium">{label}</div> 187 <div className="text-xs text-gray-500">{description}</div> 188 </div> 189 </label> 190 ))} 191 </div> 192 </div> 193 194 {/* Save Button */} 195 <div className="p-4 flex items-center justify-between"> 196 <button 197 onClick={handleSave} 198 className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700" 199 > 200 Save Preferences 201 </button> 202 203 {saved && ( 204 <span className="text-sm text-green-600 flex items-center gap-1"> 205 <CheckIcon className="h-4 w-4" /> 206 Saved 207 </span> 208 )} 209 </div> 210 </div> 211 ) 212 } 213 214 function CheckIcon({ className }: { className?: string }) { 215 return ( 216 <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> 217 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> 218 </svg> 219 ) 220 }