Skip to content

feat: Add folder/grouping functionality for pages #7522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: preview
Choose a base branch
from

Conversation

naaa760
Copy link

@naaa760 naaa760 commented Jul 31, 2025

Description

Implemented folder/grouping functionality for pages to allow users to organize pages into folders for better discoverability and organization. This feature addresses the need for organizing pages in projects with many pages and long-running projects.

#7511

Key Features:

  • Create folders with custom names, descriptions, and colors
  • Add pages to folders via context menu or folder interface
  • Remove pages from folders with a simple "Remove" button
  • Visual folder display with expand/collapse functionality
  • Pages organized by folders in the pages list
  • Complete state management with MobX store integration

Technical Implementation:

  • Added TPageFolder type and folder_id field to TPage type
  • Created PageFolderStore for folder state management
  • Created PageFolderService for API operations
  • Added folder components: FolderItem, CreateFolderModal, AddToFolderModal
  • Updated pages list to display folders and organize pages
  • Added "Add to Folder" action to page context menu
  • Integrated folder store into root store

Summary by CodeRabbit

  • New Features

    • Introduced page folders, allowing users to organize pages into folders.
    • Added the ability to create, edit, and delete folders via a user-friendly modal interface.
    • Enabled adding and removing pages from folders directly from the page actions menu.
    • Pages list now displays folders and their contained pages, with support for expanding/collapsing folder views.
  • Improvements

    • Updated the page actions menu with a new "Add to Folder" option.
    • Enhanced the pages list UI to visually separate pages inside folders from those outside.
  • Bug Fixes

    • Minor UI adjustments for improved accessibility and user experience.
  • Chores

    • Underlying support for folder management and page-folder association added to the application’s data layer.

Copy link
Contributor

coderabbitai bot commented Jul 31, 2025

Walkthrough

This change introduces comprehensive support for organizing pages into folders. It adds new folder-related types, store management, service logic, and UI components for creating, displaying, and modifying folders and their contents. The update also extends page actions to allow assigning pages to folders and adjusts the page list UI to reflect folder organization.

Changes

Cohort / File(s) Change Summary
Types: Page and Folder Types
packages/types/src/pages.d.ts
Added TPageFolder type and extended TPage with optional folder_id property.
Store: Page Folder Store and Root Store Integration
web/core/store/pages/page-folder.store.ts, web/core/store/root.store.ts, web/ce/hooks/store/use-page-store.ts
Introduced MobX PageFolderStore, store interface, and root store integration; added React hook for accessing the folder store.
Services: Page Folder API
web/core/services/page-folder.ts
Added PageFolderService class for CRUD and association operations with folders via REST API.
UI: Folder Modals and Items
web/core/components/pages/folder/add-to-folder-modal.tsx, web/core/components/pages/folder/create-folder-modal.tsx, web/core/components/pages/folder/folder-item.tsx
Introduced modal dialogs for adding pages to folders and creating folders; added folder item component for displaying folders and their pages.
UI: Page Actions and List
web/core/components/pages/dropdowns/actions.tsx, web/core/components/pages/list/block-item-action.tsx, web/core/components/pages/list/root.tsx
Extended page actions with "add to folder" option, updated list UI to display folders and unassigned pages, and adjusted item actions.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI (PagesListRoot)
  participant FolderStore
  participant PageFolderService
  participant Modal (CreateFolderModal/AddToFolderModal)

  User->>UI (PagesListRoot): Open page list
  UI (PagesListRoot)->>FolderStore: fetchFoldersList()
  FolderStore->>PageFolderService: fetchAll()
  PageFolderService-->>FolderStore: Return folders
  FolderStore-->>UI (PagesListRoot): Folders data

  User->>UI (PagesListRoot): Click "Create Folder"
  UI (PagesListRoot)->>Modal (CreateFolderModal): Open modal
  User->>Modal (CreateFolderModal): Submit folder details
  Modal (CreateFolderModal)->>FolderStore: createFolder()
  FolderStore->>PageFolderService: create()
  PageFolderService-->>FolderStore: Return new folder
  FolderStore-->>Modal (CreateFolderModal): Folder created
  Modal (CreateFolderModal)-->>UI (PagesListRoot): Close modal

  User->>UI (PagesListRoot): Select "Add to Folder" on page
  UI (PagesListRoot)->>Modal (AddToFolderModal): Open modal
  User->>Modal (AddToFolderModal): Select folder and submit
  Modal (AddToFolderModal)->>FolderStore: addPageToFolder()
  FolderStore->>PageFolderService: addPageToFolder()
  PageFolderService-->>FolderStore: Success
  FolderStore-->>Modal (AddToFolderModal): Update state
  Modal (AddToFolderModal)-->>UI (PagesListRoot): Close modal
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related issues

  • [feature]: Folders for pages #7511: Implements type definitions, store management, UI components, and service logic for page folders, directly addressing the request for organizing pages into folders.

Suggested labels

✨feature, 🌐frontend, pages

Suggested reviewers

  • Palanikannan1437
  • prateekshourya29

Poem

A folder for every page, hooray!
Pages now gather, no longer astray.
With modals and stores,
And menus galore,
The rabbit hops in, organizing the fray.
🗂️✨
— CodeRabbit

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@CLAassistant
Copy link

CLAassistant commented Jul 31, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (9)
web/core/components/pages/list/block-item-action.tsx (2)

46-48: Loss of tooltip functionality.

The Info icon is no longer wrapped in a tooltip, reducing user experience by removing the creation date information display.


52-61: Simplified but functional favorite toggle.

The native button implementation works but is less polished than the previous FavoriteStar component. Consider if this change aligns with the design system.

web/core/components/pages/folder/folder-item.tsx (1)

79-84: Review accessibility and positioning of remove button.

The remove button is positioned absolutely which may cause accessibility issues and could overlap with page content on narrow screens.

Consider alternative approaches for better UX:

-                <button
-                  onClick={() => page?.id && handleRemovePageFromFolder(page.id)}
-                  className="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-custom-text-400 hover:text-custom-text-100 transition-colors"
-                >
-                  Remove
-                </button>
+                <div className="flex items-center justify-between">
+                  <PageListBlock pageId={page?.id || ""} storeType={storeType} />
+                  <button
+                    onClick={() => page?.id && handleRemovePageFromFolder(page.id)}
+                    className="ml-2 text-xs text-custom-text-400 hover:text-custom-text-100 transition-colors"
+                    aria-label={`Remove ${page?.title || 'page'} from folder`}
+                  >
+                    Remove
+                  </button>
+                </div>
web/core/components/pages/dropdowns/actions.tsx (2)

54-54: Consider improving type safety.

The extraOptions prop type was changed from a specific type to any, which reduces type safety.

If possible, maintain stronger typing:

-  extraOptions?: (any & { key: TPageActions })[];
+  extraOptions?: Array<{
+    key: TPageActions;
+    action: () => void;
+    title: string;
+    icon: React.ComponentType<any>;
+    shouldRender: boolean;
+  }>;

193-193: Complete the context menu implementation.

The placeholder comment suggests incomplete implementation. Consider either implementing the context menu or removing the placeholder.

Would you like me to help implement the context menu functionality or should this placeholder be removed?

web/core/components/pages/folder/add-to-folder-modal.tsx (1)

51-52: Consider using semantic HTML for better accessibility.

The modal implementation uses custom styling instead of semantic dialog elements, which may impact accessibility.

Consider using the HTML dialog element or ARIA attributes:

-    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
-      <div className="bg-white rounded-lg shadow-xl w-full max-w-md mx-4">
+    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" role="dialog" aria-modal="true" aria-labelledby="modal-title">
+      <div className="bg-white rounded-lg shadow-xl w-full max-w-md mx-4">

And add an id to the title:

-          <h3 className="text-lg font-medium">Add to Folder</h3>
+          <h3 id="modal-title" className="text-lg font-medium">Add to Folder</h3>
web/core/components/pages/folder/create-folder-modal.tsx (1)

111-120: Validate color input format.

The color text input allows free-form text but doesn't validate if it's a valid color format, which could cause issues.

Add color format validation:

              <input
                type="text"
                value={formData.color}
                onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                  setFormData({ ...formData, color: e.target.value })
                }
                placeholder="#3B82F6"
+               pattern="^#[0-9A-Fa-f]{6}$"
                className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
              />

Or add client-side validation in the onChange handler.

web/core/store/pages/page-folder.store.ts (2)

38-38: Consider initializing loader as undefined

The loader is initialized as "init-loader" which might cause the isAnyFolderAvailable computed property to return true even when no data has been fetched yet. Consider initializing it as undefined to avoid false positives.

-  loader: TLoader = "init-loader";
+  loader: TLoader = undefined;

105-108: Optimize folder data updates

Consider using a more efficient approach to update multiple folders at once instead of iterating with lodash set.

  const folders = await this.service.fetchAll(workspaceSlug, projectId);
  runInAction(() => {
-    for (const folder of folders) if (folder?.id) set(this.data, [folder.id], folder);
+    const folderMap = folders.reduce((acc, folder) => {
+      if (folder?.id) acc[folder.id] = folder;
+      return acc;
+    }, {} as Record<string, TPageFolder>);
+    Object.assign(this.data, folderMap);
    this.loader = undefined;
  });
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1458c75 and 34a4b66.

📒 Files selected for processing (11)
  • packages/types/src/pages.d.ts (2 hunks)
  • web/ce/hooks/store/use-page-store.ts (2 hunks)
  • web/core/components/pages/dropdowns/actions.tsx (7 hunks)
  • web/core/components/pages/folder/add-to-folder-modal.tsx (1 hunks)
  • web/core/components/pages/folder/create-folder-modal.tsx (1 hunks)
  • web/core/components/pages/folder/folder-item.tsx (1 hunks)
  • web/core/components/pages/list/block-item-action.tsx (1 hunks)
  • web/core/components/pages/list/root.tsx (2 hunks)
  • web/core/services/page-folder.ts (1 hunks)
  • web/core/store/pages/page-folder.store.ts (1 hunks)
  • web/core/store/root.store.ts (4 hunks)
🔇 Additional comments (21)
web/ce/hooks/store/use-page-store.ts (2)

6-6: LGTM!

The import follows the established pattern and integrates well with the existing store structure.


27-32: LGTM!

The hook implementation follows the same pattern as usePageStore with proper error handling and type safety.

web/core/store/root.store.ts (4)

27-27: LGTM!

The import is properly placed and follows the existing import structure.


54-54: LGTM!

The property declaration follows the established pattern with proper typing.


93-93: LGTM!

The store initialization in the constructor is consistent with other stores and uses proper type casting.


126-126: LGTM!

The store reset in resetOnSignOut is properly implemented and consistent with other stores.

packages/types/src/pages.d.ts (2)

4-16: Well-structured type definition for folders.

The TPageFolder type includes all necessary properties for folder management with appropriate optional fields and standard metadata.


38-38: LGTM!

The folder_id property properly establishes the relationship between pages and folders.

web/core/components/pages/list/block-item-action.tsx (1)

69-69: LGTM!

The "add-to-folder" option properly integrates the new folder functionality into the page actions.

web/core/components/pages/list/root.tsx (6)

1-3: LGTM!

The imports properly bring in the necessary dependencies for folder functionality.


22-22: LGTM!

Proper state management for the create folder modal.


26-26: LGTM!

The hook integration follows the established pattern.


33-38: Well-implemented folder-page separation logic.

The logic correctly identifies which pages are in folders and which are not, enabling proper organization in the UI.


40-46: LGTM!

Clean modal state management functions.


52-86: Well-structured folder organization UI.

The component properly renders folders first, followed by a create folder button, and then remaining pages. The modal integration is correctly implemented.

web/core/components/pages/folder/folder-item.tsx (1)

39-39: filter(Boolean) correctly filters out missing pages

The getPageById function is defined as:

getPageById = computedFn((pageId: string) => this.data?.[pageId] || undefined);

—which means it only ever returns a TProjectPage instance or undefined. Using filter(Boolean) here cleanly removes any undefined entries and preserves all valid page objects. No change is needed.

web/core/components/pages/dropdowns/actions.tsx (1)

106-112: LGTM! Well-structured folder action.

The new "add-to-folder" action is properly integrated with appropriate conditions (only showing for non-archived pages) and follows the established pattern.

web/core/components/pages/folder/add-to-folder-modal.tsx (1)

80-86: Excellent empty state handling.

The component gracefully handles the case when no folders exist with helpful messaging and visual feedback.

web/core/components/pages/folder/create-folder-modal.tsx (1)

34-38: LGTM! Proper input sanitization.

The component correctly trims whitespace from inputs and handles empty description by setting it to undefined.

web/core/services/page-folder.ts (2)

94-108: Verify HTTP method for adding pages to folders.

Using POST for adding a page to a folder is appropriate, but consider if this should be idempotent (using PUT instead) to handle duplicate additions gracefully.

The API design choice depends on the expected behavior. Should multiple calls to add the same page to the same folder be idempotent?


6-17: LGTM! Clean and consistent API structure.

The service methods follow a consistent pattern with proper HTTP methods, error handling, and return types.

Comment on lines +25 to +39
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedFolderId) return;

setIsSubmitting(true);
try {
await addPageToFolder(selectedFolderId, pageId);
setSelectedFolderId("");
onClose();
} catch (error) {
console.error("Failed to add page to folder", error);
} finally {
setIsSubmitting(false);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling and user feedback.

Similar to the folder item component, this only logs errors to console without providing user feedback.

Add user-visible error handling:

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!selectedFolderId) return;

    setIsSubmitting(true);
    try {
      await addPageToFolder(selectedFolderId, pageId);
      setSelectedFolderId("");
      onClose();
+     // Add success notification
    } catch (error) {
      console.error("Failed to add page to folder", error);
+     // Show error message to user
    } finally {
      setIsSubmitting(false);
    }
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedFolderId) return;
setIsSubmitting(true);
try {
await addPageToFolder(selectedFolderId, pageId);
setSelectedFolderId("");
onClose();
} catch (error) {
console.error("Failed to add page to folder", error);
} finally {
setIsSubmitting(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedFolderId) return;
setIsSubmitting(true);
try {
await addPageToFolder(selectedFolderId, pageId);
setSelectedFolderId("");
onClose();
// Add success notification
} catch (error) {
console.error("Failed to add page to folder", error);
// Show error message to user
} finally {
setIsSubmitting(false);
}
};
🤖 Prompt for AI Agents
In web/core/components/pages/folder/add-to-folder-modal.tsx around lines 25 to
39, the error handling only logs errors to the console without informing the
user. Enhance this by adding user-visible error feedback, such as setting an
error state and displaying an error message in the UI when addPageToFolder
fails, so users are aware of the failure.

Comment on lines +28 to +46
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.name.trim()) return;

setIsSubmitting(true);
try {
await createFolder({
name: formData.name.trim(),
description: formData.description.trim() || undefined,
color: formData.color,
});
setFormData({ name: "", description: "", color: "#3B82F6" });
onClose();
} catch (error) {
console.error("Failed to create folder", error);
} finally {
setIsSubmitting(false);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consistent error handling with other modals.

The error handling pattern is the same as in other modals - only console logging without user feedback.

Apply the same improvement pattern as suggested for other modals to provide user feedback on creation success/failure.

🤖 Prompt for AI Agents
In web/core/components/pages/folder/create-folder-modal.tsx around lines 28 to
46, the error handling only logs errors to the console without providing user
feedback. Update the catch block to show an error notification or message to the
user indicating the folder creation failed. Additionally, add a success
notification after the folder is created successfully before calling onClose.
Use the same notification or feedback mechanism as other modals in the project
for consistency.

Comment on lines +31 to +37
const handleRemovePageFromFolder = async (pageId: string) => {
try {
await removePageFromFolder(folder.id, pageId);
} catch (error) {
console.error("Failed to remove page from folder", error);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add user feedback for remove operation.

The error handling logs to console but provides no user feedback when removing a page from folder fails. Users won't know if the operation succeeded or failed.

Consider adding a toast notification or error state to inform users about operation status:

  const handleRemovePageFromFolder = async (pageId: string) => {
    try {
      await removePageFromFolder(folder.id, pageId);
+     // Add success feedback here
    } catch (error) {
      console.error("Failed to remove page from folder", error);
+     // Add user-visible error feedback here
    }
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleRemovePageFromFolder = async (pageId: string) => {
try {
await removePageFromFolder(folder.id, pageId);
} catch (error) {
console.error("Failed to remove page from folder", error);
}
};
const handleRemovePageFromFolder = async (pageId: string) => {
try {
await removePageFromFolder(folder.id, pageId);
+ // Add success feedback here
} catch (error) {
console.error("Failed to remove page from folder", error);
+ // Add user-visible error feedback here
}
};
🤖 Prompt for AI Agents
In web/core/components/pages/folder/folder-item.tsx around lines 31 to 37, the
error handling for removing a page from a folder only logs to the console
without notifying the user. Enhance the function by adding user feedback such as
a toast notification or an error message display to inform users whether the
removal succeeded or failed. Implement this by integrating a notification
mechanism in both the try and catch blocks to clearly communicate the operation
status.

Comment on lines +40 to +41
<div className="cursor-default">{/* Avatar would be implemented here */}</div>
<div className="cursor-default text-custom-text-300">{/* Access icon would be implemented here */}</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Incomplete implementation with placeholder divs.

The avatar and access icon have been replaced with placeholder divs, which removes functionality and user experience features. Consider implementing proper alternatives or restoring the original components.

🤖 Prompt for AI Agents
In web/core/components/pages/list/block-item-action.tsx at lines 40 to 41, the
placeholder divs for the avatar and access icon remove their intended
functionality and degrade user experience. Replace these placeholder divs with
the original avatar and access icon components or implement suitable
alternatives that restore their visual and interactive features.

// derived values
const filteredPageIds = getCurrentProjectFilteredPageIds(pageType);
const folders = getFoldersByProject(""); // We'll need to get projectId from context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Critical: Hardcoded empty project ID will not work.

The empty string passed to getFoldersByProject("") will not return the correct folders for the current project. This needs to be resolved before the feature can function properly.

You need to get the project ID from context or props. Consider using a project store hook or passing the project ID as a prop to this component.


🏁 Script executed:

#!/bin/bash
# Description: Search for how project ID is typically accessed in similar components
# Expected: Find patterns for accessing current project ID in list components

rg -A 5 -B 5 "getCurrentProject|projectId|project\.id" --type tsx

Length of output: 95


🏁 Script executed:

#!/bin/bash
# Search for getFoldersByProject usage and its definition across the codebase
rg -C3 "getFoldersByProject" -g '*.ts' -g '*.tsx'

Length of output: 2668


🏁 Script executed:

#!/bin/bash
# Display the beginning of the component to locate props or hooks for projectId
sed -n '1,200p' web/core/components/pages/list/root.tsx

Length of output: 2970


Critical: Replace hardcoded "" with actual projectId from route or context

The empty string passed to getFoldersByProject("") prevents retrieving any folders. You need to obtain the current projectId—for example via your routing params or a project store hook—and pass it in.

Locations to update:

  • web/core/components/pages/list/root.tsx, line where you call getFoldersByProject("")

Suggested change (using React Router‘s useParams, adjust if you use Next.js or another router):

-import { FC, useState } from "react";
+import { FC, useState } from "react";
+import { useParams } from "react-router-dom"; // or your framework’s hook

// ...

export const PagesListRoot: FC<TPagesListRoot> = observer((props) => {
  // extract projectId from URL params
- const { pageType, storeType } = props;
+ const { pageType, storeType } = props;
+ const { projectId } = useParams<{ projectId: string }>();

  const [isCreateFolderModalOpen, setIsCreateFolderModalOpen] = useState(false);

  // store hooks
  const { getCurrentProjectFilteredPageIds } = usePageStore(storeType);
  const { getFoldersByProject } = usePageFolderStore();

  // derived values
  const filteredPageIds = getCurrentProjectFilteredPageIds(pageType);
- const folders = getFoldersByProject(""); // We'll need to get projectId from context
+ const folders = getFoldersByProject(projectId!);

If your routing or context differs (e.g. Next.js useRouter or a custom project store), replace useParams with the appropriate hook. Ensure projectId is non-empty before calling getFoldersByProject.

🤖 Prompt for AI Agents
In web/core/components/pages/list/root.tsx at line 30, replace the hardcoded
empty string argument in getFoldersByProject("") with the actual projectId
obtained from the route parameters or context. Use a hook like React Router's
useParams or your framework's equivalent to retrieve projectId, ensure it is
defined and non-empty, then pass it to getFoldersByProject to correctly fetch
folders for the current project.

Comment on lines +1 to +130
if (!response.ok) {
throw new Error("Failed to fetch page folder");
}

return response.json();
}

async create(workspaceSlug: string, projectId: string, folderData: Partial<TPageFolder>): Promise<TPageFolder> {
const response = await fetch(`${API_BASE_URL}/api/workspaces/${workspaceSlug}/projects/${projectId}/page-folders`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(folderData),
});

if (!response.ok) {
throw new Error("Failed to create page folder");
}

return response.json();
}

async update(
workspaceSlug: string,
projectId: string,
folderId: string,
folderData: Partial<TPageFolder>
): Promise<TPageFolder> {
const response = await fetch(
`${API_BASE_URL}/api/workspaces/${workspaceSlug}/projects/${projectId}/page-folders/${folderId}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(folderData),
}
);

if (!response.ok) {
throw new Error("Failed to update page folder");
}

return response.json();
}

async remove(workspaceSlug: string, projectId: string, folderId: string): Promise<void> {
const response = await fetch(
`${API_BASE_URL}/api/workspaces/${workspaceSlug}/projects/${projectId}/page-folders/${folderId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);

if (!response.ok) {
throw new Error("Failed to delete page folder");
}
}

async addPageToFolder(workspaceSlug: string, projectId: string, folderId: string, pageId: string): Promise<void> {
const response = await fetch(
`${API_BASE_URL}/api/workspaces/${workspaceSlug}/projects/${projectId}/page-folders/${folderId}/pages/${pageId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
}
);

if (!response.ok) {
throw new Error("Failed to add page to folder");
}
}

async removePageFromFolder(
workspaceSlug: string,
projectId: string,
folderId: string,
pageId: string
): Promise<void> {
const response = await fetch(
`${API_BASE_URL}/api/workspaces/${workspaceSlug}/projects/${projectId}/page-folders/${folderId}/pages/${pageId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
}
);

if (!response.ok) {
throw new Error("Failed to remove page from folder");
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Add authentication headers and improve error handling.

The service methods are missing authentication headers that are likely required for API calls, and error messages could be more descriptive.

Consider these improvements:

  1. Add authentication headers:
      headers: {
        "Content-Type": "application/json",
+       "Authorization": `Bearer ${token}`, // or however auth is handled
      },
  1. Improve error handling with more specific error messages:
    if (!response.ok) {
-     throw new Error("Failed to fetch page folders");
+     const errorData = await response.json().catch(() => ({}));
+     throw new Error(errorData.message || `Failed to fetch page folders (${response.status})`);
    }

Let me check how authentication is typically handled in this codebase:


🏁 Script executed:

#!/bin/bash
# Description: Check how other services handle authentication headers

# Search for existing service files to see auth patterns
rg -A 5 -B 5 "Authorization|Bearer|headers.*:" web/core/services/

# Look for auth helper functions
rg "auth|token" web/helpers/

Length of output: 12427


#!/bin/bash

Show common API_BASE_URL helper

if [ -f web/helpers/common.helper.ts ]; then
sed -n '1,200p' web/helpers/common.helper.ts
else
echo "common.helper.ts not found"
fi

echo -e "\n---\n"

Show authentication helper to see if there's a function to get auth headers

if [ -f web/helpers/authentication.helper.tsx ]; then
sed -n '1,200p' web/helpers/authentication.helper.tsx
else
echo "authentication.helper.tsx not found"
fi```


🏁 Script executed:

#!/bin/bash
# Inspect how workspace.service sets up HTTP client and auth
sed -n '1,50p' web/core/services/workspace.service.ts

# Locate BaseService definition if it exists
rg -l "class .*Service" -n web/core/services | grep base.service.ts || echo "No base.service.ts in services"

# If BaseService exists, show its content
if [ -f web/core/services/base.service.ts ]; then
  sed -n '1,200p' web/core/services/base.service.ts
fi

Length of output: 1607


🏁 Script executed:

#!/bin/bash
# Locate APIService implementation
file=$(rg -l "class APIService" -n . | head -n1)
echo "APIService found at: $file"
echo "-----"
sed -n '1,200p' "$file"

Length of output: 4277


Unify PageFolderService with APIService for consistent authentication and error handling

PageFolderService currently uses the native fetch API, bypassing our centralized axios instance (APIService) that automatically includes credentials, intercepts 401s, and normalizes errors. To align with the rest of the codebase, refactor this class to extend APIService:

• Change the class signature and constructor:

-export class PageFolderService {
+export class PageFolderService extends APIService {
+  constructor() {
+    super(API_BASE_URL);
+  }

• Replace each fetch call with the corresponding axios helper, e.g.:

-  async fetchAll(ws: string, proj: string): Promise<TPageFolder[]> {
-    const response = await fetch(`${API_BASE_URL}/api/workspaces/${ws}/projects/${proj}/page-folders`, {
-      method: "GET",
-      headers: { "Content-Type": "application/json" },
-    });
-    if (!response.ok) {
-      const err = await response.json().catch(() => ({}));
-      throw new Error(err.message || `Failed to fetch page folders (${response.status})`);
-    }
-    return response.json();
-  }
+  async fetchAll(workspaceSlug: string, projectId: string): Promise<TPageFolder[]> {
+    return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/page-folders/`)
+      .then((res) => res.data)
+      .catch((error) => {
+        throw error?.response?.data;
+      });
+  }

• Apply the same pattern for fetchById, create, update, remove, addPageToFolder, removePageFromFolder.

This ensures:

  1. withCredentials: true is applied automatically.
  2. 401 responses redirect via the interceptor.
  3. Error payloads are thrown as error.response.data for consistency.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In web/core/services/page-folder.ts lines 1 to 130, refactor PageFolderService
to extend the centralized APIService class instead of using native fetch calls.
Replace all fetch calls with the corresponding axios methods provided by
APIService, ensuring requests automatically include credentials and benefit from
interceptors handling 401 responses and error normalization. Update the class
signature to extend APIService and remove manual headers and error handling,
relying on APIService's built-in mechanisms to throw error.response.data for
consistency across all methods including fetchAll, fetchById, create, update,
remove, addPageToFolder, and removePageFromFolder.

Comment on lines +73 to +76
get isAnyFolderAvailable() {
if (this.loader) return true;
return Object.keys(this.data).length > 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Misleading computed property logic

The isAnyFolderAvailable property returns true when the loader is active, which contradicts its name. This could cause UI components to incorrectly assume folders are available during loading states.

  get isAnyFolderAvailable() {
-    if (this.loader) return true;
-    return Object.keys(this.data).length > 0;
+    return Object.keys(this.data).length > 0;
  }

If you need to check loading state separately, consider adding a dedicated computed property:

get isLoading() {
  return this.loader !== undefined;
}
🤖 Prompt for AI Agents
In web/core/store/pages/page-folder.store.ts around lines 73 to 76, the computed
property isAnyFolderAvailable incorrectly returns true when the loader is
active, which misleads consumers into thinking folders are available during
loading. Modify isAnyFolderAvailable to only check if this.data has keys and
does not consider the loader state. Add a separate computed property isLoading
that returns true when the loader is active to clearly distinguish loading state
from folder availability.

Comment on lines +226 to +245
removeFolder = async (folderId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !folderId) return undefined;

await this.service.remove(workspaceSlug, projectId, folderId);
runInAction(() => {
unset(this.data, [folderId]);
});
} catch (error) {
runInAction(() => {
this.loader = undefined;
this.error = {
title: "Failed",
description: "Failed to delete a folder, Please try again later.",
};
});
throw error;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add loader state management and fix return type

The removeFolder method lacks loader state management unlike other async methods, and has an inconsistent return type.

  removeFolder = async (folderId: string) => {
    try {
      const { workspaceSlug, projectId } = this.store.router;
-      if (!workspaceSlug || !projectId || !folderId) return undefined;
+      if (!workspaceSlug || !projectId || !folderId) return;
+
+      runInAction(() => {
+        this.loader = "mutation-loader";
+        this.error = undefined;
+      });

      await this.service.remove(workspaceSlug, projectId, folderId);
      runInAction(() => {
        unset(this.data, [folderId]);
+        this.loader = undefined;
      });
    } catch (error) {
      runInAction(() => {
        this.loader = undefined;
        this.error = {
          title: "Failed",
          description: "Failed to delete a folder, Please try again later.",
        };
      });
      throw error;
    }
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
removeFolder = async (folderId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !folderId) return undefined;
await this.service.remove(workspaceSlug, projectId, folderId);
runInAction(() => {
unset(this.data, [folderId]);
});
} catch (error) {
runInAction(() => {
this.loader = undefined;
this.error = {
title: "Failed",
description: "Failed to delete a folder, Please try again later.",
};
});
throw error;
}
};
removeFolder = async (folderId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !folderId) return;
runInAction(() => {
this.loader = "mutation-loader";
this.error = undefined;
});
await this.service.remove(workspaceSlug, projectId, folderId);
runInAction(() => {
unset(this.data, [folderId]);
this.loader = undefined;
});
} catch (error) {
runInAction(() => {
this.loader = undefined;
this.error = {
title: "Failed",
description: "Failed to delete a folder, Please try again later.",
};
});
throw error;
}
};
🤖 Prompt for AI Agents
In web/core/store/pages/page-folder.store.ts around lines 226 to 245, the
removeFolder method is missing loader state management and has an inconsistent
return type. Add setting this.loader to true at the start of the method and
reset it to undefined in both success and error paths to manage loading state
properly. Also, ensure the method consistently returns a value or undefined by
adding an explicit return statement after successful removal or when early
returning due to missing parameters.

Comment on lines +252 to +272
addPageToFolder = async (folderId: string, pageId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !folderId || !pageId) return undefined;

await this.service.addPageToFolder(workspaceSlug, projectId, folderId, pageId);
runInAction(() => {
const folder = this.data[folderId];
if (folder && folder.page_ids) {
if (!folder.page_ids.includes(pageId)) {
folder.page_ids.push(pageId);
}
} else if (folder) {
folder.page_ids = [pageId];
}
});
} catch (error) {
console.error("Failed to add page to folder", error);
throw error;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add proper state management and error handling

The method lacks loader/error state management and uses console.error which may not be appropriate for production.

  addPageToFolder = async (folderId: string, pageId: string) => {
    try {
      const { workspaceSlug, projectId } = this.store.router;
-      if (!workspaceSlug || !projectId || !folderId || !pageId) return undefined;
+      if (!workspaceSlug || !projectId || !folderId || !pageId) return;
+
+      runInAction(() => {
+        this.loader = "mutation-loader";
+        this.error = undefined;
+      });

      await this.service.addPageToFolder(workspaceSlug, projectId, folderId, pageId);
      runInAction(() => {
        const folder = this.data[folderId];
        if (folder && folder.page_ids) {
          if (!folder.page_ids.includes(pageId)) {
            folder.page_ids.push(pageId);
          }
        } else if (folder) {
          folder.page_ids = [pageId];
        }
+        this.loader = undefined;
      });
    } catch (error) {
-      console.error("Failed to add page to folder", error);
+      runInAction(() => {
+        this.loader = undefined;
+        this.error = {
+          title: "Failed",
+          description: "Failed to add page to folder, Please try again later.",
+        };
+      });
      throw error;
    }
  };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In web/core/store/pages/page-folder.store.ts around lines 252 to 272, the
addPageToFolder method lacks proper loader and error state management and uses
console.error for error logging, which is not suitable for production. Introduce
observable state variables to track loading and error states, set the loading
state to true at the start and false at the end of the operation, and update the
error state appropriately on failure. Replace console.error with a proper
logging mechanism or error handling approach consistent with the application's
standards.

Comment on lines +279 to +295
removePageFromFolder = async (folderId: string, pageId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !folderId || !pageId) return undefined;

await this.service.removePageFromFolder(workspaceSlug, projectId, folderId, pageId);
runInAction(() => {
const folder = this.data[folderId];
if (folder && folder.page_ids) {
folder.page_ids = folder.page_ids.filter((id) => id !== pageId);
}
});
} catch (error) {
console.error("Failed to remove page from folder", error);
throw error;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add proper state management and error handling

Similar to addPageToFolder, this method lacks loader/error state management and uses console.error.

  removePageFromFolder = async (folderId: string, pageId: string) => {
    try {
      const { workspaceSlug, projectId } = this.store.router;
-      if (!workspaceSlug || !projectId || !folderId || !pageId) return undefined;
+      if (!workspaceSlug || !projectId || !folderId || !pageId) return;
+
+      runInAction(() => {
+        this.loader = "mutation-loader";
+        this.error = undefined;
+      });

      await this.service.removePageFromFolder(workspaceSlug, projectId, folderId, pageId);
      runInAction(() => {
        const folder = this.data[folderId];
        if (folder && folder.page_ids) {
          folder.page_ids = folder.page_ids.filter((id) => id !== pageId);
        }
+        this.loader = undefined;
      });
    } catch (error) {
-      console.error("Failed to remove page from folder", error);
+      runInAction(() => {
+        this.loader = undefined;
+        this.error = {
+          title: "Failed",
+          description: "Failed to remove page from folder, Please try again later.",
+        };
+      });
      throw error;
    }
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
removePageFromFolder = async (folderId: string, pageId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !folderId || !pageId) return undefined;
await this.service.removePageFromFolder(workspaceSlug, projectId, folderId, pageId);
runInAction(() => {
const folder = this.data[folderId];
if (folder && folder.page_ids) {
folder.page_ids = folder.page_ids.filter((id) => id !== pageId);
}
});
} catch (error) {
console.error("Failed to remove page from folder", error);
throw error;
}
};
removePageFromFolder = async (folderId: string, pageId: string) => {
try {
const { workspaceSlug, projectId } = this.store.router;
if (!workspaceSlug || !projectId || !folderId || !pageId) return;
runInAction(() => {
this.loader = "mutation-loader";
this.error = undefined;
});
await this.service.removePageFromFolder(workspaceSlug, projectId, folderId, pageId);
runInAction(() => {
const folder = this.data[folderId];
if (folder && folder.page_ids) {
folder.page_ids = folder.page_ids.filter((id) => id !== pageId);
}
this.loader = undefined;
});
} catch (error) {
runInAction(() => {
this.loader = undefined;
this.error = {
title: "Failed",
description: "Failed to remove page from folder, Please try again later.",
};
});
throw error;
}
};
🤖 Prompt for AI Agents
In web/core/store/pages/page-folder.store.ts around lines 279 to 295, the
removePageFromFolder method lacks proper loader and error state management and
uses console.error for error logging. Refactor the method to set a loading state
before the async call and clear it afterward, handle errors by updating an error
state instead of console.error, and ensure the UI can react to these state
changes consistently like in addPageToFolder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants