/ frontend / src / components / notifications / NotificationSettings.tsx
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  }