Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import js from '@eslint/js';
import solid from 'eslint-plugin-solid/configs/recommended';
// import eslintPluginUnicorn from 'eslint-plugin-unicorn';
import * as tsParser from '@typescript-eslint/parser';
import sonarjs from 'eslint-plugin-sonarjs';
// import eslintPluginUnicorn from 'eslint-plugin-unicorn';
// import sonarjs from 'eslint-plugin-sonarjs';

export default [
js.configs.recommended,
Expand All @@ -15,9 +15,9 @@ export default [
// },
{
files: ['**/*.{js,jsx,ts,tsx}'],
plugins: {
sonarjs,
},
// plugins: {
// sonarjs,
// },
languageOptions: {
parser: tsParser,
parserOptions: {
Expand Down Expand Up @@ -107,7 +107,7 @@ export default [
caughtErrorsIgnorePattern: '^_',
},
],
'sonarjs/cognitive-complexity': 'error',
// 'sonarjs/cognitive-complexity': 'error',
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
},
"devDependencies": {
"@solidjs/testing-library": "^0.8.10",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/jest-dom": "^6.9.1",
"jsdom": "^26.1.0",
"solid-icons": "^1.1.0",
"solid-js": "^1.9.10",
Expand Down
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"devDependencies": {
"@solidjs/testing-library": "^0.8.10",
"@tailwindcss/vite": "^4.1.18",
"@testing-library/jest-dom": "^6.9.1",
"@vitest/ui": "^4.0.15",
"jsdom": "^27.3.0",
"prettier": "^3.7.4",
Expand Down
18 changes: 10 additions & 8 deletions packages/web/src/components/project-ui/ProjectContext.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
/**
* ProjectContext - Provides project data and handlers to child components
* Eliminates prop drilling for projectId, handlers, and utilities
* ProjectContext - Provides project identity and user role to child components
*
* This context is simplified to only provide:
* - projectId: The current project ID
* - userRole: The current user's role in the project
* - isOwner: Whether the current user is the project owner
* - getAssigneeName: Helper to get a member's display name
*
* For actions (mutations), import projectActionsStore directly:
* import projectActionsStore from '@/stores/projectActionsStore.js';
*/

import { createContext, useContext, createMemo } from 'solid-js';
Expand Down Expand Up @@ -34,12 +42,6 @@ export function ProjectProvider(props) {
get projectId() {
return props.projectId;
},
get handlers() {
return props.handlers;
},
get projectActions() {
return props.projectActions;
},
userRole,
isOwner,
getAssigneeName,
Expand Down
27 changes: 22 additions & 5 deletions packages/web/src/components/project-ui/ProjectDashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import { createEffect, createSignal, onCleanup, For, Show } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import useNotifications from '@primitives/useNotifications.js';
import projectStore from '@/stores/projectStore.js';
import { useConfirmDialog } from '@corates/ui';
import useProjectMemberHandlers from '@primitives/useProjectMemberHandlers.js';
import projectActionsStore from '@/stores/projectActionsStore';
import { useConfirmDialog, showToast } from '@corates/ui';
import { useBetterAuth } from '@api/better-auth-store.js';
import CreateProjectForm from './CreateProjectForm.jsx';
import ProjectCard from './ProjectCard.jsx';
import { getRestoreParamsFromUrl } from '@lib/formStatePersistence.js';

export default function ProjectDashboard(props) {
const navigate = useNavigate();
const confirmDialog = useConfirmDialog();

// Check if we're returning from OAuth with state to restore
const restoreParams = getRestoreParamsFromUrl();
Expand Down Expand Up @@ -80,9 +81,25 @@ export default function ProjectDashboard(props) {
navigate(`/projects/${projectId}`);
};

// Confirm dialog and handlers for delete
const confirmDialog = useConfirmDialog();
const { handleDeleteProject } = useProjectMemberHandlers(null, confirmDialog);
// Handler for deleting projects from dashboard
const handleDeleteProject = async targetProjectId => {
const confirmed = await confirmDialog.open({
title: 'Delete Project',
description:
'Are you sure you want to delete this entire project? This action cannot be undone.',
confirmText: 'Delete Project',
variant: 'danger',
});
if (!confirmed) return;

try {
// Use deleteById since we're outside the project view (no active project set)
await projectActionsStore.project.deleteById(targetProjectId);
showToast.success('Project Deleted', 'The project has been deleted successfully');
} catch (err) {
showToast.error('Delete Failed', err.message || 'Failed to delete project');
}
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return (
<div class='space-y-6'>
Expand Down
76 changes: 25 additions & 51 deletions packages/web/src/components/project-ui/ProjectView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,19 @@ import { createSignal, createEffect, Show, onCleanup, batch } from 'solid-js';
import { useParams, useNavigate, useLocation } from '@solidjs/router';
import useProject from '@/primitives/useProject/index.js';
import projectStore from '@/stores/projectStore.js';
import projectActionsStore from '@/stores/projectActionsStore';
import { useBetterAuth } from '@api/better-auth-store.js';
import { uploadPdf, deletePdf } from '@api/pdf-api.js';
import { cachePdf } from '@primitives/pdfCache.js';
import { importFromGoogleDrive } from '@api/google-drive.js';
import { useConfirmDialog, Tabs } from '@corates/ui';
import { Tabs } from '@corates/ui';
import { BiRegularHome } from 'solid-icons/bi';
import { BsListTask } from 'solid-icons/bs';
import { CgArrowsExchange } from 'solid-icons/cg';
import { AiFillCheckCircle, AiOutlineBook } from 'solid-icons/ai';

// Handler hooks
import useProjectStudyHandlers from '@primitives/useProjectStudyHandlers.js';
import useProjectChecklistHandlers from '@primitives/useProjectChecklistHandlers.js';
import useProjectPdfHandlers from '@primitives/useProjectPdfHandlers.js';
import useProjectMemberHandlers from '@primitives/useProjectMemberHandlers.js';

// Components
import { ProjectProvider } from './ProjectContext.jsx';
import AddMemberModal from './AddMemberModal.jsx';
import ProjectHeader from './ProjectHeader.jsx';
import PdfPreviewPanel from './PdfPreviewPanel.jsx';
import { OverviewTab } from './overview-tab';
Expand All @@ -39,33 +33,28 @@ export default function ProjectView() {
const navigate = useNavigate();
const location = useLocation();
const { user } = useBetterAuth();
const confirmDialog = useConfirmDialog();

// Modal state that needs to be at this level (shared across tabs or triggered from header)
const [showAddMemberModal, setShowAddMemberModal] = createSignal(false);

// Y.js hook for write operations
const projectActions = useProject(params.projectId);
const { connect, disconnect, createStudy, addPdfToStudy } = projectActions;
// Y.js hook - connection is also registered with projectActionsStore
const projectConnection = useProject(params.projectId);
const { connect, disconnect } = projectConnection;

// Read data from store (only what's needed at this level)
const studies = () => projectStore.getStudies(params.projectId);
const meta = () => projectStore.getMeta(params.projectId);
const connectionState = () => projectStore.getConnectionState(params.projectId);

// Create handlers
const studyHandlers = useProjectStudyHandlers(params.projectId, projectActions, confirmDialog);
const checklistHandlers = useProjectChecklistHandlers(
params.projectId,
projectActions,
confirmDialog,
);
const pdfHandlers = useProjectPdfHandlers(params.projectId, projectActions);
const memberHandlers = useProjectMemberHandlers(params.projectId, confirmDialog);

// Connect to Y.js on mount
// Set active project for action store (so methods don't need projectId)
createEffect(() => {
if (params.projectId) connect();
if (params.projectId) {
projectActionsStore._setActiveProject(params.projectId);
connect();
}
});

// Clear active project on unmount
onCleanup(() => {
projectActionsStore._clearActiveProject();
disconnect();
});

// Retrieve pending data from projectStore (stored during project creation)
Expand All @@ -92,14 +81,14 @@ export default function ProjectView() {
doi: pdf.doi ?? pdf.metadata?.doi ?? null,
importSource: pdf.metadata?.importSource || 'pdf',
};
const studyId = createStudy(pdf.title, abstract, metadata);
const studyId = projectActionsStore.study.create(pdf.title, abstract, metadata);
if (studyId && pdf.data) {
const arrayBuffer = new Uint8Array(pdf.data).buffer;
uploadPdf(params.projectId, studyId, arrayBuffer, pdf.fileName)
.then(result => {
cachePdf(params.projectId, studyId, result.fileName, arrayBuffer).catch(console.warn);
try {
addPdfToStudy(studyId, {
projectActionsStore.pdf.addToStudy(studyId, {
key: result.key,
fileName: result.fileName,
size: result.size,
Expand Down Expand Up @@ -128,7 +117,7 @@ export default function ProjectView() {
});

for (const ref of refs) {
createStudy(ref.title, ref.metadata?.abstract || '', ref.metadata || {});
projectActionsStore.study.create(ref.title, ref.metadata?.abstract || '', ref.metadata || {});
}
});

Expand All @@ -151,12 +140,12 @@ export default function ProjectView() {
...(file.metadata || {}),
importSource: file.metadata?.importSource || file.importSource || 'google-drive',
};
const studyId = createStudy(title, abstract, metadata);
const studyId = projectActionsStore.study.create(title, abstract, metadata);
if (studyId && file.id) {
importFromGoogleDrive(file.id, params.projectId, studyId)
.then(result => {
try {
addPdfToStudy(studyId, {
projectActionsStore.pdf.addToStudy(studyId, {
key: result.file.key,
fileName: result.file.fileName,
size: result.file.size,
Expand All @@ -175,8 +164,6 @@ export default function ProjectView() {
}
});

onCleanup(() => disconnect());

// Helper functions to count studies for tab badges
const getToDoCount = () => {
const userId = user()?.id;
Expand Down Expand Up @@ -246,25 +233,20 @@ export default function ProjectView() {

return (
<div class='mx-auto max-w-7xl p-6'>
<ProjectProvider
projectId={params.projectId}
handlers={{ studyHandlers, checklistHandlers, pdfHandlers, memberHandlers }}
projectActions={projectActions}
onAddMember={() => setShowAddMemberModal(true)}
>
<ProjectProvider projectId={params.projectId}>
<ProjectHeader
name={() => meta()?.name}
description={() => meta()?.description}
onRename={projectActions.renameProject}
onUpdateDescription={projectActions.updateDescription}
onRename={newName => projectActionsStore.project.rename(newName)}
onUpdateDescription={desc => projectActionsStore.project.updateDescription(desc)}
onBack={() => navigate('/dashboard')}
/>

<Tabs tabs={tabDefinitions} value={tabFromUrl()} onValueChange={handleTabChange}>
{tabValue => (
<>
<Show when={tabValue === 'overview'}>
<OverviewTab onAddMember={() => setShowAddMemberModal(true)} />
<OverviewTab />
</Show>

<Show when={tabValue === 'all-studies'}>
Expand All @@ -287,15 +269,7 @@ export default function ProjectView() {
</Tabs>
</ProjectProvider>

<AddMemberModal
isOpen={showAddMemberModal()}
onClose={() => setShowAddMemberModal(false)}
projectId={params.projectId}
/>

<PdfPreviewPanel />

<confirmDialog.ConfirmDialogComponent />
</div>
);
}
Loading