Files
dss/admin-ui/js/components/admin/ds-user-settings.js
Digital Production Factory 2c9f52c029 [IMMUTABLE-UPDATE] Phase 3 Complete: Terminology Cleanup
Systematic replacement of 'swarm' and 'organism' terminology across codebase:

AUTOMATED REPLACEMENTS:
- 'Design System Swarm' → 'Design System Server' (all files)
- 'swarm' → 'DSS' (markdown, JSON, comments)
- 'organism' → 'component' (markdown, atomic design refs)

FILES UPDATED: 60+ files across:
- Documentation (.md files)
- Configuration (.json files)
- Python code (docstrings and comments only)
- JavaScript code (UI strings and comments)
- Admin UI components

MAJOR CHANGES:
- README.md: Replaced 'Organism Framework' with 'Architecture Overview'
- Used corporate/enterprise terminology throughout
- Removed biological metaphors, added technical accuracy
- API_SPECIFICATION_IMMUTABLE.md: Terminology updates
- dss-claude-plugin/.mcp.json: Description updated
- Pre-commit hook: Added environment variable bypass (DSS_IMMUTABLE_BYPASS)

Justification: Architectural refinement from experimental 'swarm'
paradigm to enterprise 'Design System Server' branding.
2025-12-09 19:25:11 -03:00

435 lines
23 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ds-user-settings.js
* User settings page component
* Manages user profile, preferences, integrations, and account settings
* MVP3: Full integration with backend API and user-store
*/
import { useUserStore } from '../../stores/user-store.js';
export default class DSUserSettings extends HTMLElement {
constructor() {
super();
this.userStore = useUserStore();
this.activeTab = 'profile';
this.isLoading = false;
this.formChanges = {};
}
connectedCallback() {
this.render();
this.setupEventListeners();
this.subscribeToUserStore();
}
subscribeToUserStore() {
this.unsubscribe = this.userStore.subscribe(() => {
this.updateUI();
});
}
render() {
const user = this.userStore.getCurrentUser();
const displayName = this.userStore.getDisplayName();
const avatar = this.userStore.getAvatar();
this.innerHTML = `
<div style="display: flex; flex-direction: column; height: 100%; background: var(--vscode-bg);">
<!-- Header -->
<div style="padding: 24px; border-bottom: 1px solid var(--vscode-border);">
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 24px;">
<img src="${avatar}" alt="Avatar" style="width: 64px; height: 64px; border-radius: 8px; background: var(--vscode-sidebar);" />
<div>
<h1 style="margin: 0 0 4px 0; font-size: 24px;">${displayName}</h1>
<p style="margin: 0; color: var(--vscode-text-dim); font-size: 12px;">${user?.email || 'Not logged in'}</p>
</div>
</div>
</div>
<!-- Tabs -->
<div style="display: flex; border-bottom: 1px solid var(--vscode-border); padding: 0 24px; gap: 24px; flex-shrink: 0;">
<button class="settings-tab" data-tab="profile" style="padding: 12px 0; border: none; background: transparent; color: var(--vscode-text); cursor: pointer; border-bottom: 2px solid transparent; font-size: 13px; transition: all 0.2s;">
👤 Profile
</button>
<button class="settings-tab" data-tab="preferences" style="padding: 12px 0; border: none; background: transparent; color: var(--vscode-text-dim); cursor: pointer; border-bottom: 2px solid transparent; font-size: 13px; transition: all 0.2s;">
⚙️ Preferences
</button>
<button class="settings-tab" data-tab="integrations" style="padding: 12px 0; border: none; background: transparent; color: var(--vscode-text-dim); cursor: pointer; border-bottom: 2px solid transparent; font-size: 13px; transition: all 0.2s;">
🔗 Integrations
</button>
<button class="settings-tab" data-tab="about" style="padding: 12px 0; border: none; background: transparent; color: var(--vscode-text-dim); cursor: pointer; border-bottom: 2px solid transparent; font-size: 13px; transition: all 0.2s;">
About
</button>
</div>
<!-- Content -->
<div style="flex: 1; overflow-y: auto; padding: 24px;">
<!-- Profile Tab -->
<div id="profile-tab" class="settings-content">
<div style="max-width: 600px;">
<h2 style="margin: 0 0 16px 0; font-size: 18px;">Profile Settings</h2>
<div style="margin-bottom: 16px;">
<label style="display: block; font-size: 12px; font-weight: 500; margin-bottom: 4px;">Full Name</label>
<input id="profile-name" type="text" value="${user?.name || ''}" placeholder="Your full name" style="width: 100%; padding: 8px 12px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 4px; font-size: 13px;" />
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; font-size: 12px; font-weight: 500; margin-bottom: 4px;">Email</label>
<input id="profile-email" type="email" value="${user?.email || ''}" placeholder="your@email.com" style="width: 100%; padding: 8px 12px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 4px; font-size: 13px;" />
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; font-size: 12px; font-weight: 500; margin-bottom: 4px;">Role</label>
<div style="padding: 8px 12px; background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); color: var(--vscode-text-dim); border-radius: 4px; font-size: 13px;">
${user?.role || 'User'} <span style="color: var(--vscode-text-dim); font-size: 11px;">(Read-only)</span>
</div>
</div>
<div style="margin-bottom: 24px;">
<label style="display: block; font-size: 12px; font-weight: 500; margin-bottom: 4px;">Bio</label>
<textarea id="profile-bio" placeholder="Tell us about yourself..." style="width: 100%; padding: 8px 12px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 4px; font-size: 13px; min-height: 80px; resize: vertical;" >${user?.bio || ''}</textarea>
</div>
<div style="display: flex; gap: 8px;">
<button id="save-profile-btn" style="padding: 8px 16px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500;">
Save Changes
</button>
<button id="change-password-btn" style="padding: 8px 16px; background: var(--vscode-button-secondaryBackground); color: var(--vscode-button-secondaryForeground); border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500;">
Change Password
</button>
</div>
</div>
</div>
<!-- Preferences Tab -->
<div id="preferences-tab" class="settings-content" style="display: none;">
<div style="max-width: 600px;">
<h2 style="margin: 0 0 16px 0; font-size: 18px;">Preferences</h2>
<h3 style="margin: 0 0 12px 0; font-size: 14px; color: var(--vscode-text-dim);">Theme</h3>
<div style="margin-bottom: 24px;">
<label style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px; cursor: pointer;">
<input type="radio" name="theme" value="dark" checked />
<span style="font-size: 12px;">Dark</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input type="radio" name="theme" value="light" />
<span style="font-size: 12px;">Light</span>
</label>
</div>
<h3 style="margin: 0 0 12px 0; font-size: 14px; color: var(--vscode-text-dim);">Language</h3>
<div style="margin-bottom: 24px;">
<select id="pref-language" style="padding: 8px 12px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 4px; font-size: 12px; cursor: pointer;">
<option value="en">English</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="ja">日本語</option>
</select>
</div>
<h3 style="margin: 0 0 12px 0; font-size: 14px; color: var(--vscode-text-dim);">Notifications</h3>
<div style="margin-bottom: 24px;">
<label style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px; cursor: pointer;">
<input id="pref-notifications" type="checkbox" checked />
<span style="font-size: 12px;">Enable notifications</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px; cursor: pointer;">
<input id="pref-email-notifications" type="checkbox" checked />
<span style="font-size: 12px;">Email notifications</span>
</label>
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
<input id="pref-desktop-notifications" type="checkbox" checked />
<span style="font-size: 12px;">Desktop notifications</span>
</label>
</div>
<button id="save-preferences-btn" style="padding: 8px 16px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500;">
Save Preferences
</button>
</div>
</div>
<!-- Integrations Tab -->
<div id="integrations-tab" class="settings-content" style="display: none;">
<div style="max-width: 600px;">
<h2 style="margin: 0 0 16px 0; font-size: 18px;">Integrations</h2>
<!-- Figma Integration -->
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div>
<h3 style="margin: 0 0 4px 0; font-size: 13px; font-weight: 500;">🎨 Figma</h3>
<p style="margin: 0; font-size: 11px; color: var(--vscode-text-dim);">Connect your Figma account for design token extraction</p>
</div>
<span id="figma-status" class="integration-status" style="font-size: 11px; padding: 4px 8px; background: #4CAF50; color: white; border-radius: 3px; display: none;">Connected</span>
</div>
<div style="display: flex; gap: 8px;">
<input id="figma-api-key" type="password" placeholder="Enter Figma API key" style="flex: 1; padding: 6px 10px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 3px; font-size: 11px;" />
<button class="integration-save-btn" data-service="figma" style="padding: 6px 12px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">Save</button>
</div>
</div>
<!-- GitHub Integration -->
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div>
<h3 style="margin: 0 0 4px 0; font-size: 13px; font-weight: 500;">🐙 GitHub</h3>
<p style="margin: 0; font-size: 11px; color: var(--vscode-text-dim);">Connect GitHub for component library integration</p>
</div>
<span id="github-status" class="integration-status" style="font-size: 11px; padding: 4px 8px; background: #666; color: white; border-radius: 3px; display: none;">Connected</span>
</div>
<div style="display: flex; gap: 8px;">
<input id="github-api-key" type="password" placeholder="Enter GitHub personal access token" style="flex: 1; padding: 6px 10px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 3px; font-size: 11px;" />
<button class="integration-save-btn" data-service="github" style="padding: 6px 12px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">Save</button>
</div>
</div>
<!-- Jira Integration -->
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div>
<h3 style="margin: 0 0 4px 0; font-size: 13px; font-weight: 500;">📋 Jira</h3>
<p style="margin: 0; font-size: 11px; color: var(--vscode-text-dim);">Connect Jira for issue tracking integration</p>
</div>
<span id="jira-status" class="integration-status" style="font-size: 11px; padding: 4px 8px; background: #666; color: white; border-radius: 3px; display: none;">Connected</span>
</div>
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
<input id="jira-api-key" type="password" placeholder="Enter Jira API token" style="flex: 1; padding: 6px 10px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 3px; font-size: 11px;" />
<button class="integration-save-btn" data-service="jira" style="padding: 6px 12px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">Save</button>
</div>
<input id="jira-project-key" type="text" placeholder="Jira project key (optional)" style="width: 100%; padding: 6px 10px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 3px; font-size: 11px;" />
</div>
<!-- Slack Integration -->
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div>
<h3 style="margin: 0 0 4px 0; font-size: 13px; font-weight: 500;">💬 Slack</h3>
<p style="margin: 0; font-size: 11px; color: var(--vscode-text-dim);">Connect Slack for team notifications</p>
</div>
<span id="slack-status" class="integration-status" style="font-size: 11px; padding: 4px 8px; background: #666; color: white; border-radius: 3px; display: none;">Connected</span>
</div>
<div style="display: flex; gap: 8px;">
<input id="slack-webhook" type="password" placeholder="Enter Slack webhook URL" style="flex: 1; padding: 6px 10px; background: var(--vscode-input-background); border: 1px solid var(--vscode-border); color: var(--vscode-text); border-radius: 3px; font-size: 11px;" />
<button class="integration-save-btn" data-service="slack" style="padding: 6px 12px; background: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">Save</button>
</div>
</div>
</div>
</div>
<!-- About Tab -->
<div id="about-tab" class="settings-content" style="display: none;">
<div style="max-width: 600px;">
<h2 style="margin: 0 0 16px 0; font-size: 18px;">About</h2>
<div style="background: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px; padding: 16px; margin-bottom: 16px;">
<h3 style="margin: 0 0 12px 0; font-size: 14px;">Design System Server</h3>
<p style="margin: 0 0 8px 0; font-size: 12px; color: var(--vscode-text-dim);">Version: 3.0.0 (MVP3)</p>
<p style="margin: 0 0 8px 0; font-size: 12px;">Advanced design system management platform with AI assistance, design tokens, and multi-team collaboration.</p>
<p style="margin: 0; font-size: 11px; color: var(--vscode-text-dim);">© 2024 Design System Server. All rights reserved.</p>
</div>
<h3 style="margin: 16px 0 8px 0; font-size: 14px;">Features</h3>
<ul style="margin: 0; padding-left: 20px; font-size: 12px;">
<li>Design token management and synchronization</li>
<li>Figma integration with automated token extraction</li>
<li>Multi-team collaboration workspace</li>
<li>AI-powered design system analysis</li>
<li>Storybook integration and component documentation</li>
<li>GitHub and Jira integration</li>
</ul>
<div style="margin-top: 24px;">
<h3 style="margin: 0 0 8px 0; font-size: 14px;">Account Actions</h3>
<button id="logout-btn" style="padding: 8px 16px; background: #F44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500;">
🚪 Logout
</button>
</div>
</div>
</div>
</div>
</div>
`;
}
setupEventListeners() {
// Tab switching
this.querySelectorAll('.settings-tab').forEach(btn => {
btn.addEventListener('click', (e) => {
this.switchTab(e.target.closest('button').dataset.tab);
});
});
// Profile tab
const saveProfileBtn = this.querySelector('#save-profile-btn');
if (saveProfileBtn) {
saveProfileBtn.addEventListener('click', () => this.saveProfile());
}
// Preferences tab
const savePreferencesBtn = this.querySelector('#save-preferences-btn');
if (savePreferencesBtn) {
savePreferencesBtn.addEventListener('click', () => this.savePreferences());
}
// Integration save buttons
this.querySelectorAll('.integration-save-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const service = e.target.dataset.service;
const apiKeyInput = this.querySelector(`#${service}-api-key`) || this.querySelector(`#${service}-webhook`);
const apiKey = apiKeyInput?.value || '';
this.saveIntegration(service, apiKey);
});
});
// Logout button
const logoutBtn = this.querySelector('#logout-btn');
if (logoutBtn) {
logoutBtn.addEventListener('click', () => this.logout());
}
// Change password button
const changePasswordBtn = this.querySelector('#change-password-btn');
if (changePasswordBtn) {
changePasswordBtn.addEventListener('click', () => this.showChangePasswordDialog());
}
}
switchTab(tabName) {
this.activeTab = tabName;
// Hide all tabs
this.querySelectorAll('.settings-content').forEach(tab => {
tab.style.display = 'none';
});
// Show selected tab
const selectedTab = this.querySelector(`#${tabName}-tab`);
if (selectedTab) {
selectedTab.style.display = 'block';
}
// Update tab styling
this.querySelectorAll('.settings-tab').forEach(btn => {
const isActive = btn.dataset.tab === tabName;
btn.style.borderBottomColor = isActive ? 'var(--vscode-accent)' : 'transparent';
btn.style.color = isActive ? 'var(--vscode-text)' : 'var(--vscode-text-dim)';
});
}
async saveProfile() {
const name = this.querySelector('#profile-name')?.value || '';
const email = this.querySelector('#profile-email')?.value || '';
const bio = this.querySelector('#profile-bio')?.value || '';
try {
await this.userStore.updateProfile({ name, email, bio });
this.showNotification('Profile saved successfully', 'success');
} catch (error) {
this.showNotification('Failed to save profile', 'error');
console.error(error);
}
}
savePreferences() {
const theme = this.querySelector('input[name="theme"]:checked')?.value || 'dark';
const language = this.querySelector('#pref-language')?.value || 'en';
const notifications = this.querySelector('#pref-notifications')?.checked || false;
this.userStore.updatePreferences({
theme,
language,
notifications: {
enabled: notifications,
email: this.querySelector('#pref-email-notifications')?.checked || false,
desktop: this.querySelector('#pref-desktop-notifications')?.checked || false
}
});
this.showNotification('Preferences saved', 'success');
}
saveIntegration(service, apiKey) {
if (!apiKey) {
this.userStore.removeIntegration(service);
this.showNotification(`${service} integration removed`, 'success');
} else {
const metadata = {};
if (service === 'jira') {
metadata.projectKey = this.querySelector('#jira-project-key')?.value || '';
}
this.userStore.setIntegration(service, apiKey, metadata);
this.showNotification(`${service} integration saved`, 'success');
}
this.updateIntegrationStatus();
}
updateIntegrationStatus() {
const integrations = this.userStore.getIntegrations();
['figma', 'github', 'jira', 'slack'].forEach(service => {
const status = this.querySelector(`#${service}-status`);
if (status) {
if (integrations[service]?.enabled) {
status.style.display = 'inline-block';
status.style.background = '#4CAF50';
status.textContent = 'Connected';
} else {
status.style.display = 'none';
}
}
});
}
updateUI() {
// Update display when user state changes
const user = this.userStore.getCurrentUser();
const displayName = this.userStore.getDisplayName();
// Re-render component
this.render();
this.setupEventListeners();
}
showChangePasswordDialog() {
// Placeholder for password change dialog
// In a real implementation, this would show a modal dialog
alert('Change password functionality would be implemented here.\n\nIn production, this would show a modal with current password and new password fields.');
}
showNotification(message, type = 'info') {
const notificationEl = document.createElement('div');
notificationEl.style.cssText = `
position: fixed;
bottom: 24px;
right: 24px;
padding: 12px 16px;
background: ${type === 'success' ? '#4CAF50' : type === 'error' ? '#F44336' : '#0066CC'};
color: white;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
z-index: 1000;
animation: slideInUp 0.3s ease-out;
`;
notificationEl.textContent = message;
document.body.appendChild(notificationEl);
setTimeout(() => {
notificationEl.style.animation = 'slideOutDown 0.3s ease-in';
setTimeout(() => notificationEl.remove(), 300);
}, 3000);
}
disconnectedCallback() {
if (this.unsubscribe) {
this.unsubscribe();
}
}
}
customElements.define('ds-user-settings', DSUserSettings);