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
251 lines
6.5 KiB
JavaScript
251 lines
6.5 KiB
JavaScript
/**
|
|
* project-store.js
|
|
* Project configuration store
|
|
* Manages project list, current project selection, and project metadata
|
|
* MVP3: Integrates with API backend while maintaining localStorage fallback
|
|
*/
|
|
|
|
import apiClient from '../services/api-client.js';
|
|
|
|
export class ProjectStore {
|
|
constructor() {
|
|
// Load projects from localStorage (fallback for MVP2)
|
|
const stored = localStorage.getItem('projects_list');
|
|
this.projects = stored ? JSON.parse(stored) : this.getDefaultProjects();
|
|
|
|
// Load current project selection
|
|
this.currentProjectId = localStorage.getItem('current_project_id') || (this.projects.length > 0 ? this.projects[0].id : null);
|
|
|
|
this.listeners = new Set();
|
|
this.isLoading = false;
|
|
this.hasLoadedFromAPI = false;
|
|
|
|
// Try to load from API on initialization
|
|
this.syncWithAPI();
|
|
}
|
|
|
|
async syncWithAPI() {
|
|
try {
|
|
const projects = await apiClient.getProjects();
|
|
this.projects = projects;
|
|
this.hasLoadedFromAPI = true;
|
|
this.persist();
|
|
this.notifyListeners();
|
|
} catch (error) {
|
|
console.warn('[ProjectStore] Failed to sync with API, using localStorage:', error);
|
|
// Continue using localStorage data
|
|
}
|
|
}
|
|
|
|
getDefaultProjects() {
|
|
return [
|
|
{
|
|
id: 'DEFAULT-DESIGN-SYSTEM',
|
|
name: 'Default Design System',
|
|
skinSelected: 'default',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString()
|
|
}
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get all projects
|
|
* @returns {Array} Projects array
|
|
*/
|
|
getProjects() {
|
|
return [...this.projects];
|
|
}
|
|
|
|
/**
|
|
* Get current project
|
|
* @returns {Object|null} Current project or null
|
|
*/
|
|
getCurrentProject() {
|
|
return this.projects.find(p => p.id === this.currentProjectId) || null;
|
|
}
|
|
|
|
/**
|
|
* Get project by ID
|
|
* @param {string} id - Project ID
|
|
* @returns {Object|null} Project or null
|
|
*/
|
|
getProject(id) {
|
|
return this.projects.find(p => p.id === id) || null;
|
|
}
|
|
|
|
/**
|
|
* Create new project
|
|
* @param {Object} projectData - {name, key, description, figmaFileKey, jiraProjectKey, storybookUrl}
|
|
* @returns {Object} Created project
|
|
*/
|
|
async createProject(projectData) {
|
|
try {
|
|
// Try API first if authenticated
|
|
if (apiClient.accessToken) {
|
|
const project = await apiClient.createProject(projectData);
|
|
this.projects.push(project);
|
|
this.persist();
|
|
this.notifyListeners();
|
|
return project;
|
|
}
|
|
} catch (error) {
|
|
console.warn('[ProjectStore] API creation failed, using localStorage:', error);
|
|
}
|
|
|
|
// Fallback to localStorage
|
|
const now = new Date().toISOString();
|
|
const project = {
|
|
id: projectData.id || `PROJECT-${Date.now()}`,
|
|
name: projectData.name || 'Untitled Project',
|
|
key: projectData.key || `KEY-${Date.now()}`,
|
|
description: projectData.description || '',
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
...projectData
|
|
};
|
|
|
|
this.projects.push(project);
|
|
this.persist();
|
|
this.notifyListeners();
|
|
return project;
|
|
}
|
|
|
|
/**
|
|
* Update project
|
|
* @param {string} id - Project ID
|
|
* @param {Object} updates - Partial project updates
|
|
* @returns {Object|null} Updated project or null
|
|
*/
|
|
async updateProject(id, updates) {
|
|
const project = this.projects.find(p => p.id === id);
|
|
if (!project) return null;
|
|
|
|
try {
|
|
// Try API first if authenticated
|
|
if (apiClient.accessToken) {
|
|
const updated = await apiClient.updateProject(id, updates);
|
|
const index = this.projects.findIndex(p => p.id === id);
|
|
if (index !== -1) {
|
|
this.projects[index] = updated;
|
|
}
|
|
this.persist();
|
|
this.notifyListeners();
|
|
return updated;
|
|
}
|
|
} catch (error) {
|
|
console.warn('[ProjectStore] API update failed, using localStorage:', error);
|
|
}
|
|
|
|
// Fallback to localStorage
|
|
Object.assign(project, updates, {
|
|
updatedAt: new Date().toISOString()
|
|
});
|
|
|
|
this.persist();
|
|
this.notifyListeners();
|
|
return project;
|
|
}
|
|
|
|
/**
|
|
* Delete project
|
|
* @param {string} id - Project ID
|
|
* @returns {boolean} Success
|
|
*/
|
|
async deleteProject(id) {
|
|
try {
|
|
// Try API first if authenticated
|
|
if (apiClient.accessToken) {
|
|
await apiClient.deleteProject(id);
|
|
}
|
|
} catch (error) {
|
|
console.warn('[ProjectStore] API deletion failed, using localStorage:', error);
|
|
}
|
|
|
|
const index = this.projects.findIndex(p => p.id === id);
|
|
if (index === -1) return false;
|
|
|
|
this.projects.splice(index, 1);
|
|
|
|
// If deleted project was selected, select first available
|
|
if (this.currentProjectId === id) {
|
|
this.currentProjectId = this.projects.length > 0 ? this.projects[0].id : null;
|
|
localStorage.setItem('current_project_id', this.currentProjectId || '');
|
|
}
|
|
|
|
this.persist();
|
|
this.notifyListeners();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Select/switch to different project
|
|
* @param {string} id - Project ID
|
|
* @returns {Object|null} Selected project or null
|
|
*/
|
|
selectProject(id) {
|
|
const project = this.projects.find(p => p.id === id);
|
|
if (!project) return null;
|
|
|
|
this.currentProjectId = id;
|
|
localStorage.setItem('current_project_id', id);
|
|
this.notifyListeners();
|
|
return project;
|
|
}
|
|
|
|
/**
|
|
* Update selected skin for current project
|
|
* @param {string} skin - Skin name
|
|
* @returns {Object|null} Updated project
|
|
*/
|
|
setSkinForCurrentProject(skin) {
|
|
if (!this.currentProjectId) return null;
|
|
return this.updateProject(this.currentProjectId, { skinSelected: skin });
|
|
}
|
|
|
|
/**
|
|
* Persist to localStorage
|
|
*/
|
|
persist() {
|
|
localStorage.setItem('projects_list', JSON.stringify(this.projects));
|
|
}
|
|
|
|
/**
|
|
* Subscribe to changes
|
|
* @param {Function} callback - Called on state changes
|
|
* @returns {Function} Unsubscribe function
|
|
*/
|
|
subscribe(callback) {
|
|
this.listeners.add(callback);
|
|
return () => this.listeners.delete(callback);
|
|
}
|
|
|
|
notifyListeners() {
|
|
this.listeners.forEach(listener => listener({
|
|
projects: this.getProjects(),
|
|
currentProject: this.getCurrentProject()
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Reset to default project
|
|
*/
|
|
reset() {
|
|
localStorage.removeItem('projects_list');
|
|
localStorage.removeItem('current_project_id');
|
|
this.projects = this.getDefaultProjects();
|
|
this.currentProjectId = this.projects[0].id;
|
|
this.notifyListeners();
|
|
}
|
|
}
|
|
|
|
// Singleton instance
|
|
let projectStoreInstance = null;
|
|
|
|
export function useProjectStore() {
|
|
if (!projectStoreInstance) {
|
|
projectStoreInstance = new ProjectStore();
|
|
}
|
|
return projectStoreInstance;
|
|
}
|