Add shared file storage with folder tree, permissions, and file manager UI

New document hub for centralized file storage — replaces scattered
drives and USB sticks for non-technical SMBs. Three new tables:
storage_folder (nested hierarchy), storage_folder_permission (role
and user-level access control), storage_file.

Backend: folder CRUD with nested paths, file upload/download via
signed URLs, permission checks (view/edit/admin with inheritance
from parent folders), public/private toggle, breadcrumb navigation,
file search.

Frontend: two-panel file manager — collapsible folder tree on left,
icon grid view on right. Folder icons by type, file size display,
upload button, context menu for download/delete. Breadcrumb nav.
Files sidebar link added.
This commit is contained in:
Ryan Moon
2026-03-29 15:31:20 -05:00
parent d36c6f7135
commit 0f6cc104d2
13 changed files with 1093 additions and 1 deletions

View File

@@ -19,6 +19,7 @@ import { Route as AuthenticatedRolesIndexRouteImport } from './routes/_authentic
import { Route as AuthenticatedRepairsIndexRouteImport } from './routes/_authenticated/repairs/index'
import { Route as AuthenticatedRepairBatchesIndexRouteImport } from './routes/_authenticated/repair-batches/index'
import { Route as AuthenticatedMembersIndexRouteImport } from './routes/_authenticated/members/index'
import { Route as AuthenticatedFilesIndexRouteImport } from './routes/_authenticated/files/index'
import { Route as AuthenticatedAccountsIndexRouteImport } from './routes/_authenticated/accounts/index'
import { Route as AuthenticatedRolesNewRouteImport } from './routes/_authenticated/roles/new'
import { Route as AuthenticatedRolesRoleIdRouteImport } from './routes/_authenticated/roles/$roleId'
@@ -88,6 +89,11 @@ const AuthenticatedMembersIndexRoute =
path: '/members/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedFilesIndexRoute = AuthenticatedFilesIndexRouteImport.update({
id: '/files/',
path: '/files/',
getParentRoute: () => AuthenticatedRoute,
} as any)
const AuthenticatedAccountsIndexRoute =
AuthenticatedAccountsIndexRouteImport.update({
id: '/accounts/',
@@ -200,6 +206,7 @@ export interface FileRoutesByFullPath {
'/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts/': typeof AuthenticatedAccountsIndexRoute
'/files/': typeof AuthenticatedFilesIndexRoute
'/members/': typeof AuthenticatedMembersIndexRoute
'/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs/': typeof AuthenticatedRepairsIndexRoute
@@ -226,6 +233,7 @@ export interface FileRoutesByTo {
'/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/roles/new': typeof AuthenticatedRolesNewRoute
'/accounts': typeof AuthenticatedAccountsIndexRoute
'/files': typeof AuthenticatedFilesIndexRoute
'/members': typeof AuthenticatedMembersIndexRoute
'/repair-batches': typeof AuthenticatedRepairBatchesIndexRoute
'/repairs': typeof AuthenticatedRepairsIndexRoute
@@ -255,6 +263,7 @@ export interface FileRoutesById {
'/_authenticated/roles/$roleId': typeof AuthenticatedRolesRoleIdRoute
'/_authenticated/roles/new': typeof AuthenticatedRolesNewRoute
'/_authenticated/accounts/': typeof AuthenticatedAccountsIndexRoute
'/_authenticated/files/': typeof AuthenticatedFilesIndexRoute
'/_authenticated/members/': typeof AuthenticatedMembersIndexRoute
'/_authenticated/repair-batches/': typeof AuthenticatedRepairBatchesIndexRoute
'/_authenticated/repairs/': typeof AuthenticatedRepairsIndexRoute
@@ -284,6 +293,7 @@ export interface FileRouteTypes {
| '/roles/$roleId'
| '/roles/new'
| '/accounts/'
| '/files/'
| '/members/'
| '/repair-batches/'
| '/repairs/'
@@ -310,6 +320,7 @@ export interface FileRouteTypes {
| '/roles/$roleId'
| '/roles/new'
| '/accounts'
| '/files'
| '/members'
| '/repair-batches'
| '/repairs'
@@ -338,6 +349,7 @@ export interface FileRouteTypes {
| '/_authenticated/roles/$roleId'
| '/_authenticated/roles/new'
| '/_authenticated/accounts/'
| '/_authenticated/files/'
| '/_authenticated/members/'
| '/_authenticated/repair-batches/'
| '/_authenticated/repairs/'
@@ -426,6 +438,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AuthenticatedMembersIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/files/': {
id: '/_authenticated/files/'
path: '/files'
fullPath: '/files/'
preLoaderRoute: typeof AuthenticatedFilesIndexRouteImport
parentRoute: typeof AuthenticatedRoute
}
'/_authenticated/accounts/': {
id: '/_authenticated/accounts/'
path: '/accounts'
@@ -584,6 +603,7 @@ interface AuthenticatedRouteChildren {
AuthenticatedRolesRoleIdRoute: typeof AuthenticatedRolesRoleIdRoute
AuthenticatedRolesNewRoute: typeof AuthenticatedRolesNewRoute
AuthenticatedAccountsIndexRoute: typeof AuthenticatedAccountsIndexRoute
AuthenticatedFilesIndexRoute: typeof AuthenticatedFilesIndexRoute
AuthenticatedMembersIndexRoute: typeof AuthenticatedMembersIndexRoute
AuthenticatedRepairBatchesIndexRoute: typeof AuthenticatedRepairBatchesIndexRoute
AuthenticatedRepairsIndexRoute: typeof AuthenticatedRepairsIndexRoute
@@ -608,6 +628,7 @@ const AuthenticatedRouteChildren: AuthenticatedRouteChildren = {
AuthenticatedRolesRoleIdRoute: AuthenticatedRolesRoleIdRoute,
AuthenticatedRolesNewRoute: AuthenticatedRolesNewRoute,
AuthenticatedAccountsIndexRoute: AuthenticatedAccountsIndexRoute,
AuthenticatedFilesIndexRoute: AuthenticatedFilesIndexRoute,
AuthenticatedMembersIndexRoute: AuthenticatedMembersIndexRoute,
AuthenticatedRepairBatchesIndexRoute: AuthenticatedRepairBatchesIndexRoute,
AuthenticatedRepairsIndexRoute: AuthenticatedRepairsIndexRoute,