Resources
Key capabilities:
- Folder tree with drag‑and‑drop
- File upload & download
- Per‑folder/file ACL
- Trash + restore (soft delete + hard delete)
- Search across folders/files
- Usage tracking (file views)
The UX is built around a left folder tree and a main content panel:
- Tree and global state live in
ResourcesRoot. - The main file/folder view lives in
Resources(module component). - Data and mutations are driven by
internalResources.tsand React Query hooks inservices/reactQuery/internalResourceQuery.ts.
System Overview Diagram
Section titled “System Overview Diagram”ResourcesRoot │ ├── ResourceSidebar (search, root folder, trash) ├── TreeListBuilder (folder tree + drag-and-drop) └── Resources (main panel)
Resources (module.tsx) │ ├── useInternalResources.* (React Query hooks) │ │ │ └── internalResources.ts │ │ │ └── /internal-resources API │ └── Child components ├── ResourceTabView ├── ResourceFileView ├── ShareModal └── ViewStatsModalHigh‑Level Architecture
Section titled “High‑Level Architecture”- External resources API (
src/services/resources.ts)- Provides a path‑based API for
/resources, used for legacy/public resources and folder access configs.
- Provides a path‑based API for
- Internal resources API (
src/services/internalResources.ts)- Provides ID‑based APIs under
/internal-resources/**and/search/resources. - Powers the Resources module UI via React Query hooks (
useInternalResources).
- Provides ID‑based APIs under
- Module shell & tree
ResourcesRoot.tsx– root layout, tree sidebar, URL/breadcrumb handling, drag‑and‑drop, and trash integration.module.tsx(exported asResources) – main content/viewer, uploads, deletions, trash display.- Hooks in
src/modules/Resources/hooks/*split responsibilities (data, operations, URL, breadcrumbs, tree, state).
Core Types & Data Model
Section titled “Core Types & Data Model”Internal Resources (Folders & Files)
Section titled “Internal Resources (Folders & Files)”Defined in src/services/internalResources.ts:
-
Folder (
IRFolder)id: numbername: stringparentFolderId: number | nullhasChildren?: number | boolean– 1/true means folder has (or may have) children.orderIndex?: number/order_index?: numberfiles?: IRFile[]childFolders?: IRFolder[]filesPagination?– pagination metadata for files (page, limit, total, totalPages, hasNextPage, hasPrevPage).
-
File (
IRFile)id: numbername: stringfolderId: number | null
-
Folder contents (
ContentsResponse)success: booleanmessage: stringdata:folders: IRFolder[]files?: IRFile[]filesPagination?: { page, limit, total, totalPages, hasNextPage, hasPrevPage }
-
Search results
SearchResponse– top‑level structure from/search/resources.data.totaldata.sections: SearchSection[]
SearchSection:module: "resource_folders" | "resource_files"total: numberitems: { id: number; title: string; ... }[]
SearchContentsResponse(UI‑friendly):data: { folders: IRFolder[]; files: IRFile[] }- Built by
searchResources(see below).
ACL (Access Control) – Internal Resources
Section titled “ACL (Access Control) – Internal Resources”At the internal resources layer:
-
PrincipalType = "user" | "group" -
AccessType = "read" -
EffectType = "allow" | "deny" -
ACLRule
principalType: PrincipalTypeprincipalId: numberaccess: AccessTypeeffect: EffectType
-
ACLConfig
inherit: boolean– whether to inherit from parent.rules: ACLRule[]
-
ACLResponse
success: booleandata?: ACLConfigerror?: string
These are used by the ACL endpoints:
getFolderACL(folderId)updateFolderACL(folderId, acl)getFileACL(fileId)updateFileACL(fileId, acl)
Access Config – External Resources
Section titled “Access Config – External Resources”Separate from internal ACL, src/types/Resources.ts defines a path‑based access config (used with the /resources/access-config endpoints):
-
AllowedUser
userId: numberuserName: string
-
AllowedGroup
groupId: numbergroupName: string
-
DenyUser
userId: numberuserName: string
-
ResourceAccessConfig
allowedUsers?: AllowedUser[]allowedGroups?: AllowedGroup[]inheritFromParent?: booleandenyUsers?: DenyUser[]
Validated via ResourceAccessConfigSchema (Zod).
Resource Module Configs
Section titled “Resource Module Configs”src/modules/Resources/config.ts:
-
ResourceModuleConfig
key: string– route/module key.root: string– root folder path segment used for this sub‑module.title: string– page title.label: string– sidebar label.
-
RESOURCE_MODULES- Predefined modules, e.g.:
standard-operating-procedures/standard_operating_proceduresmarketing-material/marketing_materialfull-team-manual,franchisee-manual,internal-forms,client-forms.
- Predefined modules, e.g.:
External Resources API (src/services/resources.ts)
Section titled “External Resources API (src/services/resources.ts)”These are path‑based endpoints (legacy/public resources and folder access config).
-
Get all resources (optionally in a folder path)
getAllResources(path?: string)- Endpoint:
GET /resources?folderPath={path}(query param optional). - Returns:
Resource[](from@/modules/Resources/utils/types).
-
Create folder by path
createNewFolder(path: string)- Endpoint:
POST /resources/folders - Body:
{ folderPath: path }
-
Delete folder by path
deleteFolder(path: string, force?: boolean)- Endpoint:
DELETE /resources/folders?folderPath={path}&force=1? force=1for forced deletion.
-
List folders by parent path
getAllFolders(path?: string)- Endpoint:
GET /resources/folders?parentPath={path}. - Returns:
Folder[](fromutils/types).
-
Save folder order
saveFolderOrder(order: SortOrder)- Endpoint:
POST /resources/folders/reorder - Payload:
SortOrder(fromutils/types).
-
Delete resource by ID
deleteResourceById(id: number)- Endpoint:
DELETE /resources/{id}
-
Upload resource to a path
uploadResource(path: string, file: File)- Endpoint:
POST /resources - Body:
FormDatawith:files(file)folderPath(string)
-
Get folder access config
getAccessConfig(folderPath: string): Promise<ResourceAccessConfig | undefined>- Endpoint:
GET /resources/access-config?folderPath={path}(viaResourceURL). - Parses response via
ResourceAccessConfigSchema.
-
Save folder access config
saveAccessConfig(folderPath: string, config: ResourceAccessConfig)- Endpoint:
PUT /resources/access-config?folderPath={path} - Body:
{ config }.
Internal Resources API (src/services/internalResources.ts)
Section titled “Internal Resources API (src/services/internalResources.ts)”Folders & Files
Section titled “Folders & Files”-
List folders
listFolders(parentId: number | null): Promise<IRFolder[]>- Endpoint:
GET /internal-resources/folders?parentId={parentId}(or no param for root).
-
List files in a folder
listFiles(folderId: number): Promise<IRFile[]>- Endpoint:
GET /internal-resources/folders/{folderId}/files.
-
Create folder
createFolder(name: string, parentFolderId: number | null, isPublic = true)- Endpoint:
POST /internal-resources/folders. - Body:
{ name, parentFolderId, isPublic }. - Returns
{ success, id?, status?, error? }.
-
Update folder
updateFolder(folderId, name?, hasChildren?, parentFolderId?)- Endpoint:
PATCH /internal-resources/folders/{folderId} - Body: combination of:
name?: stringhasChildren?: 1 | 0(boolean normalised to number)parentFolderId?: number | null
-
Soft delete folder
deleteFolder(folderId)- Endpoint:
DELETE /internal-resources/folders/{folderId}
-
Hard delete folder
permanentlyDeleteFolder(folderId)- Endpoint:
DELETE /internal-resources/folders/{folderId}/permanent
-
Update folder order
updateFolderOrder(parentFolderId: number | null, folderIds: number[])- Endpoint:
PATCH /internal-resources/folders/{parentIdOr0}/order- Root uses
0whenparentFolderId === null.
- Body:
{ folders: folderIds }.
-
Upload file
uploadFile(folderId: number, file: File)- Endpoint:
POST /internal-resources/folders/{folderId}/files - Body:
FormDatawithfiles. - Returns
{ success, id?, status?, error? }(first uploaded file).
-
Update file
updateFile(fileId: number, name?: string)- Endpoint:
PATCH /internal-resources/files/{fileId} - Body:
{ name?: string }.
-
Delete file / folder
- Soft delete
- Files:
DELETE /internal-resources/files/{id} - Folders:
DELETE /internal-resources/folders/{id}
- Files:
- Hard delete
- Files:
DELETE /internal-resources/files/{id}/permanent - Folders:
DELETE /internal-resources/folders/{id}/permanent
- Files:
- Soft delete
-
Move file
moveFile(fileId: number, folderId: number | null)- Endpoint:
PATCH /internal-resources/files/{fileId}/move - Body:
{ folderId: number | null }.
Folder Contents & All Contents
Section titled “Folder Contents & All Contents”-
Get folder contents
getFolderContents(folderId: number, opts?: { page?: number; limit?: number }): Promise<ContentsResponse | null>- Endpoint:
GET /internal-resources/folders/{folderId}/contents?page=&limit=. - Returns
ContentsResponse.
-
Get all root contents
getAllContents(): Promise<ContentsResponse | null>- Endpoint:
GET /internal-resources/contents. - Used for root tree & initial load.
Search
Section titled “Search”- Search resources
searchResources(query: string): Promise<SearchContentsResponse | null>- Endpoint:
GET /search/resources?q={query} - Internally:
- Groups sections by
resource_foldersandresource_files. - Builds a
foldersMapkeyed by folder id.- For
resource_foldersitems: createsIRFolderwithid,name,parentFolderId,hasChildren. - For
resource_filesitems:- Adds
IRFiletofiles. - Ensures folder entries exist for
folderId(even if not present in folder section), optionally tracking names to backfill.
- Adds
- For
- For folders with missing names, recursively loads from API:
- Calls
listFolders(null)for roots. - Uses
getFolderContentsrecursively to search for matching folders.
- Calls
- Groups files by
folderIdand attaches to folders.
- Groups sections by
- Returns
{ success, message, data: { folders, files } }.
ACL & Views
Section titled “ACL & Views”-
Folder ACL
getFolderACL(folderId: number): Promise<ACLResponse>updateFolderACL(folderId: number, acl: ACLConfig)
-
File ACL
getFileACL(fileId: number): Promise<ACLResponse>updateFileACL(fileId: number, acl: ACLConfig)
-
File views – tracking & stats
trackFileView(fileId: number)POST /internal-resources/files/{fileId}/views
getFileViews(fileId: number)GET /internal-resources/files/{fileId}/views
Trash (Soft Deleted Items)
Section titled “Trash (Soft Deleted Items)”-
Get trash contents
getTrash(opts?: { page?: number; limit?: number })- Endpoint:
GET /internal-resources/trash?page=&limit= - Returns folders and files plus combined pagination/total stats.
-
Restore file/folder from trash
restoreFile(fileId: number)→POST /internal-resources/files/{fileId}/restorerestoreFolder(folderId: number)→POST /internal-resources/folders/{folderId}/restore
When to Use Each API
Section titled “When to Use Each API”Use internalResources.ts when:
- Working inside the Resources module (tree, main panel, trash).
- Accessing folders/files by ID (
folderId,fileId). - Managing ACL for internal folders/files.
- Working with trash, search, or view statistics.
Use resources.ts when:
- Working with path‑based folders (
folderPath,parentPath). - Managing public/legacy resources outside of the internal resources tree.
- Managing folder access config via
/resources/access-configusingResourceAccessConfig.
Typical Flows
Section titled “Typical Flows”Example: Uploading a File
Section titled “Example: Uploading a File”User drops or selects file in Resources UI ↓Resources.module.ts (handleUploadFile / handleUploadFiles) ↓useInternalResources.uploadFile(folderId) ↓POST /internal-resources/folders/{folderId}/files ↓React Query refetches / invalidates: ["internal-resources", "contents", folderId] ["internal-resources", "files", folderId]Example: Moving a Folder
Section titled “Example: Moving a Folder”User drags folder in TreeListBuilder ↓ResourcesRoot.handleTreeMove() ↓parseFolderId(...) → validates move (no circular refs, not into self) ↓updateFolderMut({ folderId, parentFolderId: toParentId }) ↓PATCH /internal-resources/folders/{folderId} ↓Tree and URL/breadcrumbs updated via useResourcesFolderToggle / useResourcesBreadcrumbsExample: Soft Deleting a File
Section titled “Example: Soft Deleting a File”User clicks "Delete" in ResourceFileView ↓Resources.handleDelete(fileId) ↓useInternalResources.deleteFile().mutateAsync(fileId) ↓DELETE /internal-resources/files/{fileId} ↓React Query invalidates: ["internal-resources", "trash"] ["internal-resources", "contents", folderId] ["internal-resources", "contents", null] ["internal-resources", "files", folderId] ↓Parent notified via onFolderContentsChanged(folderId) to update tree caretFile Structure (Resources Module)
Section titled “File Structure (Resources Module)”src/modules/Resources│├── ResourcesRoot.tsx // Shell: layout, tree, URL, breadcrumbs, trash, DnD├── module.tsx // Main panel: uploads, viewer, trash, list/grid├── index.ts // Legacy├── config.ts // Legacy│├── hooks│ ├── useResourcesState.ts│ ├── useResourcesUrlState.ts│ ├── useResourcesData.ts│ ├── useResourcesOperations.ts│ ├── useResourcesTree.ts│ ├── useResourcesBreadcrumbs.ts│ └── useResourcesFolderToggle.ts│├── components│ ├── ResourceSidebar.tsx│ ├── ResourceTabView.tsx│ ├── ResourceFileView.tsx│ ├── ResourceFileCreator.tsx│ ├── ResourceAccessForm.tsx│ ├── ResourcePropertiesModal.tsx│ ├── ViewStatsModal.tsx│ ├── ShareModal.tsx│ ├── ResourcesPdfRender.tsx│ ├── ResourceFolderSorter.tsx│ └── ResourceDragAndDropList.tsx│└── styles ├── ResourcesRoot.module.css ├── resources.module.css ├── resourcesidebar.module.css ├── resourcetabview.module.css ├── resourceFileView.module.css ├── ResourceDragAndDropList.module.css ├── resourcemainview.module.css ├── resourcemobileview.module.css ├── resourcesorter.module.css ├── viewstats.module.css └── resourcefileselector.module.cssReact Query Layer (useInternalResources hooks)
Section titled “React Query Layer (useInternalResources hooks)”The module primarily uses hooks from src/services/reactQuery/internalResourceQuery.ts such as:
- Queries:
useInternalResources.contents(folderId, page, limit, enabled)useInternalResources.trash(page, limit)
- Mutations:
useInternalResources.uploadFile(folderId)useInternalResources.deleteFile()useInternalResources.permanentlyDeleteFile()useInternalResources.permanentlyDeleteFolder()useInternalResources.restoreFile()useInternalResources.restoreFolder()useInternalResources.createFolder()useInternalResources.updateFolder()useInternalResources.updateFolderOrder()useInternalResources.updateFile()useInternalResources.updateFolderACL()
These wrap the internal resources service functions and standardise query keys such as:
["internal-resources", "contents", folderId]["internal-resources", "files", folderId]["internal-resources", "trash"]["internal-resources", "search", query]
When adding new features, reuse these keys to keep cache invalidation consistent.
Module UI – ResourcesRoot
Section titled “Module UI – ResourcesRoot”File: src/modules/Resources/ResourcesRoot.tsx.
Responsibilities:
- Integrates module into the global layout:
- Collapses the main app sidebar (
sidebar_collapselocalStorage). - Responsive left sidebar (mobile overlay vs fixed).
- Collapses the main app sidebar (
- Owns global state for:
- Selected folder/file, breadcrumbs, expanded folder ids, renaming/properties/access modals.
- Controls URL state and deep‑linking:
- Uses
useResourcesUrlStateto read/writeurlPath(folder chain) andurlFileId. - Expands folders to match URL and sets selection/breadcrumbs on mount.
- Uses
- Drives tree data:
- Uses
useResourcesDatato:- Fetch root contents / search results / expanded folder contents.
- Maintain
contentsByFolderId,hasMoreFilesByFolderId,folderNameById,folderById.
- Uses
useResourcesTreeto constructtreeDataforTreeListBuilder. - Tracks
expandedFolderIds(internal “known contents”) separate from UI expansion.
- Uses
- Handles drag‑and‑drop:
handleTreeChange:- Detects root‑folder reordering and calls
updateFolderOrderMutwithparentFolderId: null.
- Detects root‑folder reordering and calls
handleTreeMove:- Distinguishes file vs folder move by ID prefix (
file-vsfolder-). - Validates moves:
- Files cannot be moved to root.
- Folders cannot be moved into themselves or descendants.
- Calls
moveFileMutorupdateFolderMut({ folderId, parentFolderId }).
- Distinguishes file vs folder move by ID prefix (
- Implements semi‑lazy loading:
- Tracks
processedFolderIdsto avoid re‑processing. - When a folder’s contents are loaded, auto‑expands children with
hasChildrenso their caret/state is ready. - When a folder is expanded in the tree, fetches contents and may recursively expand children with
hasChildren.
- Tracks
- Integrates trash view:
showTrashtoggles between normal view and a “Deleted Items” view (viaResourceswithshowTrash).- Stores previous folder & breadcrumbs to restore when exiting trash.
Key components it composes:
ResourceSidebar– search, root folder creation, “Trash” entry.TreeListBuilder– interactive tree UI usingtreeData.Resources– main panel (see below).ResourceFileCreator– modal for creating/renaming folders/files.ResourcePropertiesModal– folder/file properties + ACL launcher.ResourceAccessForm– ACL editing modal for folder/file.
Module UI – Resources (Main View)
Section titled “Module UI – Resources (Main View)”File: src/modules/Resources/module.tsx (also re‑exported from index.ts).
Responsibilities:
-
Fetching & paging:
- Uses
useInternalResources.contents(folderId, filesPage, FILES_PAGE_SIZE, !showTrash)to load:data.folders(IRFolder[])data.files(IRFile[])filesPagination/pagination.
- Uses
useInternalResources.trash(trashPage, FILES_PAGE_SIZE)whenshowTrashis true.
- Uses
-
Soft delete vs trash:
- Normal view:
handleDeletecallsdeleteFileMut(fileId)after confirm.- Invalidates:
["internal-resources", "trash"]["internal-resources", "contents", folderId]["internal-resources", "contents", null]["internal-resources", "files", folderId]
- Notifies parent via
onFolderContentsChangedto update carets.
- Trash view:
- Presents trash folders/files with pagination.
handleRestoreFile,handleRestoreFolder– call restore mutations and refetch trash + main folder contents.handlePermanentlyDeleteFile,handlePermanentlyDeleteFolder– confirm irreversible, then call hard‑delete mutations and refetch trash.
- Normal view:
-
Uploads:
- Single file upload:
handleUploadFile(file)- Enforces 50MB per file.
- Calls
uploadFileMut(file)with toast messaging. - Refetches
contentsandfilesqueries for the current folder.
- Multiple files:
handleUploadFiles(files[]):- Rejects files > 50MB and total batch > 500MB (500MB limit).
- Shows per‑file error toasts for invalid files.
- Uploads valid files in parallel.
- Shows aggregate success/warning/error messages.
- Refetches folder queries when at least one upload succeeds.
- Single file upload:
-
Rendering:
- Wraps content in
ResourceTabView, which:- Provides title, trash button, breadcrumb bar, and search/sort UI.
- Calls back with
(query, _, __, sortOrder)to render file/folder list or viewer.
- Normal view:
- Filters and sorts
irFiles/irFoldersby search and sort order. - Ensures selected
fileIdis included even if filtered out or not yet loaded. - Builds
fileItemswith download URLs (/internal-resources/files/{id}/download). - Shows:
ResourceFileViewas a file viewer when a file is selected.ResourceFileViewas a file/folder grid with optional dropzone otherwise.FileDropzoneempty state when no folders or files.
- Filters and sorts
- Trash view:
- Renders
ResourceFileViewwith:showTrash={true}onRestoreFile,onRestoreFolderonPermanentlyDeleteFile,onPermanentlyDeleteFolderonExitTrash
- Renders
- Wraps content in
-
Sharing:
- Manages a
ShareModal:handleShare(fileId)opens the modal for that file.ShareModalusesfileIdand file name to build sharing links/metadata.
- Manages a
Operations & State Hooks
Section titled “Operations & State Hooks”useResourcesData
Section titled “useResourcesData”File: src/modules/Resources/hooks/useResourcesData.ts.
Responsibilities:
-
Root content and search:
rootContentsQuery–getAllContents()when no search query.searchResultsQuery–searchResources(searchQuery)when searching.rootFolders:- When searching: uses
searchResultsQuery.data.data.folders. - Otherwise:
rootContentsQuery.data.data.folders.
- When searching: uses
-
Expanded folder contents (for tree):
- Tracks
treeFilePagesByFolderIdto support multiple pages of files in tree display. - Uses
useQueriesoverexpandedFolderIds:- For each folder id, calls
getFolderContents(folderId, { page })up to current page. - Merges folders/files from pages into a single
folders/filesarray. - Computes
treeHasMoreFilesbased onhasNextPageor page size.
- For each folder id, calls
- Produces:
contentsByFolderId[folderId] = { folders, files }hasMoreFilesByFolderId[folderId] = boolean
- Tracks
-
Properties modal support:
propertiesFolderContentsQuery:getFolderContents(propertiesFolder.id)for the folder being inspected, for counts.
-
Folder lookup helpers:
folderNameById:- Recursively walks
rootFoldersandcontentsByFolderIdto build name map.
- Recursively walks
folderById:- Same, but storing
IRFolderobjects.
- Same, but storing
Exports:
queryClient,rootContentsQuery,searchResultsQueryrootFolderscontentsQueries,contentsByFolderIdhasMoreFilesByFolderId,loadMoreFolderFilespropertiesFolderContentsQueryfolderNameById,folderByIdexpandedIdsArray
useResourcesOperations
Section titled “useResourcesOperations”File: src/modules/Resources/hooks/useResourcesOperations.ts.
Responsibilities:
-
File upload from tree context:
handleAddFile(folder):- Opens a hidden
<input type="file" multiple>. - Validates per‑file and batch size limits.
- Calls
uploadFile(folder.id, file)(service, not React Query). - Sets
hasChildrenByFolderId[folder.id] = trueto show caret. - Refetches
["internal-resources", "contents", folder.id]. - If folder is root, invalidates
["internal-resources", "contents", null].
- Opens a hidden
-
Folder creation:
handleCreateFolder(folderName):- Admin‑only; rejects non‑admins.
- Uses
creatingRootFolder+selectedFolderto infer root vs subfolder. - Calls
createFolderMut({ name, parentFolderId, isPublic: true }). - If admin and
userIdis known, sets default ACL:inherit: true, with a read allow rule for the creator.
- For subfolders:
- Marks parent in
hasChildrenByFolderId. - Adds parent to
expandedFolderIds. - Fetches parent contents via
getFolderContents. - Invalidates parent container’s contents so that hasChildren is recalculated.
- Marks parent in
- For root folders:
- Invalidates the root contents query.
-
Rename folder
handleRenameFolder(newName):- Admin‑only.
- Calls
updateFolderMut({ folderId, name }). - Invalidates:
["internal-resources", "contents"](all folder contents queries).["internal-resources", "search"](all search results).
- If renamed folder is currently selected, updates
selectedFolder.
-
Rename file
handleRenameFile(newName):- Admin‑only.
- Calls
updateFileMut({ fileId, name }). - Invalidates:
["internal-resources", "contents"]["internal-resources", "search"]
-
Soft delete folder
handleDeleteFolder(folder):- Confirms, then calls
deleteFolderMut(folder.id). - Invalidates
["internal-resources", "trash"]. - Clears contents cache for deleted folder.
- Invalidates parent’s contents.
- Removes folder from
expandedFolderIds. - Clears
hasChildrenByFolderId[folder.id].
- Confirms, then calls
Exports:
handleAddFilehandleCreateFolderhandleRenameFolderhandleRenameFilehandleDeleteFolder
Access, Search & Trash – UI Components
Section titled “Access, Search & Trash – UI Components”While not exhaustive, key components around the core flows:
-
Access control
ResourceAccessForm:- Opens for either
type="folder"ortype="file"withid. - Uses ACL APIs (
getFolderACL,updateFolderACL,getFileACL,updateFileACL).
- Opens for either
- Triggered from
ResourcePropertiesModalvia “Access” actions.
-
Search
- Sidebar search in
ResourceSidebarupdatessearchQueryinResourcesRoot. useResourcesDataautomatically switches from root contents tosearchResourceswhile searching.- Tree & main panel both reflect search results (folders/files).
- Sidebar search in
-
Trash
- “Trash” action in
ResourceSidebartogglesshowTrashinResourcesRoot. ResourcesinshowTrashmode:- Uses trash query instead of folder contents.
- Provides restore + hard delete in the UI.
- “Trash” action in
Implementation Notes & Gotchas
Section titled “Implementation Notes & Gotchas”-
Two resource APIs:
- The module UI is almost entirely driven by
internalResources.ts+useInternalResources. resources.ts+ResourceAccessConfigare a separate, path‑based API mostly for legacy/public resources and high‑level access config.- Be explicit about which API you’re extending to avoid mixing path‑based and ID‑based logic.
- The module UI is almost entirely driven by
-
Caching & invalidation:
- Always invalidate or refetch:
["internal-resources", "contents", folderId]when files/folders change in that folder.["internal-resources", "contents", null]when root folders change (create/delete root, move folder to/from root).["internal-resources", "trash"]when soft‑deleting/restoring items.["internal-resources", "search"]when something that affects search results changes (rename, create, delete).
- Always invalidate or refetch:
-
expandedFolderIdsvs UI expansion:expandedFolderIdsis used for data loading (tree contents), not just visual caret state.- When you want children to exist in
contentsByFolderIdand show carets, make sure to add folder ids here. - Do not aggressively remove items from
expandedFolderIdson UI collapse; otherwise, carets and children can disappear unexpectedly.
-
File size limits:
- Both
ResourcesanduseResourcesOperationsenforce:- 50MB per file.
- 500MB per batch.
- Reuse these constants for any new upload flows to keep behaviour consistent.
- Both
-
URL deep‑links:
ResourcesRootexpectsurlPathto be a chain of folder ids andurlFileIdas a file id.- When adding new entry points (e.g. directly opening a resource from another module), set these values via
useResourcesUrlStateso that the tree, breadcrumbs, and main view stay in sync.