Skip to content

Commit 76f1668

Browse files
committed
v1.9.0: Consolidate settings tables and enhance notification system
1 parent e583913 commit 76f1668

26 files changed

+1716
-129
lines changed

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,41 @@ All notable changes to the PowerPulse project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.9.0] - 2025-03-01
9+
10+
### Changed
11+
- Consolidated settings tables:
12+
- Merged notification settings into the user_settings table
13+
- Created a view for backward compatibility
14+
- Improved settings management with a unified approach
15+
- Enhanced data consistency and reduced database complexity
16+
- Simplified API endpoints for settings management
17+
- Enhanced notification system:
18+
- Improved settings loading and saving
19+
- Added better error handling and validation
20+
- Enhanced settings synchronization between client and server
21+
- Added detailed logging for troubleshooting
22+
- Improved verification of saved settings
23+
24+
### Added
25+
- New migration system for settings consolidation:
26+
- Added SQL migration script for consolidating tables
27+
- Created JavaScript migration utility for data transfer
28+
- Added shell script for easy migration execution
29+
- Implemented backward compatibility layer
30+
- New userSettings API endpoint:
31+
- Added comprehensive validation for all settings
32+
- Improved error handling and reporting
33+
- Enhanced security with proper input validation
34+
- Added detailed logging for troubleshooting
35+
36+
### Technical
37+
- Improved code organization:
38+
- Created dedicated userSettingsService for client-side settings management
39+
- Enhanced SettingsContext with better state management
40+
- Added SettingsLoader component for improved UX during settings loading
41+
- Implemented proper settings synchronization between components
42+
843
## [1.8.3] - 2025-03-01
944

1045
### Added

client/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/App.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Routes, Route, Navigate } from 'react-router-dom';
33
import { useAuth } from './context/AuthContext';
44
import { ToastProvider } from './components/ToastContainer';
55
import ErrorBoundary from './components/ErrorBoundary';
6+
import SettingsLoader from './components/SettingsLoader';
67

78
// Pages
89
import Dashboard from './pages/Dashboard';
@@ -54,6 +55,7 @@ const App = () => {
5455
return (
5556
<ErrorBoundary>
5657
<ToastProvider>
58+
<SettingsLoader />
5759
<Routes>
5860
{isFirstTimeSetup ? (
5961
<>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import React, { useEffect } from 'react';
2+
import { useSettings } from '../context/SettingsContext';
3+
import { useAuth } from '../context/AuthContext';
4+
5+
/**
6+
* SettingsLoader Component
7+
*
8+
* This component is responsible for loading settings from the server
9+
* when the app starts or when the user logs in. It should be mounted
10+
* at the top level of the app.
11+
*/
12+
const SettingsLoader = () => {
13+
const { loadSettingsFromServer } = useSettings();
14+
const { user } = useAuth();
15+
16+
// Load settings from server when the component mounts or when the user changes
17+
useEffect(() => {
18+
if (user) {
19+
console.log('SettingsLoader: Loading settings from server...');
20+
loadSettingsFromServer()
21+
.then(settings => {
22+
if (settings) {
23+
console.log('SettingsLoader: Current settings in context:', settings);
24+
}
25+
})
26+
.catch(error => {
27+
console.error('SettingsLoader: Error loading settings:', error);
28+
});
29+
}
30+
}, [user, loadSettingsFromServer]);
31+
32+
// This component doesn't render anything
33+
return null;
34+
};
35+
36+
export default SettingsLoader;

client/src/components/notifications/DiscordNotificationSettings.jsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from 'react';
2-
import axios from 'axios';
32
import { FiBell } from 'react-icons/fi';
3+
import userSettingsService from '../../services/userSettingsService';
44

55
/**
66
* Discord Notification Settings Component
@@ -27,9 +27,7 @@ const DiscordNotificationSettings = ({
2727

2828
setIsTesting(true);
2929
try {
30-
await axios.post('/api/notifications/test', {
31-
discord_webhook_url: settings.discordWebhookUrl
32-
});
30+
await userSettingsService.sendTestDiscordNotification(settings.discordWebhookUrl);
3331

3432
setSuccess('Test notification sent to Discord');
3533
setTimeout(() => setSuccess(null), 3000);
@@ -60,7 +58,23 @@ const DiscordNotificationSettings = ({
6058
id="discordWebhookUrl"
6159
name="discordWebhookUrl"
6260
value={settings.discordWebhookUrl || ''}
63-
onChange={(e) => updateSetting('discordWebhookUrl', e.target.value)}
61+
onChange={(e) => {
62+
console.log(`Updating Discord webhook URL to: ${e.target.value}`);
63+
updateSetting('discordWebhookUrl', e.target.value);
64+
}}
65+
onBlur={async () => {
66+
// Save the setting to the server when the input loses focus
67+
console.log('Discord webhook URL input lost focus, saving to server');
68+
try {
69+
await userSettingsService.updateNotificationSettings({
70+
...settings,
71+
discordWebhookUrl: settings.discordWebhookUrl
72+
});
73+
console.log('Saved Discord webhook URL to server');
74+
} catch (error) {
75+
console.error('Error saving Discord webhook URL to server:', error);
76+
}
77+
}}
6478
disabled={!notificationsEnabled}
6579
placeholder="https://discord.com/api/webhooks/..."
6680
className={`mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-primary-500 focus:border-primary-500 ${!notificationsEnabled ? 'opacity-50' : ''}`}

client/src/components/notifications/EmailNotificationSettings.jsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from 'react';
2-
import axios from 'axios';
32
import { FiMail } from 'react-icons/fi';
3+
import userSettingsService from '../../services/userSettingsService';
44

55
/**
66
* Email Notification Settings Component
@@ -27,9 +27,7 @@ const EmailNotificationSettings = ({
2727

2828
setIsTesting(true);
2929
try {
30-
await axios.post('/api/notifications/test-email', {
31-
email_recipients: settings.emailRecipients
32-
});
30+
await userSettingsService.sendTestEmailNotification(settings.emailRecipients);
3331

3432
setSuccess('Test notification sent to email recipients');
3533
setTimeout(() => setSuccess(null), 3000);
@@ -55,7 +53,22 @@ const EmailNotificationSettings = ({
5553
name="emailNotifications"
5654
type="checkbox"
5755
checked={settings.emailNotifications}
58-
onChange={(e) => updateSetting('emailNotifications', e.target.checked)}
56+
onChange={async (e) => {
57+
console.log(`Updating Email notifications to: ${e.target.checked}`);
58+
await updateSetting('emailNotifications', e.target.checked);
59+
60+
// Save the setting to the server immediately
61+
console.log('Email notifications checkbox changed, saving to server');
62+
try {
63+
await userSettingsService.updateNotificationSettings({
64+
...settings,
65+
emailNotifications: e.target.checked
66+
});
67+
console.log('Saved email notifications setting to server');
68+
} catch (error) {
69+
console.error('Error saving email notifications setting to server:', error);
70+
}
71+
}}
5972
disabled={!notificationsEnabled}
6073
className="focus:ring-primary-500 h-4 w-4 text-primary-600 border-gray-300 rounded disabled:opacity-50"
6174
/>
@@ -85,7 +98,23 @@ const EmailNotificationSettings = ({
8598
id="emailRecipients"
8699
name="emailRecipients"
87100
value={settings.emailRecipients || ''}
88-
onChange={(e) => updateSetting('emailRecipients', e.target.value)}
101+
onChange={(e) => {
102+
console.log(`Updating Email recipients to: ${e.target.value}`);
103+
updateSetting('emailRecipients', e.target.value);
104+
}}
105+
onBlur={async () => {
106+
// Save the setting to the server when the input loses focus
107+
console.log('Email recipients input lost focus, saving to server');
108+
try {
109+
await userSettingsService.updateNotificationSettings({
110+
...settings,
111+
emailRecipients: settings.emailRecipients
112+
});
113+
console.log('Saved email recipients to server');
114+
} catch (error) {
115+
console.error('Error saving email recipients to server:', error);
116+
}
117+
}}
89118
disabled={!notificationsEnabled || !settings.emailNotifications}
90119
placeholder="email1@example.com, email2@example.com"
91120
className={`mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-primary-500 focus:border-primary-500 ${!notificationsEnabled || !settings.emailNotifications ? 'opacity-50' : ''}`}

client/src/components/notifications/NotificationSettings.jsx

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from 'react';
2-
import axios from 'axios';
32
import { FiBell, FiSave, FiChevronDown, FiChevronRight } from 'react-icons/fi';
3+
import userSettingsService from '../../services/userSettingsService';
44
import { useSettings } from '../../context/SettingsContext';
55
import DiscordNotificationSettings from './DiscordNotificationSettings';
66
import SlackNotificationSettings from './SlackNotificationSettings';
@@ -38,23 +38,28 @@ const NotificationSettings = ({ setError, setSuccess }) => {
3838
const handleSaveSettings = async () => {
3939
setIsSaving(true);
4040
try {
41-
// Convert client settings format to server format
42-
const serverSettings = {
43-
discord_webhook_url: settings.discordWebhookUrl,
44-
slack_webhook_url: settings.slackWebhookUrl,
45-
notifications_enabled: settings.notifications,
46-
battery_notifications: settings.batteryNotifications,
47-
low_battery_notifications: settings.lowBatteryNotifications,
48-
email_notifications: settings.emailNotifications,
49-
email_recipients: settings.emailRecipients
50-
};
51-
52-
// Send settings to server
53-
const response = await axios.post('/api/notifications/settings', serverSettings);
41+
console.log('Saving notification settings to server:', settings);
42+
43+
// Use the userSettingsService to save all settings
44+
const response = await userSettingsService.updateNotificationSettings(settings);
5445

5546
setSuccess('Notification settings saved successfully');
5647
setTimeout(() => setSuccess(null), 3000);
57-
console.log('Saved notification settings to server:', response.data);
48+
console.log('Saved notification settings to server:', response);
49+
50+
// Force reload settings from server to verify they were saved correctly
51+
setTimeout(() => {
52+
console.log('Reloading settings from server to verify...');
53+
// We can't directly call loadSettingsFromServer here because it's not exposed
54+
// But we can trigger a reload by calling getUserSettings
55+
userSettingsService.getUserSettings()
56+
.then(serverSettings => {
57+
console.log('Reloaded settings from server:', serverSettings);
58+
})
59+
.catch(error => {
60+
console.error('Error reloading settings from server:', error);
61+
});
62+
}, 1000);
5863
} catch (err) {
5964
setError(err.response?.data?.message || 'Failed to save notification settings');
6065
setTimeout(() => setError(null), 5000);
@@ -73,7 +78,22 @@ const NotificationSettings = ({ setError, setSuccess }) => {
7378
name="notifications"
7479
type="checkbox"
7580
checked={settings.notifications}
76-
onChange={(e) => updateSetting('notifications', e.target.checked)}
81+
onChange={async (e) => {
82+
console.log(`Updating main notifications to: ${e.target.checked}`);
83+
await updateSetting('notifications', e.target.checked);
84+
85+
// Save the setting to the server immediately
86+
console.log('Main notifications checkbox changed, saving to server');
87+
try {
88+
await userSettingsService.updateNotificationSettings({
89+
...settings,
90+
notifications: e.target.checked
91+
});
92+
console.log('Saved main notifications setting to server');
93+
} catch (error) {
94+
console.error('Error saving main notifications setting to server:', error);
95+
}
96+
}}
7797
className="focus:ring-primary-500 h-4 w-4 text-primary-600 border-gray-300 rounded"
7898
/>
7999
</div>

client/src/components/notifications/NotificationTypes.jsx

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import userSettingsService from '../../services/userSettingsService';
23

34
/**
45
* Notification Types Component
@@ -23,7 +24,22 @@ const NotificationTypes = ({
2324
name="batteryNotifications"
2425
type="checkbox"
2526
checked={settings.batteryNotifications !== false}
26-
onChange={(e) => updateSetting('batteryNotifications', e.target.checked)}
27+
onChange={async (e) => {
28+
console.log(`Updating Battery notifications to: ${e.target.checked}`);
29+
await updateSetting('batteryNotifications', e.target.checked);
30+
31+
// Save the setting to the server immediately
32+
console.log('Battery notifications checkbox changed, saving to server');
33+
try {
34+
await userSettingsService.updateNotificationSettings({
35+
...settings,
36+
batteryNotifications: e.target.checked
37+
});
38+
console.log('Saved battery notifications setting to server');
39+
} catch (error) {
40+
console.error('Error saving battery notifications setting to server:', error);
41+
}
42+
}}
2743
disabled={!notificationsEnabled}
2844
className="focus:ring-primary-500 h-4 w-4 text-primary-600 border-gray-300 rounded disabled:opacity-50"
2945
/>
@@ -48,7 +64,22 @@ const NotificationTypes = ({
4864
name="lowBatteryNotifications"
4965
type="checkbox"
5066
checked={settings.lowBatteryNotifications !== false}
51-
onChange={(e) => updateSetting('lowBatteryNotifications', e.target.checked)}
67+
onChange={async (e) => {
68+
console.log(`Updating Low Battery notifications to: ${e.target.checked}`);
69+
await updateSetting('lowBatteryNotifications', e.target.checked);
70+
71+
// Save the setting to the server immediately
72+
console.log('Low battery notifications checkbox changed, saving to server');
73+
try {
74+
await userSettingsService.updateNotificationSettings({
75+
...settings,
76+
lowBatteryNotifications: e.target.checked
77+
});
78+
console.log('Saved low battery notifications setting to server');
79+
} catch (error) {
80+
console.error('Error saving low battery notifications setting to server:', error);
81+
}
82+
}}
5283
disabled={!notificationsEnabled}
5384
className="focus:ring-primary-500 h-4 w-4 text-primary-600 border-gray-300 rounded disabled:opacity-50"
5485
/>

client/src/components/notifications/SlackNotificationSettings.jsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useState } from 'react';
2-
import axios from 'axios';
32
import { FiSlack } from 'react-icons/fi';
3+
import userSettingsService from '../../services/userSettingsService';
44

55
/**
66
* Slack Notification Settings Component
@@ -27,9 +27,7 @@ const SlackNotificationSettings = ({
2727

2828
setIsTesting(true);
2929
try {
30-
await axios.post('/api/notifications/test-slack', {
31-
slack_webhook_url: settings.slackWebhookUrl
32-
});
30+
await userSettingsService.sendTestSlackNotification(settings.slackWebhookUrl);
3331

3432
setSuccess('Test notification sent to Slack');
3533
setTimeout(() => setSuccess(null), 3000);
@@ -60,7 +58,23 @@ const SlackNotificationSettings = ({
6058
id="slackWebhookUrl"
6159
name="slackWebhookUrl"
6260
value={settings.slackWebhookUrl || ''}
63-
onChange={(e) => updateSetting('slackWebhookUrl', e.target.value)}
61+
onChange={(e) => {
62+
console.log(`Updating Slack webhook URL to: ${e.target.value}`);
63+
updateSetting('slackWebhookUrl', e.target.value);
64+
}}
65+
onBlur={async () => {
66+
// Save the setting to the server when the input loses focus
67+
console.log('Slack webhook URL input lost focus, saving to server');
68+
try {
69+
await userSettingsService.updateNotificationSettings({
70+
...settings,
71+
slackWebhookUrl: settings.slackWebhookUrl
72+
});
73+
console.log('Saved Slack webhook URL to server');
74+
} catch (error) {
75+
console.error('Error saving Slack webhook URL to server:', error);
76+
}
77+
}}
6478
disabled={!notificationsEnabled}
6579
placeholder="https://hooks.slack.com/services/..."
6680
className={`mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md shadow-sm py-2 px-3 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-primary-500 focus:border-primary-500 ${!notificationsEnabled ? 'opacity-50' : ''}`}

0 commit comments

Comments
 (0)