FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

149
src/lib/projects/service.ts Normal file
View File

@@ -0,0 +1,149 @@
import { createSignal, createEffect, Accessor } from 'solid-js';
import { Project, ProjectStatus, ProjectCollaborator, UserRole } from '../auth/types';
const STORAGE_KEY = 'frenocorp_projects';
function loadProjects(): Project[] {
try {
const data = localStorage.getItem(STORAGE_KEY);
return data ? JSON.parse(data) : [];
} catch {
return [];
}
}
function saveProjects(projects: Project[]): void {
localStorage.setItem(STORAGE_KEY, JSON.stringify(projects));
}
export interface ProjectService {
projects: Accessor<Project[]>;
loading: Accessor<boolean>;
createProject: (name: string, description: string, ownerId: string) => Promise<Project>;
updateProject: (id: string, updates: Partial<Project>) => Promise<Project>;
deleteProject: (id: string) => Promise<void>;
addCollaborator: (projectId: string, userId: string, role: UserRole) => Promise<Project>;
removeCollaborator: (projectId: string, userId: string) => Promise<Project>;
archiveProject: (id: string) => Promise<Project>;
}
export function createProjectService(): ProjectService {
const [projects, setProjects] = createSignal<Project[]>(loadProjects());
const [loading, setLoading] = createSignal(false);
createEffect(() => {
const current = projects();
saveProjects(current);
});
const createProject = async (
name: string,
description: string,
ownerId: string
): Promise<Project> => {
setLoading(true);
const project: Project = {
id: generateProjectId(),
name,
description,
ownerId,
status: 'draft',
collaborators: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
setProjects((prev) => [project, ...prev]);
setLoading(false);
return project;
};
const updateProject = async (
id: string,
updates: Partial<Project>
): Promise<Project> => {
setLoading(true);
setProjects((prev) =>
prev.map((p) =>
p.id === id
? { ...p, ...updates, updatedAt: new Date().toISOString() }
: p
)
);
setLoading(false);
return projects().find((p) => p.id === id)!;
};
const deleteProject = async (id: string): Promise<void> => {
setLoading(true);
setProjects((prev) => prev.filter((p) => p.id !== id));
setLoading(false);
};
const addCollaborator = async (
projectId: string,
userId: string,
role: UserRole
): Promise<Project> => {
setLoading(true);
setProjects((prev) =>
prev.map((p) => {
if (p.id !== projectId) return p;
const existing = p.collaborators.find((c) => c.userId === userId);
if (existing) return p;
return {
...p,
collaborators: [
...p.collaborators,
{ userId, role, addedAt: new Date().toISOString() },
],
updatedAt: new Date().toISOString(),
};
})
);
setLoading(false);
return projects().find((p) => p.id === projectId)!;
};
const removeCollaborator = async (
projectId: string,
userId: string
): Promise<Project> => {
setLoading(true);
setProjects((prev) =>
prev.map((p) => {
if (p.id !== projectId) return p;
return {
...p,
collaborators: p.collaborators.filter((c) => c.userId !== userId),
updatedAt: new Date().toISOString(),
};
})
);
setLoading(false);
return projects().find((p) => p.id === projectId)!;
};
const archiveProject = async (id: string): Promise<Project> => {
return updateProject(id, { status: 'archived' });
};
return {
projects,
loading,
createProject,
updateProject,
deleteProject,
addCollaborator,
removeCollaborator,
archiveProject,
};
}
export function generateProjectId(): string {
return `proj_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
}
export function useProjectService() {
return createProjectService();
}