add default settings

This commit is contained in:
chark1es 2025-03-02 01:42:36 -08:00
parent 4a33fc3ce4
commit 03b0e677ed
5 changed files with 265 additions and 258 deletions

View file

@ -4,6 +4,7 @@ import UserProfileSettings from "./SettingsSection/UserProfileSettings";
import AccountSecuritySettings from "./SettingsSection/AccountSecuritySettings"; import AccountSecuritySettings from "./SettingsSection/AccountSecuritySettings";
import NotificationSettings from "./SettingsSection/NotificationSettings"; import NotificationSettings from "./SettingsSection/NotificationSettings";
import DisplaySettings from "./SettingsSection/DisplaySettings"; import DisplaySettings from "./SettingsSection/DisplaySettings";
import { Toaster } from "react-hot-toast";
--- ---
<div id="settings-section" class=""> <div id="settings-section" class="">
@ -12,6 +13,8 @@ import DisplaySettings from "./SettingsSection/DisplaySettings";
<p class="opacity-70">Manage your account settings and preferences</p> <p class="opacity-70">Manage your account settings and preferences</p>
</div> </div>
<Toaster position="top-right" client:load />
<!-- Profile Settings Card --> <!-- Profile Settings Card -->
<div <div
class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-6" class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-6"

View file

@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { SendLog } from '../../../scripts/pocketbase/SendLog'; import { SendLog } from '../../../scripts/pocketbase/SendLog';
import { toast } from 'react-hot-toast';
export default function AccountSecuritySettings() { export default function AccountSecuritySettings() {
const auth = Authentication.getInstance(); const auth = Authentication.getInstance();
@ -50,6 +51,7 @@ export default function AccountSecuritySettings() {
window.location.href = '/'; window.location.href = '/';
} catch (error) { } catch (error) {
console.error('Error during logout:', error); console.error('Error during logout:', error);
toast.error('Failed to log out. Please try again.');
} }
}; };
@ -96,10 +98,8 @@ export default function AccountSecuritySettings() {
if (!isAuthenticated) { if (!isAuthenticated) {
return ( return (
<div className="alert alert-error"> <div className="p-4 text-error bg-error bg-opacity-10 rounded-lg">
<div> <span>You must be logged in to access this page.</span>
<span>You must be logged in to access this page.</span>
</div>
</div> </div>
); );
} }
@ -134,11 +134,9 @@ export default function AccountSecuritySettings() {
Password management is handled through your IEEEUCSD account. Password management is handled through your IEEEUCSD account.
</p> </p>
<div className="alert alert-info"> <p className="text-sm text-info p-3 bg-info bg-opacity-10 rounded-lg">
<div> To change your password, please use the "Forgot Password" option on the login page.
<span>To change your password, please visit the UCSD SSO portal.</span> </p>
</div>
</div>
</div> </div>
{/* Account Actions */} {/* Account Actions */}
@ -153,14 +151,10 @@ export default function AccountSecuritySettings() {
Sign Out Sign Out
</button> </button>
<div className="alert alert-warning"> <p className="text-sm text-warning p-3 bg-warning bg-opacity-10 rounded-lg">
<div> If you need to delete your account or have other account-related issues,
<span> please contact an IEEE UCSD administrator.
If you need to delete your account or have other account-related issues, </p>
please contact an IEEE UCSD administrator.
</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -2,16 +2,27 @@ import { useState, useEffect } from 'react';
import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { Update } from '../../../scripts/pocketbase/Update'; import { Update } from '../../../scripts/pocketbase/Update';
import { Collections } from '../../../schemas/pocketbase/schema'; import { Collections } from '../../../schemas/pocketbase/schema';
import { toast } from 'react-hot-toast';
// Default display preferences
const DEFAULT_DISPLAY_PREFERENCES = {
theme: 'dark',
fontSize: 'medium'
};
// Default accessibility settings
const DEFAULT_ACCESSIBILITY_SETTINGS = {
colorBlindMode: false,
reducedMotion: false
};
export default function DisplaySettings() { export default function DisplaySettings() {
const auth = Authentication.getInstance(); const auth = Authentication.getInstance();
const update = Update.getInstance(); const update = Update.getInstance();
const [theme, setTheme] = useState('dark'); const [theme, setTheme] = useState(DEFAULT_DISPLAY_PREFERENCES.theme);
const [fontSize, setFontSize] = useState('medium'); const [fontSize, setFontSize] = useState(DEFAULT_DISPLAY_PREFERENCES.fontSize);
const [colorBlindMode, setColorBlindMode] = useState(false); const [colorBlindMode, setColorBlindMode] = useState(DEFAULT_ACCESSIBILITY_SETTINGS.colorBlindMode);
const [reducedMotion, setReducedMotion] = useState(false); const [reducedMotion, setReducedMotion] = useState(DEFAULT_ACCESSIBILITY_SETTINGS.reducedMotion);
const [successMessage, setSuccessMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
// Load saved preferences on component mount // Load saved preferences on component mount
@ -19,18 +30,20 @@ export default function DisplaySettings() {
const loadPreferences = async () => { const loadPreferences = async () => {
try { try {
// First check localStorage for immediate UI updates // First check localStorage for immediate UI updates
const savedTheme = localStorage.getItem('theme') || 'dark'; const savedTheme = localStorage.getItem('theme') || DEFAULT_DISPLAY_PREFERENCES.theme;
const savedFontSize = localStorage.getItem('fontSize') || 'medium'; // Ensure theme is either light or dark
const validTheme = ['light', 'dark'].includes(savedTheme) ? savedTheme : DEFAULT_DISPLAY_PREFERENCES.theme;
const savedFontSize = localStorage.getItem('fontSize') || DEFAULT_DISPLAY_PREFERENCES.fontSize;
const savedColorBlindMode = localStorage.getItem('colorBlindMode') === 'true'; const savedColorBlindMode = localStorage.getItem('colorBlindMode') === 'true';
const savedReducedMotion = localStorage.getItem('reducedMotion') === 'true'; const savedReducedMotion = localStorage.getItem('reducedMotion') === 'true';
setTheme(savedTheme); setTheme(validTheme);
setFontSize(savedFontSize); setFontSize(savedFontSize);
setColorBlindMode(savedColorBlindMode); setColorBlindMode(savedColorBlindMode);
setReducedMotion(savedReducedMotion); setReducedMotion(savedReducedMotion);
// Apply theme to document // Apply theme to document
document.documentElement.setAttribute('data-theme', savedTheme); document.documentElement.setAttribute('data-theme', validTheme);
// Apply font size // Apply font size
applyFontSize(savedFontSize); applyFontSize(savedFontSize);
@ -46,67 +59,116 @@ export default function DisplaySettings() {
// Then check if user has saved preferences in their profile // Then check if user has saved preferences in their profile
const user = auth.getCurrentUser(); const user = auth.getCurrentUser();
if (user && user.display_preferences) { if (user) {
try { let needsDisplayPrefsUpdate = false;
const userPrefs = JSON.parse(user.display_preferences); let needsAccessibilityUpdate = false;
// Only update if values exist and are different from localStorage // Check and handle display preferences
if (userPrefs.theme && userPrefs.theme !== savedTheme) { if (user.display_preferences && typeof user.display_preferences === 'string' && user.display_preferences.trim() !== '') {
setTheme(userPrefs.theme); try {
localStorage.setItem('theme', userPrefs.theme); const userPrefs = JSON.parse(user.display_preferences);
document.documentElement.setAttribute('data-theme', userPrefs.theme);
}
if (userPrefs.fontSize && userPrefs.fontSize !== savedFontSize) { // Only update if values exist and are different from localStorage
setFontSize(userPrefs.fontSize); if (userPrefs.theme && ['light', 'dark'].includes(userPrefs.theme) && userPrefs.theme !== validTheme) {
localStorage.setItem('fontSize', userPrefs.fontSize); setTheme(userPrefs.theme);
applyFontSize(userPrefs.fontSize); localStorage.setItem('theme', userPrefs.theme);
document.documentElement.setAttribute('data-theme', userPrefs.theme);
} else if (!['light', 'dark'].includes(userPrefs.theme)) {
// If theme is not valid, mark for update
needsDisplayPrefsUpdate = true;
}
if (userPrefs.fontSize && userPrefs.fontSize !== savedFontSize) {
setFontSize(userPrefs.fontSize);
localStorage.setItem('fontSize', userPrefs.fontSize);
applyFontSize(userPrefs.fontSize);
}
} catch (e) {
console.error('Error parsing display preferences:', e);
needsDisplayPrefsUpdate = true;
} }
} catch (e) { } else {
console.error('Error parsing display preferences:', e); needsDisplayPrefsUpdate = true;
} }
}
if (user && user.accessibility_settings) { // Check and handle accessibility settings
try { if (user.accessibility_settings && typeof user.accessibility_settings === 'string' && user.accessibility_settings.trim() !== '') {
const accessibilityPrefs = JSON.parse(user.accessibility_settings); try {
const accessibilityPrefs = JSON.parse(user.accessibility_settings);
if (typeof accessibilityPrefs.colorBlindMode === 'boolean' && if (typeof accessibilityPrefs.colorBlindMode === 'boolean' &&
accessibilityPrefs.colorBlindMode !== savedColorBlindMode) { accessibilityPrefs.colorBlindMode !== savedColorBlindMode) {
setColorBlindMode(accessibilityPrefs.colorBlindMode); setColorBlindMode(accessibilityPrefs.colorBlindMode);
localStorage.setItem('colorBlindMode', accessibilityPrefs.colorBlindMode.toString()); localStorage.setItem('colorBlindMode', accessibilityPrefs.colorBlindMode.toString());
if (accessibilityPrefs.colorBlindMode) { if (accessibilityPrefs.colorBlindMode) {
document.documentElement.classList.add('color-blind-mode'); document.documentElement.classList.add('color-blind-mode');
} else { } else {
document.documentElement.classList.remove('color-blind-mode'); document.documentElement.classList.remove('color-blind-mode');
}
} }
}
if (typeof accessibilityPrefs.reducedMotion === 'boolean' && if (typeof accessibilityPrefs.reducedMotion === 'boolean' &&
accessibilityPrefs.reducedMotion !== savedReducedMotion) { accessibilityPrefs.reducedMotion !== savedReducedMotion) {
setReducedMotion(accessibilityPrefs.reducedMotion); setReducedMotion(accessibilityPrefs.reducedMotion);
localStorage.setItem('reducedMotion', accessibilityPrefs.reducedMotion.toString()); localStorage.setItem('reducedMotion', accessibilityPrefs.reducedMotion.toString());
if (accessibilityPrefs.reducedMotion) { if (accessibilityPrefs.reducedMotion) {
document.documentElement.classList.add('reduced-motion'); document.documentElement.classList.add('reduced-motion');
} else { } else {
document.documentElement.classList.remove('reduced-motion'); document.documentElement.classList.remove('reduced-motion');
}
} }
} catch (e) {
console.error('Error parsing accessibility settings:', e);
needsAccessibilityUpdate = true;
} }
} catch (e) { } else {
console.error('Error parsing accessibility settings:', e); needsAccessibilityUpdate = true;
}
// Initialize default settings if needed
if (needsDisplayPrefsUpdate || needsAccessibilityUpdate) {
await initializeDefaultSettings(user.id, needsDisplayPrefsUpdate, needsAccessibilityUpdate);
} }
} }
} catch (error) { } catch (error) {
console.error('Error loading preferences:', error); console.error('Error loading preferences:', error);
setErrorMessage('Failed to load display preferences'); toast.error('Failed to load display preferences');
} }
}; };
loadPreferences(); loadPreferences();
}, []); }, []);
// Initialize default settings if not set
const initializeDefaultSettings = async (userId: string, updateDisplayPrefs: boolean, updateAccessibility: boolean) => {
try {
const updateData: any = {};
if (updateDisplayPrefs) {
updateData.display_preferences = JSON.stringify({
theme,
fontSize
});
}
if (updateAccessibility) {
updateData.accessibility_settings = JSON.stringify({
colorBlindMode,
reducedMotion
});
}
if (Object.keys(updateData).length > 0) {
await update.updateFields(Collections.USERS, userId, updateData);
console.log('Initialized default display and accessibility settings');
}
} catch (error) {
console.error('Error initializing default settings:', error);
}
};
// Apply font size to document // Apply font size to document
const applyFontSize = (size: string) => { const applyFontSize = (size: string) => {
const htmlElement = document.documentElement; const htmlElement = document.documentElement;
@ -191,8 +253,6 @@ export default function DisplaySettings() {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setSaving(true); setSaving(true);
setSuccessMessage('');
setErrorMessage('');
try { try {
const user = auth.getCurrentUser(); const user = auth.getCurrentUser();
@ -221,15 +281,10 @@ export default function DisplaySettings() {
); );
// Show success message // Show success message
setSuccessMessage('Display settings saved successfully!'); toast.success('Display settings saved successfully!');
// Clear success message after 3 seconds
setTimeout(() => {
setSuccessMessage('');
}, 3000);
} catch (error) { } catch (error) {
console.error('Error saving display settings:', error); console.error('Error saving display settings:', error);
setErrorMessage('Failed to save display settings to your profile'); toast.error('Failed to save display settings to your profile');
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -237,114 +292,71 @@ export default function DisplaySettings() {
return ( return (
<div> <div>
{successMessage && (
<div className="alert alert-success mb-4">
<div>
<span>{successMessage}</span>
</div>
</div>
)}
{errorMessage && (
<div className="alert alert-error mb-4">
<div>
<span>{errorMessage}</span>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{/* Theme Selection */} {/* Theme Settings */}
<div className="form-control"> <div>
<label className="label"> <h4 className="font-semibold text-lg mb-2">Theme</h4>
<span className="label-text font-medium">Theme</span> <div className="form-control w-full max-w-xs">
</label> <select
<select value={theme}
className="select select-bordered w-full" onChange={handleThemeChange}
value={theme} className="select select-bordered"
onChange={handleThemeChange} >
> <option value="light">Light</option>
<option value="light">Light</option> <option value="dark">Dark</option>
<option value="dark">Dark</option> </select>
<option value="cupcake">Cupcake</option> <label className="label">
<option value="bumblebee">Bumblebee</option> <span className="label-text-alt">Select your preferred theme</span>
<option value="emerald">Emerald</option> </label>
<option value="corporate">Corporate</option> </div>
<option value="synthwave">Synthwave</option>
<option value="retro">Retro</option>
<option value="cyberpunk">Cyberpunk</option>
<option value="valentine">Valentine</option>
<option value="halloween">Halloween</option>
<option value="garden">Garden</option>
<option value="forest">Forest</option>
<option value="aqua">Aqua</option>
<option value="lofi">Lo-Fi</option>
<option value="pastel">Pastel</option>
<option value="fantasy">Fantasy</option>
<option value="wireframe">Wireframe</option>
<option value="black">Black</option>
<option value="luxury">Luxury</option>
<option value="dracula">Dracula</option>
<option value="cmyk">CMYK</option>
<option value="autumn">Autumn</option>
<option value="business">Business</option>
<option value="acid">Acid</option>
<option value="lemonade">Lemonade</option>
<option value="night">Night</option>
<option value="coffee">Coffee</option>
<option value="winter">Winter</option>
</select>
<label className="label">
<span className="label-text-alt">Choose a theme for your dashboard</span>
</label>
</div> </div>
{/* Font Size */} {/* Font Size Settings */}
<div className="form-control"> <div>
<label className="label"> <h4 className="font-semibold text-lg mb-2">Font Size</h4>
<span className="label-text font-medium">Font Size</span> <div className="form-control w-full max-w-xs">
</label> <select
<select value={fontSize}
className="select select-bordered w-full" onChange={handleFontSizeChange}
value={fontSize} className="select select-bordered"
onChange={handleFontSizeChange} >
> <option value="small">Small</option>
<option value="small">Small</option> <option value="medium">Medium</option>
<option value="medium">Medium</option> <option value="large">Large</option>
<option value="large">Large</option> <option value="extra-large">Extra Large</option>
<option value="extra-large">Extra Large</option> </select>
</select> <label className="label">
<label className="label"> <span className="label-text-alt">Select your preferred font size</span>
<span className="label-text-alt">Adjust the text size for better readability</span> </label>
</label> </div>
</div> </div>
{/* Accessibility Options */} {/* Accessibility Settings */}
<div className="form-control"> <div>
<label className="label"> <h4 className="font-semibold text-lg mb-2">Accessibility</h4>
<span className="label-text font-medium">Accessibility Options</span>
</label>
<div className="space-y-4 p-4 bg-base-200 rounded-lg"> <div className="form-control">
<label className="cursor-pointer label justify-start gap-4"> <label className="cursor-pointer label justify-start gap-4">
<input <input
type="checkbox" type="checkbox"
className="toggle toggle-primary"
checked={colorBlindMode} checked={colorBlindMode}
onChange={handleColorBlindModeChange} onChange={handleColorBlindModeChange}
className="toggle toggle-primary"
/> />
<div> <div>
<span className="label-text font-medium">Color Blind Mode</span> <span className="label-text font-medium">Color Blind Mode</span>
<p className="text-xs opacity-70">Enhances color contrast for better visibility</p> <p className="text-xs opacity-70">Enhances color contrast and uses color-blind friendly palettes</p>
</div> </div>
</label> </label>
</div>
<div className="form-control mt-2">
<label className="cursor-pointer label justify-start gap-4"> <label className="cursor-pointer label justify-start gap-4">
<input <input
type="checkbox" type="checkbox"
className="toggle toggle-primary"
checked={reducedMotion} checked={reducedMotion}
onChange={handleReducedMotionChange} onChange={handleReducedMotionChange}
className="toggle toggle-primary"
/> />
<div> <div>
<span className="label-text font-medium">Reduced Motion</span> <span className="label-text font-medium">Reduced Motion</span>
@ -354,27 +366,11 @@ export default function DisplaySettings() {
</div> </div>
</div> </div>
{/* Preview */} <p className="text-sm text-info">
<div className="form-control"> These settings are saved to your browser and your IEEE UCSD account. They will be applied whenever you log in.
<label className="label"> </p>
<span className="label-text font-medium">Preview</span>
</label>
<div className="p-4 bg-base-200 rounded-lg">
<div className="card bg-base-100 shadow-md">
<div className="card-body">
<h3 className="card-title">Theme Preview</h3>
<p>This is how your content will look with the selected settings.</p>
<div className="flex gap-2 mt-2">
<button className="btn btn-primary">Primary</button>
<button className="btn btn-secondary">Secondary</button>
<button className="btn btn-accent">Accent</button>
</div>
</div>
</div>
</div>
</div>
<div className="form-control mt-6"> <div className="form-control">
<button <button
type="submit" type="submit"
className={`btn btn-primary ${saving ? 'loading' : ''}`} className={`btn btn-primary ${saving ? 'loading' : ''}`}

View file

@ -2,24 +2,26 @@ import { useState, useEffect } from 'react';
import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { Update } from '../../../scripts/pocketbase/Update'; import { Update } from '../../../scripts/pocketbase/Update';
import { Collections } from '../../../schemas/pocketbase/schema'; import { Collections } from '../../../schemas/pocketbase/schema';
import { toast } from 'react-hot-toast';
// Default notification preferences
const DEFAULT_NOTIFICATION_PREFERENCES = {
emailNotifications: true,
eventReminders: true,
eventUpdates: true,
reimbursementUpdates: true,
officerAnnouncements: true,
marketingEmails: false
};
export default function NotificationSettings() { export default function NotificationSettings() {
const auth = Authentication.getInstance(); const auth = Authentication.getInstance();
const update = Update.getInstance(); const update = Update.getInstance();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [successMessage, setSuccessMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
// Notification preferences // Notification preferences
const [preferences, setPreferences] = useState({ const [preferences, setPreferences] = useState(DEFAULT_NOTIFICATION_PREFERENCES);
emailNotifications: true,
eventReminders: true,
eventUpdates: true,
reimbursementUpdates: true,
officerAnnouncements: true,
marketingEmails: false
});
useEffect(() => { useEffect(() => {
const loadPreferences = async () => { const loadPreferences = async () => {
@ -28,7 +30,7 @@ export default function NotificationSettings() {
if (user) { if (user) {
// If user has notification_preferences, parse and use them // If user has notification_preferences, parse and use them
// Otherwise use defaults // Otherwise use defaults
if (user.notification_preferences) { if (user.notification_preferences && typeof user.notification_preferences === 'string' && user.notification_preferences.trim() !== '') {
try { try {
const savedPrefs = JSON.parse(user.notification_preferences); const savedPrefs = JSON.parse(user.notification_preferences);
setPreferences(prev => ({ setPreferences(prev => ({
@ -37,12 +39,17 @@ export default function NotificationSettings() {
})); }));
} catch (e) { } catch (e) {
console.error('Error parsing notification preferences:', e); console.error('Error parsing notification preferences:', e);
// Initialize with defaults and save to user profile
await initializeDefaultPreferences(user.id);
} }
} else {
// Initialize with defaults and save to user profile
await initializeDefaultPreferences(user.id);
} }
} }
} catch (error) { } catch (error) {
console.error('Error loading notification preferences:', error); console.error('Error loading notification preferences:', error);
setErrorMessage('Failed to load notification preferences'); toast.error('Failed to load notification preferences');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -51,6 +58,21 @@ export default function NotificationSettings() {
loadPreferences(); loadPreferences();
}, []); }, []);
// Initialize default preferences if not set
const initializeDefaultPreferences = async (userId: string) => {
try {
await update.updateFields(
Collections.USERS,
userId,
{ notification_preferences: JSON.stringify(DEFAULT_NOTIFICATION_PREFERENCES) }
);
setPreferences(DEFAULT_NOTIFICATION_PREFERENCES);
console.log('Initialized default notification preferences');
} catch (error) {
console.error('Error initializing default notification preferences:', error);
}
};
const handleToggleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleToggleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, checked } = e.target; const { name, checked } = e.target;
setPreferences(prev => ({ setPreferences(prev => ({
@ -62,8 +84,6 @@ export default function NotificationSettings() {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setSaving(true); setSaving(true);
setSuccessMessage('');
setErrorMessage('');
try { try {
const user = auth.getCurrentUser(); const user = auth.getCurrentUser();
@ -76,15 +96,10 @@ export default function NotificationSettings() {
{ notification_preferences: JSON.stringify(preferences) } { notification_preferences: JSON.stringify(preferences) }
); );
setSuccessMessage('Notification preferences saved successfully!'); toast.success('Notification preferences saved successfully!');
// Clear success message after 3 seconds
setTimeout(() => {
setSuccessMessage('');
}, 3000);
} catch (error) { } catch (error) {
console.error('Error saving notification preferences:', error); console.error('Error saving notification preferences:', error);
setErrorMessage('Failed to save notification preferences'); toast.error('Failed to save notification preferences');
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -100,22 +115,6 @@ export default function NotificationSettings() {
return ( return (
<div> <div>
{successMessage && (
<div className="alert alert-success mb-4">
<div>
<span>{successMessage}</span>
</div>
</div>
)}
{errorMessage && (
<div className="alert alert-error mb-4">
<div>
<span>{errorMessage}</span>
</div>
</div>
)}
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="space-y-4"> <div className="space-y-4">
<div className="form-control"> <div className="form-control">
@ -215,13 +214,9 @@ export default function NotificationSettings() {
</div> </div>
</div> </div>
<div className="alert alert-info mt-6 mb-6"> <p className="text-sm text-info mt-6 mb-6">
<div> Note: Some critical notifications about your account cannot be disabled.
<span> </p>
Note: Some critical notifications about your account cannot be disabled.
</span>
</div>
</div>
<div className="form-control"> <div className="form-control">
<button <button

View file

@ -3,6 +3,7 @@ import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { Update } from '../../../scripts/pocketbase/Update'; import { Update } from '../../../scripts/pocketbase/Update';
import { Collections, type User } from '../../../schemas/pocketbase/schema'; import { Collections, type User } from '../../../schemas/pocketbase/schema';
import allMajors from '../../../data/allUCSDMajors.txt?raw'; import allMajors from '../../../data/allUCSDMajors.txt?raw';
import { toast } from 'react-hot-toast';
export default function UserProfileSettings() { export default function UserProfileSettings() {
const auth = Authentication.getInstance(); const auth = Authentication.getInstance();
@ -15,13 +16,16 @@ export default function UserProfileSettings() {
email: '', email: '',
major: '', major: '',
graduation_year: '', graduation_year: '',
zelle_information: '' zelle_information: '',
pid: '',
member_id: ''
}); });
const [successMessage, setSuccessMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
// Parse the majors list from the text file // Parse the majors list from the text file and sort alphabetically
const majorsList = allMajors.split('\n').filter(major => major.trim() !== ''); const majorsList = allMajors
.split('\n')
.filter(major => major.trim() !== '')
.sort((a, b) => a.localeCompare(b));
useEffect(() => { useEffect(() => {
const loadUserData = async () => { const loadUserData = async () => {
@ -34,12 +38,14 @@ export default function UserProfileSettings() {
email: currentUser.email || '', email: currentUser.email || '',
major: currentUser.major || '', major: currentUser.major || '',
graduation_year: currentUser.graduation_year?.toString() || '', graduation_year: currentUser.graduation_year?.toString() || '',
zelle_information: currentUser.zelle_information || '' zelle_information: currentUser.zelle_information || '',
pid: currentUser.pid || '',
member_id: currentUser.member_id || ''
}); });
} }
} catch (error) { } catch (error) {
console.error('Error loading user data:', error); console.error('Error loading user data:', error);
setErrorMessage('Failed to load user data. Please try again later.'); toast.error('Failed to load user data. Please try again later.');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -59,8 +65,6 @@ export default function UserProfileSettings() {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setSaving(true); setSaving(true);
setSuccessMessage('');
setErrorMessage('');
try { try {
if (!user) throw new Error('User not authenticated'); if (!user) throw new Error('User not authenticated');
@ -68,7 +72,9 @@ export default function UserProfileSettings() {
const updateData: Partial<User> = { const updateData: Partial<User> = {
name: formData.name, name: formData.name,
major: formData.major || undefined, major: formData.major || undefined,
zelle_information: formData.zelle_information || undefined zelle_information: formData.zelle_information || undefined,
pid: formData.pid || undefined,
member_id: formData.member_id || undefined
}; };
// Only include graduation_year if it's a valid number // Only include graduation_year if it's a valid number
@ -81,15 +87,10 @@ export default function UserProfileSettings() {
// Update local user state // Update local user state
setUser(prev => prev ? { ...prev, ...updateData } : null); setUser(prev => prev ? { ...prev, ...updateData } : null);
setSuccessMessage('Profile updated successfully!'); toast.success('Profile updated successfully!');
// Clear success message after 3 seconds
setTimeout(() => {
setSuccessMessage('');
}, 3000);
} catch (error) { } catch (error) {
console.error('Error updating profile:', error); console.error('Error updating profile:', error);
setErrorMessage('Failed to update profile. Please try again.'); toast.error('Failed to update profile. Please try again.');
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -115,22 +116,6 @@ export default function UserProfileSettings() {
return ( return (
<div> <div>
{successMessage && (
<div className="alert alert-success mb-4">
<div>
<span>{successMessage}</span>
</div>
</div>
)}
{errorMessage && (
<div className="alert alert-error mb-4">
<div>
<span>{errorMessage}</span>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">
@ -163,6 +148,40 @@ export default function UserProfileSettings() {
</label> </label>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="form-control">
<label className="label">
<span className="label-text">PID</span>
<span className="label-text-alt text-info">UCSD Student ID</span>
</label>
<input
type="text"
name="pid"
value={formData.pid}
onChange={handleInputChange}
className="input input-bordered w-full"
placeholder="A12345678"
pattern="[A-Za-z][0-9]{8}"
title="PID format: A12345678"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">IEEE Member ID</span>
<span className="label-text-alt text-info">Optional</span>
</label>
<input
type="text"
name="member_id"
value={formData.member_id}
onChange={handleInputChange}
className="input input-bordered w-full"
placeholder="IEEE Membership Number"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="form-control"> <div className="form-control">
<label className="label"> <label className="label">