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 NotificationSettings from "./SettingsSection/NotificationSettings";
import DisplaySettings from "./SettingsSection/DisplaySettings";
import { Toaster } from "react-hot-toast";
---
<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>
</div>
<Toaster position="top-right" client:load />
<!-- Profile Settings Card -->
<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"

View file

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

View file

@ -2,16 +2,27 @@ import { useState, useEffect } from 'react';
import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { Update } from '../../../scripts/pocketbase/Update';
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() {
const auth = Authentication.getInstance();
const update = Update.getInstance();
const [theme, setTheme] = useState('dark');
const [fontSize, setFontSize] = useState('medium');
const [colorBlindMode, setColorBlindMode] = useState(false);
const [reducedMotion, setReducedMotion] = useState(false);
const [successMessage, setSuccessMessage] = useState('');
const [errorMessage, setErrorMessage] = useState('');
const [theme, setTheme] = useState(DEFAULT_DISPLAY_PREFERENCES.theme);
const [fontSize, setFontSize] = useState(DEFAULT_DISPLAY_PREFERENCES.fontSize);
const [colorBlindMode, setColorBlindMode] = useState(DEFAULT_ACCESSIBILITY_SETTINGS.colorBlindMode);
const [reducedMotion, setReducedMotion] = useState(DEFAULT_ACCESSIBILITY_SETTINGS.reducedMotion);
const [saving, setSaving] = useState(false);
// Load saved preferences on component mount
@ -19,18 +30,20 @@ export default function DisplaySettings() {
const loadPreferences = async () => {
try {
// First check localStorage for immediate UI updates
const savedTheme = localStorage.getItem('theme') || 'dark';
const savedFontSize = localStorage.getItem('fontSize') || 'medium';
const savedTheme = localStorage.getItem('theme') || DEFAULT_DISPLAY_PREFERENCES.theme;
// 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 savedReducedMotion = localStorage.getItem('reducedMotion') === 'true';
setTheme(savedTheme);
setTheme(validTheme);
setFontSize(savedFontSize);
setColorBlindMode(savedColorBlindMode);
setReducedMotion(savedReducedMotion);
// Apply theme to document
document.documentElement.setAttribute('data-theme', savedTheme);
document.documentElement.setAttribute('data-theme', validTheme);
// Apply font size
applyFontSize(savedFontSize);
@ -46,67 +59,116 @@ export default function DisplaySettings() {
// Then check if user has saved preferences in their profile
const user = auth.getCurrentUser();
if (user && user.display_preferences) {
try {
const userPrefs = JSON.parse(user.display_preferences);
if (user) {
let needsDisplayPrefsUpdate = false;
let needsAccessibilityUpdate = false;
// Only update if values exist and are different from localStorage
if (userPrefs.theme && userPrefs.theme !== savedTheme) {
setTheme(userPrefs.theme);
localStorage.setItem('theme', userPrefs.theme);
document.documentElement.setAttribute('data-theme', userPrefs.theme);
}
// Check and handle display preferences
if (user.display_preferences && typeof user.display_preferences === 'string' && user.display_preferences.trim() !== '') {
try {
const userPrefs = JSON.parse(user.display_preferences);
if (userPrefs.fontSize && userPrefs.fontSize !== savedFontSize) {
setFontSize(userPrefs.fontSize);
localStorage.setItem('fontSize', userPrefs.fontSize);
applyFontSize(userPrefs.fontSize);
// Only update if values exist and are different from localStorage
if (userPrefs.theme && ['light', 'dark'].includes(userPrefs.theme) && userPrefs.theme !== validTheme) {
setTheme(userPrefs.theme);
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) {
console.error('Error parsing display preferences:', e);
} else {
needsDisplayPrefsUpdate = true;
}
}
if (user && user.accessibility_settings) {
try {
const accessibilityPrefs = JSON.parse(user.accessibility_settings);
// Check and handle accessibility settings
if (user.accessibility_settings && typeof user.accessibility_settings === 'string' && user.accessibility_settings.trim() !== '') {
try {
const accessibilityPrefs = JSON.parse(user.accessibility_settings);
if (typeof accessibilityPrefs.colorBlindMode === 'boolean' &&
accessibilityPrefs.colorBlindMode !== savedColorBlindMode) {
setColorBlindMode(accessibilityPrefs.colorBlindMode);
localStorage.setItem('colorBlindMode', accessibilityPrefs.colorBlindMode.toString());
if (typeof accessibilityPrefs.colorBlindMode === 'boolean' &&
accessibilityPrefs.colorBlindMode !== savedColorBlindMode) {
setColorBlindMode(accessibilityPrefs.colorBlindMode);
localStorage.setItem('colorBlindMode', accessibilityPrefs.colorBlindMode.toString());
if (accessibilityPrefs.colorBlindMode) {
document.documentElement.classList.add('color-blind-mode');
} else {
document.documentElement.classList.remove('color-blind-mode');
if (accessibilityPrefs.colorBlindMode) {
document.documentElement.classList.add('color-blind-mode');
} else {
document.documentElement.classList.remove('color-blind-mode');
}
}
}
if (typeof accessibilityPrefs.reducedMotion === 'boolean' &&
accessibilityPrefs.reducedMotion !== savedReducedMotion) {
setReducedMotion(accessibilityPrefs.reducedMotion);
localStorage.setItem('reducedMotion', accessibilityPrefs.reducedMotion.toString());
if (typeof accessibilityPrefs.reducedMotion === 'boolean' &&
accessibilityPrefs.reducedMotion !== savedReducedMotion) {
setReducedMotion(accessibilityPrefs.reducedMotion);
localStorage.setItem('reducedMotion', accessibilityPrefs.reducedMotion.toString());
if (accessibilityPrefs.reducedMotion) {
document.documentElement.classList.add('reduced-motion');
} else {
document.documentElement.classList.remove('reduced-motion');
if (accessibilityPrefs.reducedMotion) {
document.documentElement.classList.add('reduced-motion');
} else {
document.documentElement.classList.remove('reduced-motion');
}
}
} catch (e) {
console.error('Error parsing accessibility settings:', e);
needsAccessibilityUpdate = true;
}
} catch (e) {
console.error('Error parsing accessibility settings:', e);
} else {
needsAccessibilityUpdate = true;
}
// Initialize default settings if needed
if (needsDisplayPrefsUpdate || needsAccessibilityUpdate) {
await initializeDefaultSettings(user.id, needsDisplayPrefsUpdate, needsAccessibilityUpdate);
}
}
} catch (error) {
console.error('Error loading preferences:', error);
setErrorMessage('Failed to load display preferences');
toast.error('Failed to load display preferences');
}
};
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
const applyFontSize = (size: string) => {
const htmlElement = document.documentElement;
@ -191,8 +253,6 @@ export default function DisplaySettings() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
setSuccessMessage('');
setErrorMessage('');
try {
const user = auth.getCurrentUser();
@ -221,15 +281,10 @@ export default function DisplaySettings() {
);
// Show success message
setSuccessMessage('Display settings saved successfully!');
// Clear success message after 3 seconds
setTimeout(() => {
setSuccessMessage('');
}, 3000);
toast.success('Display settings saved successfully!');
} catch (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 {
setSaving(false);
}
@ -237,114 +292,71 @@ export default function DisplaySettings() {
return (
<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">
{/* Theme Selection */}
<div className="form-control">
<label className="label">
<span className="label-text font-medium">Theme</span>
</label>
<select
className="select select-bordered w-full"
value={theme}
onChange={handleThemeChange}
>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="cupcake">Cupcake</option>
<option value="bumblebee">Bumblebee</option>
<option value="emerald">Emerald</option>
<option value="corporate">Corporate</option>
<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>
{/* Theme Settings */}
<div>
<h4 className="font-semibold text-lg mb-2">Theme</h4>
<div className="form-control w-full max-w-xs">
<select
value={theme}
onChange={handleThemeChange}
className="select select-bordered"
>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
<label className="label">
<span className="label-text-alt">Select your preferred theme</span>
</label>
</div>
</div>
{/* Font Size */}
<div className="form-control">
<label className="label">
<span className="label-text font-medium">Font Size</span>
</label>
<select
className="select select-bordered w-full"
value={fontSize}
onChange={handleFontSizeChange}
>
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
<option value="extra-large">Extra Large</option>
</select>
<label className="label">
<span className="label-text-alt">Adjust the text size for better readability</span>
</label>
{/* Font Size Settings */}
<div>
<h4 className="font-semibold text-lg mb-2">Font Size</h4>
<div className="form-control w-full max-w-xs">
<select
value={fontSize}
onChange={handleFontSizeChange}
className="select select-bordered"
>
<option value="small">Small</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
<option value="extra-large">Extra Large</option>
</select>
<label className="label">
<span className="label-text-alt">Select your preferred font size</span>
</label>
</div>
</div>
{/* Accessibility Options */}
<div className="form-control">
<label className="label">
<span className="label-text font-medium">Accessibility Options</span>
</label>
{/* Accessibility Settings */}
<div>
<h4 className="font-semibold text-lg mb-2">Accessibility</h4>
<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">
<input
type="checkbox"
className="toggle toggle-primary"
checked={colorBlindMode}
onChange={handleColorBlindModeChange}
className="toggle toggle-primary"
/>
<div>
<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>
</label>
</div>
<div className="form-control mt-2">
<label className="cursor-pointer label justify-start gap-4">
<input
type="checkbox"
className="toggle toggle-primary"
checked={reducedMotion}
onChange={handleReducedMotionChange}
className="toggle toggle-primary"
/>
<div>
<span className="label-text font-medium">Reduced Motion</span>
@ -354,27 +366,11 @@ export default function DisplaySettings() {
</div>
</div>
{/* Preview */}
<div className="form-control">
<label className="label">
<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>
<p className="text-sm text-info">
These settings are saved to your browser and your IEEE UCSD account. They will be applied whenever you log in.
</p>
<div className="form-control mt-6">
<div className="form-control">
<button
type="submit"
className={`btn btn-primary ${saving ? 'loading' : ''}`}

View file

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

View file

@ -3,6 +3,7 @@ import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { Update } from '../../../scripts/pocketbase/Update';
import { Collections, type User } from '../../../schemas/pocketbase/schema';
import allMajors from '../../../data/allUCSDMajors.txt?raw';
import { toast } from 'react-hot-toast';
export default function UserProfileSettings() {
const auth = Authentication.getInstance();
@ -15,13 +16,16 @@ export default function UserProfileSettings() {
email: '',
major: '',
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
const majorsList = allMajors.split('\n').filter(major => major.trim() !== '');
// Parse the majors list from the text file and sort alphabetically
const majorsList = allMajors
.split('\n')
.filter(major => major.trim() !== '')
.sort((a, b) => a.localeCompare(b));
useEffect(() => {
const loadUserData = async () => {
@ -34,12 +38,14 @@ export default function UserProfileSettings() {
email: currentUser.email || '',
major: currentUser.major || '',
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) {
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 {
setLoading(false);
}
@ -59,8 +65,6 @@ export default function UserProfileSettings() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSaving(true);
setSuccessMessage('');
setErrorMessage('');
try {
if (!user) throw new Error('User not authenticated');
@ -68,7 +72,9 @@ export default function UserProfileSettings() {
const updateData: Partial<User> = {
name: formData.name,
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
@ -81,15 +87,10 @@ export default function UserProfileSettings() {
// Update local user state
setUser(prev => prev ? { ...prev, ...updateData } : null);
setSuccessMessage('Profile updated successfully!');
// Clear success message after 3 seconds
setTimeout(() => {
setSuccessMessage('');
}, 3000);
toast.success('Profile updated successfully!');
} catch (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 {
setSaving(false);
}
@ -115,22 +116,6 @@ export default function UserProfileSettings() {
return (
<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">
<div className="form-control">
<label className="label">
@ -163,6 +148,40 @@ export default function UserProfileSettings() {
</label>
</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="form-control">
<label className="label">