Files
dss/admin-ui/js/components/tools/ds-figma-status.js
Digital Production Factory 276ed71f31 Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm

Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)

Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability

Migration completed: $(date)
🤖 Clean migration with full functionality preserved
2025-12-09 18:45:48 -03:00

412 lines
15 KiB
JavaScript

/**
* ds-figma-status.js
* Figma integration status and sync controls
*/
import toolBridge from '../../services/tool-bridge.js';
import { ComponentHelpers } from '../../utils/component-helpers.js';
class DSFigmaStatus extends HTMLElement {
constructor() {
super();
this.figmaToken = null;
this.figmaFileKey = null;
this.connectionStatus = 'unknown';
this.lastSync = null;
this.isConfiguring = false;
this.isSyncing = false;
}
async connectedCallback() {
this.render();
this.setupEventListeners();
await this.checkConfiguration();
}
/**
* Check if Figma is configured and test connection
*/
async checkConfiguration() {
const statusContent = this.querySelector('#figma-status-content');
if (!statusContent) return;
try {
// Check for stored file key in localStorage (not token - that's server-side)
this.figmaFileKey = localStorage.getItem('figma_file_key');
if (!this.figmaFileKey) {
this.connectionStatus = 'not_configured';
this.renderStatus();
return;
}
// Test connection by calling sync with dry-run check
// Note: Backend checks for FIGMA_TOKEN env variable
statusContent.innerHTML = ComponentHelpers.renderLoading('Checking Figma connection...');
try {
// Try to get Figma file info (will fail if token not configured)
const result = await toolBridge.syncFigma(this.figmaFileKey);
if (result && result.tokens) {
this.connectionStatus = 'connected';
this.lastSync = new Date();
} else {
this.connectionStatus = 'error';
}
} catch (error) {
// Token not configured on backend
if (error.message.includes('FIGMA_TOKEN')) {
this.connectionStatus = 'token_missing';
} else {
this.connectionStatus = 'error';
}
console.error('Figma connection check failed:', error);
}
this.renderStatus();
} catch (error) {
console.error('Failed to check Figma configuration:', error);
statusContent.innerHTML = ComponentHelpers.renderError('Failed to check configuration', error);
}
}
setupEventListeners() {
// Configure button
const configureBtn = this.querySelector('#figma-configure-btn');
if (configureBtn) {
configureBtn.addEventListener('click', () => this.showConfiguration());
}
// Sync button
const syncBtn = this.querySelector('#figma-sync-btn');
if (syncBtn) {
syncBtn.addEventListener('click', () => this.syncFromFigma());
}
}
showConfiguration() {
this.isConfiguring = true;
this.renderStatus();
// Setup save handler
const saveBtn = this.querySelector('#figma-save-config-btn');
const cancelBtn = this.querySelector('#figma-cancel-config-btn');
if (saveBtn) {
saveBtn.addEventListener('click', () => this.saveConfiguration());
}
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
this.isConfiguring = false;
this.renderStatus();
});
}
}
async saveConfiguration() {
const fileKeyInput = this.querySelector('#figma-file-key-input');
const tokenInput = this.querySelector('#figma-token-input');
if (!fileKeyInput || !tokenInput) return;
const fileKey = fileKeyInput.value.trim();
const token = tokenInput.value.trim();
if (!fileKey) {
ComponentHelpers.showToast?.('Please enter a Figma file key', 'error');
return;
}
if (!token) {
ComponentHelpers.showToast?.('Please enter a Figma access token', 'error');
return;
}
try {
// Store file key in localStorage (client-side)
localStorage.setItem('figma_file_key', fileKey);
this.figmaFileKey = fileKey;
// Display warning about backend token configuration
ComponentHelpers.showToast?.('File key saved. Please configure FIGMA_TOKEN environment variable on the backend.', 'info');
this.isConfiguring = false;
this.connectionStatus = 'token_missing';
this.renderStatus();
} catch (error) {
console.error('Failed to save Figma configuration:', error);
ComponentHelpers.showToast?.(`Failed to save configuration: ${error.message}`, 'error');
}
}
async syncFromFigma() {
if (this.isSyncing || !this.figmaFileKey) return;
this.isSyncing = true;
const syncBtn = this.querySelector('#figma-sync-btn');
if (syncBtn) {
syncBtn.disabled = true;
syncBtn.textContent = '🔄 Syncing...';
}
try {
const result = await toolBridge.syncFigma(this.figmaFileKey);
if (result && result.tokens) {
this.lastSync = new Date();
this.connectionStatus = 'connected';
ComponentHelpers.showToast?.(
`Synced ${Object.keys(result.tokens).length} tokens from Figma`,
'success'
);
this.renderStatus();
} else {
throw new Error('No tokens returned from Figma');
}
} catch (error) {
console.error('Failed to sync from Figma:', error);
ComponentHelpers.showToast?.(`Sync failed: ${error.message}`, 'error');
this.connectionStatus = 'error';
this.renderStatus();
} finally {
this.isSyncing = false;
if (syncBtn) {
syncBtn.disabled = false;
syncBtn.textContent = '🔄 Sync Now';
}
}
}
getStatusBadge() {
const badges = {
connected: ComponentHelpers.createBadge('Connected', 'success'),
not_configured: ComponentHelpers.createBadge('Not Configured', 'info'),
token_missing: ComponentHelpers.createBadge('Token Required', 'warning'),
error: ComponentHelpers.createBadge('Error', 'error'),
unknown: ComponentHelpers.createBadge('Unknown', 'info')
};
return badges[this.connectionStatus] || badges.unknown;
}
renderStatus() {
const statusContent = this.querySelector('#figma-status-content');
if (!statusContent) return;
// Configuration form
if (this.isConfiguring) {
statusContent.innerHTML = `
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
<h4 style="font-size: 12px; font-weight: 600; margin-bottom: 12px;">Configure Figma Integration</h4>
<div style="margin-bottom: 12px;">
<label style="display: block; font-size: 11px; margin-bottom: 4px; color: var(--vscode-text-dim);">
Figma File Key
</label>
<input
type="text"
id="figma-file-key-input"
class="input"
placeholder="e.g., abc123xyz456"
value="${ComponentHelpers.escapeHtml(this.figmaFileKey || '')}"
style="width: 100%; font-size: 11px; font-family: 'Courier New', monospace;"
/>
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 4px;">
Find this in your Figma file URL: figma.com/file/<strong>FILE_KEY</strong>/...
</div>
</div>
<div style="margin-bottom: 16px;">
<label style="display: block; font-size: 11px; margin-bottom: 4px; color: var(--vscode-text-dim);">
Figma Access Token
</label>
<input
type="password"
id="figma-token-input"
class="input"
placeholder="figd_..."
style="width: 100%; font-size: 11px; font-family: 'Courier New', monospace;"
/>
<div style="font-size: 10px; color: var(--vscode-text-dim); margin-top: 4px;">
Generate at: <a href="https://www.figma.com/developers/api#access-tokens" target="_blank" style="color: var(--vscode-accent);">figma.com/developers/api</a>
</div>
</div>
<div style="padding: 12px; background-color: rgba(255, 191, 0, 0.1); border-radius: 4px; margin-bottom: 16px;">
<div style="font-size: 11px; color: #ffbf00;">
⚠️ <strong>Security Note:</strong> The Figma token must be configured as the <code>FIGMA_TOKEN</code> environment variable on the backend server. This UI only stores the file key locally.
</div>
</div>
<div style="display: flex; gap: 8px; justify-content: flex-end;">
<button id="figma-cancel-config-btn" class="button" style="padding: 6px 12px; font-size: 11px;">
Cancel
</button>
<button id="figma-save-config-btn" class="button" style="padding: 6px 12px; font-size: 11px;">
Save Configuration
</button>
</div>
</div>
`;
return;
}
// Not configured state
if (this.connectionStatus === 'not_configured') {
statusContent.innerHTML = `
<div style="text-align: center; padding: 32px;">
<div style="font-size: 48px; margin-bottom: 16px;">🎨</div>
<h3 style="font-size: 14px; font-weight: 600; margin-bottom: 8px;">Figma Not Configured</h3>
<p style="font-size: 12px; color: var(--vscode-text-dim); margin-bottom: 16px;">
Connect your Figma file to sync design tokens automatically.
</p>
<button id="figma-configure-btn" class="button" style="padding: 8px 16px; font-size: 12px;">
Configure Figma
</button>
</div>
`;
const configureBtn = statusContent.querySelector('#figma-configure-btn');
if (configureBtn) {
configureBtn.addEventListener('click', () => this.showConfiguration());
}
return;
}
// Token missing state
if (this.connectionStatus === 'token_missing') {
statusContent.innerHTML = `
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h4 style="font-size: 12px; font-weight: 600;">Figma Configuration</h4>
${this.getStatusBadge()}
</div>
<div style="padding: 12px; background-color: rgba(255, 191, 0, 0.1); border: 1px solid #ffbf00; border-radius: 4px; margin-bottom: 12px;">
<div style="font-size: 11px; color: #ffbf00;">
⚠️ <strong>Backend Configuration Required</strong><br/>
Please set the <code>FIGMA_TOKEN</code> environment variable on the backend server and restart.
</div>
</div>
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 8px;">
<strong>File Key:</strong> <code style="background-color: var(--vscode-bg); padding: 2px 6px; border-radius: 2px;">${ComponentHelpers.escapeHtml(this.figmaFileKey || 'N/A')}</code>
</div>
<div style="display: flex; gap: 8px; margin-top: 12px;">
<button id="figma-configure-btn" class="button" style="padding: 4px 12px; font-size: 11px; flex: 1;">
Reconfigure
</button>
</div>
</div>
`;
const configureBtn = statusContent.querySelector('#figma-configure-btn');
if (configureBtn) {
configureBtn.addEventListener('click', () => this.showConfiguration());
}
return;
}
// Connected state
if (this.connectionStatus === 'connected') {
statusContent.innerHTML = `
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h4 style="font-size: 12px; font-weight: 600;">Figma Sync</h4>
${this.getStatusBadge()}
</div>
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 8px;">
<strong>File Key:</strong> <code style="background-color: var(--vscode-bg); padding: 2px 6px; border-radius: 2px;">${ComponentHelpers.escapeHtml(this.figmaFileKey || 'N/A')}</code>
</div>
${this.lastSync ? `
<div style="font-size: 11px; color: var(--vscode-text-dim); margin-bottom: 12px;">
<strong>Last Sync:</strong> ${ComponentHelpers.formatRelativeTime(this.lastSync)}
</div>
` : ''}
<div style="display: flex; gap: 8px; margin-top: 12px;">
<button id="figma-sync-btn" class="button" style="padding: 4px 12px; font-size: 11px; flex: 1;">
🔄 Sync Now
</button>
<button id="figma-configure-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
⚙️
</button>
</div>
</div>
`;
const syncBtn = statusContent.querySelector('#figma-sync-btn');
const configureBtn = statusContent.querySelector('#figma-configure-btn');
if (syncBtn) {
syncBtn.addEventListener('click', () => this.syncFromFigma());
}
if (configureBtn) {
configureBtn.addEventListener('click', () => this.showConfiguration());
}
return;
}
// Error state
if (this.connectionStatus === 'error') {
statusContent.innerHTML = `
<div style="padding: 16px; background-color: var(--vscode-sidebar); border: 1px solid var(--vscode-border); border-radius: 4px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h4 style="font-size: 12px; font-weight: 600;">Figma Sync</h4>
${this.getStatusBadge()}
</div>
<div style="padding: 12px; background-color: rgba(244, 135, 113, 0.1); border: 1px solid #f48771; border-radius: 4px; margin-bottom: 12px;">
<div style="font-size: 11px; color: #f48771;">
❌ Failed to connect to Figma. Please check your configuration.
</div>
</div>
<div style="display: flex; gap: 8px;">
<button id="figma-configure-btn" class="button" style="padding: 4px 12px; font-size: 11px; flex: 1;">
Reconfigure
</button>
<button id="figma-sync-btn" class="button" style="padding: 4px 12px; font-size: 11px;">
Retry
</button>
</div>
</div>
`;
const configureBtn = statusContent.querySelector('#figma-configure-btn');
const syncBtn = statusContent.querySelector('#figma-sync-btn');
if (configureBtn) {
configureBtn.addEventListener('click', () => this.showConfiguration());
}
if (syncBtn) {
syncBtn.addEventListener('click', () => this.checkConfiguration());
}
}
}
render() {
this.innerHTML = `
<div style="padding: 16px; height: 100%; display: flex; flex-direction: column;">
<div id="figma-status-content" style="flex: 1;">
${ComponentHelpers.renderLoading('Checking Figma configuration...')}
</div>
</div>
`;
}
}
customElements.define('ds-figma-status', DSFigmaStatus);
export default DSFigmaStatus;