👤 Profile Page Features: - Complete user profile with avatar, name, username, bio, and email - Avatar upload with file validation (5MB limit, image types only) - Avatar generation functionality for custom avatars - Favorite games selection (MTG, Pokemon, Lorcana) - Collection statistics display (total cards, collections, decks, value) - Profile editing with real-time validation - Member since date and role display ⚙️ Settings Page Features: - Multi-section tabbed interface (Account, Security, Preferences, Notifications, Display) - Account settings: email (read-only), collection visibility, preferred currency - Security settings: password change with validation, 2FA toggle, account deletion - Preferences: cards per page (25/50/100), default view (grid/list) - Notifications: email notifications, marketing emails (toggle switches) - Display settings: theme (light/dark/system), language selection 🗄️ Database Schema Updates: - Added user profile fields: first_name, last_name, username, bio, avatar_url - Added preference fields: favorite_games (JSONB), collection_visibility, preferred_currency, cards_per_page, default_view - Added notification settings: notifications_email, notifications_marketing, two_factor_enabled - Added display settings: theme, language - Created user_settings table for complex settings - Created user_avatars table for avatar management - Added performance indexes and data validation constraints 📡 API Endpoints Created: - GET/PUT /api/user/profile - Profile information management - GET/PUT /api/user/settings - Settings and preferences management - PUT /api/user/password - Secure password change with bcrypt validation - GET /api/user/stats - Collection statistics and analytics 🔒 Security & Validation: - Password change requires current password verification - Username uniqueness validation - Input validation for all enum fields (currency, theme, view mode, etc.) - Proper error handling and user feedback - Authentication required for all user endpoints 🎨 UI/UX Features: - Beautiful fire-themed design matching app branding - Responsive design for mobile and desktop - Loading states and success/error messages - Avatar placeholder with user initials - Tabbed settings interface with icons - Toggle switches for boolean settings - Form validation with helpful error messages ✨ Additional Features: - Collection stats with game/rarity breakdowns - Recent activity tracking - Danger zone for account deletion with double confirmation - Member since display with formatted dates - Currency formatting for collection values - Game icons and themed styling throughout The profile and settings system is now fully functional with comprehensive user management! 👨💻✨
247 lines
No EOL
8.1 KiB
JavaScript
247 lines
No EOL
8.1 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Add user profile fields to support enhanced profile and settings
|
|
*
|
|
* This script adds new columns to the users table for:
|
|
* - Profile information (first_name, last_name, username, bio, avatar_url)
|
|
* - Preferences (favorite_games, collection_visibility, preferred_currency, etc.)
|
|
* - Settings (notifications, display preferences)
|
|
*/
|
|
|
|
import dotenv from 'dotenv';
|
|
import { neon } from '@neondatabase/serverless';
|
|
|
|
// Load environment variables from .env.local
|
|
dotenv.config({ path: '.env.local' });
|
|
|
|
async function addUserProfileFields() {
|
|
const sql = neon(process.env.POSTGRES_URL);
|
|
|
|
try {
|
|
console.log('✅ Connecting to Neon database...');
|
|
|
|
// Add basic profile fields
|
|
console.log('👤 Adding basic profile fields...');
|
|
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS first_name VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS last_name VARCHAR(255),
|
|
ADD COLUMN IF NOT EXISTS username VARCHAR(255) UNIQUE,
|
|
ADD COLUMN IF NOT EXISTS bio TEXT,
|
|
ADD COLUMN IF NOT EXISTS avatar_url TEXT
|
|
`;
|
|
console.log('✅ Added basic profile fields');
|
|
|
|
// Add preference fields
|
|
console.log('⚙️ Adding preference fields...');
|
|
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS favorite_games JSONB DEFAULT '["MTG"]',
|
|
ADD COLUMN IF NOT EXISTS collection_visibility VARCHAR(20) DEFAULT 'private',
|
|
ADD COLUMN IF NOT EXISTS preferred_currency VARCHAR(3) DEFAULT 'USD',
|
|
ADD COLUMN IF NOT EXISTS cards_per_page INTEGER DEFAULT 50,
|
|
ADD COLUMN IF NOT EXISTS default_view VARCHAR(10) DEFAULT 'grid'
|
|
`;
|
|
console.log('✅ Added preference fields');
|
|
|
|
// Add notification settings
|
|
console.log('🔔 Adding notification settings...');
|
|
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS notifications_email BOOLEAN DEFAULT true,
|
|
ADD COLUMN IF NOT EXISTS notifications_marketing BOOLEAN DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS two_factor_enabled BOOLEAN DEFAULT false
|
|
`;
|
|
console.log('✅ Added notification settings');
|
|
|
|
// Add display settings
|
|
console.log('🎨 Adding display settings...');
|
|
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS theme VARCHAR(10) DEFAULT 'system',
|
|
ADD COLUMN IF NOT EXISTS language VARCHAR(5) DEFAULT 'en'
|
|
`;
|
|
console.log('✅ Added display settings');
|
|
|
|
// Create user_settings table for more complex settings
|
|
console.log('📊 Creating user_settings table...');
|
|
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS user_settings (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
|
setting_key VARCHAR(100) NOT NULL,
|
|
setting_value JSONB NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(user_id, setting_key)
|
|
)
|
|
`;
|
|
console.log('✅ Created user_settings table');
|
|
|
|
// Create user_avatars table for avatar management
|
|
console.log('🖼️ Creating user_avatars table...');
|
|
|
|
await sql`
|
|
CREATE TABLE IF NOT EXISTS user_avatars (
|
|
id SERIAL PRIMARY KEY,
|
|
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
|
filename VARCHAR(255) NOT NULL,
|
|
original_name VARCHAR(255),
|
|
mime_type VARCHAR(100),
|
|
file_size INTEGER,
|
|
file_path TEXT NOT NULL,
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`;
|
|
console.log('✅ Created user_avatars table');
|
|
|
|
// Add indexes for performance
|
|
console.log('🚀 Adding indexes for performance...');
|
|
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_user_settings_user_id ON user_settings(user_id)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_user_settings_key ON user_settings(setting_key)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_user_avatars_user_id ON user_avatars(user_id)`;
|
|
await sql`CREATE INDEX IF NOT EXISTS idx_user_avatars_active ON user_avatars(user_id, is_active)`;
|
|
|
|
console.log('✅ Added performance indexes');
|
|
|
|
// Add constraints and validation (skip if they already exist)
|
|
console.log('🔒 Adding constraints and validation...');
|
|
|
|
try {
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD CONSTRAINT check_collection_visibility
|
|
CHECK (collection_visibility IN ('private', 'public', 'unlisted'))
|
|
`;
|
|
} catch (error) {
|
|
if (!error.message.includes('already exists')) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD CONSTRAINT check_preferred_currency
|
|
CHECK (preferred_currency IN ('USD', 'EUR', 'GBP', 'CAD', 'JPY'))
|
|
`;
|
|
} catch (error) {
|
|
if (!error.message.includes('already exists')) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD CONSTRAINT check_cards_per_page
|
|
CHECK (cards_per_page IN (25, 50, 100))
|
|
`;
|
|
} catch (error) {
|
|
if (!error.message.includes('already exists')) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD CONSTRAINT check_default_view
|
|
CHECK (default_view IN ('grid', 'list'))
|
|
`;
|
|
} catch (error) {
|
|
if (!error.message.includes('already exists')) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD CONSTRAINT check_theme
|
|
CHECK (theme IN ('light', 'dark', 'system'))
|
|
`;
|
|
} catch (error) {
|
|
if (!error.message.includes('already exists')) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
try {
|
|
await sql`
|
|
ALTER TABLE users
|
|
ADD CONSTRAINT check_language
|
|
CHECK (language IN ('en', 'es', 'fr', 'de', 'ja'))
|
|
`;
|
|
} catch (error) {
|
|
if (!error.message.includes('already exists')) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
console.log('✅ Added constraints and validation');
|
|
|
|
// Update existing users with default values
|
|
console.log('🔄 Updating existing users with default values...');
|
|
|
|
await sql`
|
|
UPDATE users
|
|
SET
|
|
favorite_games = '["MTG"]'::jsonb,
|
|
collection_visibility = 'private',
|
|
preferred_currency = 'USD',
|
|
cards_per_page = 50,
|
|
default_view = 'grid',
|
|
notifications_email = true,
|
|
notifications_marketing = false,
|
|
two_factor_enabled = false,
|
|
theme = 'system',
|
|
language = 'en'
|
|
WHERE
|
|
favorite_games IS NULL OR
|
|
collection_visibility IS NULL OR
|
|
preferred_currency IS NULL OR
|
|
cards_per_page IS NULL OR
|
|
default_view IS NULL OR
|
|
notifications_email IS NULL OR
|
|
notifications_marketing IS NULL OR
|
|
two_factor_enabled IS NULL OR
|
|
theme IS NULL OR
|
|
language IS NULL
|
|
`;
|
|
console.log('✅ Updated existing users with defaults');
|
|
|
|
console.log('\n🎉 User profile fields added successfully!');
|
|
console.log('\n📋 Summary of changes:');
|
|
console.log(' 📝 Basic Profile: first_name, last_name, username, bio, avatar_url');
|
|
console.log(' ⚙️ Preferences: favorite_games, collection_visibility, preferred_currency, cards_per_page, default_view');
|
|
console.log(' 🔔 Notifications: notifications_email, notifications_marketing, two_factor_enabled');
|
|
console.log(' 🎨 Display: theme, language');
|
|
console.log(' 📊 New Tables: user_settings, user_avatars');
|
|
console.log(' 🚀 Performance: Added indexes for fast queries');
|
|
console.log(' 🔒 Validation: Added constraints for data integrity');
|
|
|
|
} catch (error) {
|
|
console.error('❌ Failed to add user profile fields:', error.message);
|
|
console.error('Full error:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run the migration if this script is executed directly
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
addUserProfileFields();
|
|
}
|
|
|
|
export { addUserProfileFields };
|