diff --git a/.github/workflows/preview-dashboard.yaml b/.github/workflows/preview-dashboard.yaml index 10cb1050c4..919c60baba 100644 --- a/.github/workflows/preview-dashboard.yaml +++ b/.github/workflows/preview-dashboard.yaml @@ -20,7 +20,7 @@ jobs: uses: rlespinasse/github-slug-action@v4 - uses: oven-sh/setup-bun@v1 with: - bun-version: "1.0.17" + bun-version: "1.0.18" - name: Install dependencies run: bun install --frozen-lockfile - name: 📤 Pull Vercel Environment Information diff --git a/.github/workflows/preview-website.yml b/.github/workflows/preview-website.yml index df8e72bf66..0d55bc5ec1 100644 --- a/.github/workflows/preview-website.yml +++ b/.github/workflows/preview-website.yml @@ -20,7 +20,7 @@ jobs: uses: rlespinasse/github-slug-action@v4 - uses: oven-sh/setup-bun@v1 with: - bun-version: "1.0.17" + bun-version: "1.0.18" - name: Install dependencies run: bun install --frozen-lockfile - name: 📤 Pull Vercel Environment Information diff --git a/.github/workflows/production-dashboard.yml b/.github/workflows/production-dashboard.yml index bb7f618c56..a343cbd3b8 100644 --- a/.github/workflows/production-dashboard.yml +++ b/.github/workflows/production-dashboard.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v1 with: - bun-version: "1.0.17" + bun-version: "1.0.18" - name: Install dependencies run: bun install --frozen-lockfile - name: 📤 Pull Vercel Environment Information diff --git a/.github/workflows/production-website.yml b/.github/workflows/production-website.yml index 8208cb25da..0e6b831469 100644 --- a/.github/workflows/production-website.yml +++ b/.github/workflows/production-website.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v1 with: - bun-version: "1.0.17" + bun-version: "1.0.18" - name: Install dependencies run: bun install --frozen-lockfile - name: 📤 Pull Vercel Environment Information diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 57c5a53f11..8f903fb5e9 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -22,9 +22,9 @@ "@midday/supabase": "workspace:*", "@midday/ui": "workspace:*", "@novu/headless": "^0.22.0", - "@trigger.dev/nextjs": "^2.3.5", + "@trigger.dev/nextjs": "^2.3.6", "@vercel/edge-config": "^0.4.1", - "@vercel/speed-insights": "^1.0.1", + "@vercel/speed-insights": "^1.0.2", "@vercel/toolbar": "^0.1.6", "@zip.js/zip.js": "2.7.32", "change-case": "^5.3.0", diff --git a/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx b/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx index e3416a36be..f34efa6a57 100644 --- a/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx +++ b/apps/dashboard/src/app/[locale]/@dashboard/(root)/vault/[[...folders]]/page.tsx @@ -1,7 +1,4 @@ -import { Breadcrumbs } from "@/components/breadcrumbs"; import { Table } from "@/components/tables/vault"; -import { CreateFolderButton } from "@/components/tables/vault/create-folder-button"; -import { UploadButton } from "@/components/tables/vault/upload-button"; import { Metadata } from "next"; export const metadata: Metadata = { @@ -13,17 +10,5 @@ export default function Vault({ params }) { params?.folders?.at(0) ); - return ( -
-
- - -
- - -
-
- - - ); + return
; } diff --git a/apps/dashboard/src/components/tables/vault/create-folder-button.tsx b/apps/dashboard/src/components/tables/vault/create-folder-button.tsx index f10d4f2daf..b2c6d8bfcf 100644 --- a/apps/dashboard/src/components/tables/vault/create-folder-button.tsx +++ b/apps/dashboard/src/components/tables/vault/create-folder-button.tsx @@ -1,13 +1,23 @@ +"use client"; + +import { useVaultContext } from "@/store/vault/hook"; import { Button } from "@midday/ui/button"; import { Icons } from "@midday/ui/icons"; export function CreateFolderButton({ disableActions }) { + const createFolder = useVaultContext((s) => s.createFolder); + + const handleCreateFolder = () => { + createFolder({ name: "Untitled folder" }); + }; + return ( diff --git a/apps/dashboard/src/components/tables/vault/data-table-row.tsx b/apps/dashboard/src/components/tables/vault/data-table-row.tsx index a6f90f971f..3009e65519 100644 --- a/apps/dashboard/src/components/tables/vault/data-table-row.tsx +++ b/apps/dashboard/src/components/tables/vault/data-table-row.tsx @@ -1,8 +1,12 @@ "use client"; +import { createFolderAction } from "@/actions/create-folder-action"; +import { deleteFileAction } from "@/actions/delete-file-action"; +import { deleteFolderAction } from "@/actions/delete-folder-action"; import { shareFileAction } from "@/actions/share-file-action"; import { FileIcon } from "@/components/file-icon"; import { useI18n } from "@/locales/client"; +import { useVaultContext } from "@/store/vault/hook"; import { formatSize } from "@/utils/format"; import { AlertDialog, @@ -40,12 +44,15 @@ import { HoverCardTrigger, } from "@midday/ui/hover-card"; import { Icons } from "@midday/ui/icons"; +import { Input } from "@midday/ui/input"; import { TableCell, TableRow } from "@midday/ui/table"; import { useToast } from "@midday/ui/use-toast"; import { format } from "date-fns"; import ms from "ms"; import { useAction } from "next-safe-action/hook"; -import { useParams, usePathname, useRouter } from "next/navigation"; +import Link from "next/link"; +import { useParams, usePathname } from "next/navigation"; +import { useState } from "react"; export const translatedFolderName = (t: any, folder: string) => { switch (folder) { @@ -57,24 +64,62 @@ export const translatedFolderName = (t: any, folder: string) => { return t("folders.transactions"); case "exports": return t("folders.exports"); - default: return decodeURIComponent(folder); } }; -export function DataTableRow({ - data, - deleteFile, - createFolder, - deleteFolder, - teamId, -}) { +function RowTitle({ isEditing, name: initialName, path, href }) { const t = useI18n(); const { toast } = useToast(); - const router = useRouter(); + const [name, setName] = useState(initialName ?? "Untitled Folder"); + + const createFolder = useAction(createFolderAction, { + onExecute: () => {}, + onError: () => { + toast({ + duration: 3500, + title: + "The folder already exists in the current directory. Please use a different name.", + }); + }, + }); + + const handleOnBlur = (evt) => { + createFolder.execute({ path, name }); + }; + + const handleOnKeyDown = (evt: React.KeyboardEvent) => { + if (evt.key === "Enter") { + createFolder.execute({ path, name }); + } + }; + + if (isEditing) { + return ( + setName(evt.target.value)} + /> + ); + } + + if (href) { + return {translatedFolderName(t, name)}; + } + + return {translatedFolderName(t, name)}; +} + +export function DataTableRow({ data, teamId }) { + const { toast } = useToast(); const pathname = usePathname(); const params = useParams(); + const { deleteItem, createFolder } = useVaultContext((s) => s); const folders = params?.folders ?? []; const isDefaultFolder = ["inbox", "exports", "transactions"].includes( @@ -85,6 +130,40 @@ export function DataTableRow({ const folderPath = folders.join("/"); const filepath = [...folders, data.name].join("/"); + const deleteFolder = useAction(deleteFolderAction, { + onExecute: () => deleteItem(data.name), + onError: () => { + toast({ + duration: 3500, + variant: "error", + title: "Something went wrong pleaase try again.", + }); + }, + }); + + const deleteFile = useAction(deleteFileAction, { + onExecute: ({ id }) => deleteItem(id), + onError: () => { + toast({ + duration: 3500, + variant: "error", + title: "Something went wrong pleaase try again.", + }); + }, + }); + + const handleDeleteItem = () => { + if (data.isFolder) { + deleteFolder.execute({ path: [...folders, data.name] }); + } else { + deleteFile.execute({ id: data.id, path: [...folders, data.name] }); + } + }; + + const handleCreateFolder = () => { + createFolder({ path: folderPath, name: "Untitled folder" }); + }; + const shareFile = useAction(shareFileAction, { onSuccess: async (url) => { try { @@ -99,20 +178,12 @@ export function DataTableRow({ }, }); - const handleNavigate = () => { - if (data.isFolder) { - router.push(`${pathname}/${data.name}`); - } - }; - - console.log(data); - return ( - + - {translatedFolderName(t, data.name)} + + + {data?.metadata?.size && ( {formatSize(data.metadata.size)} @@ -157,7 +235,7 @@ export function DataTableRow({ @@ -203,9 +281,9 @@ export function DataTableRow({ )} - {!disableActions && !isDefaultFolder && ( + {/* {!disableActions && !isDefaultFolder && ( Rename - )} + )} */} {data.isFolder ? ( )} - - createFolder({ - path: folderPath, - name: "Untitled folder", - }) - } - > - Create folder - - {!disableActions && !isDefaultFolder && ( - Rename + {!disableActions && ( + + Create folder + )} + {/* {!disableActions && !isDefaultFolder && ( + Rename + )} */} {data.isFolder ? ( Cancel - { - if (data.isFolder) { - deleteFolder({ - path: [...folders, data.name], - }); - } else { - deleteFile({ - id: data.id, - path: [...folders, data.name], - }); - } - }} - > + Continue diff --git a/apps/dashboard/src/components/tables/vault/data-table.tsx b/apps/dashboard/src/components/tables/vault/data-table.tsx index d338e52510..46a5b8ad22 100644 --- a/apps/dashboard/src/components/tables/vault/data-table.tsx +++ b/apps/dashboard/src/components/tables/vault/data-table.tsx @@ -1,8 +1,6 @@ "use client"; -import { createFolderAction } from "@/actions/create-folder-action"; -import { deleteFileAction } from "@/actions/delete-file-action"; -import { deleteFolderAction } from "@/actions/delete-folder-action"; +import { useVaultContext } from "@/store/vault/hook"; import { Table, TableBody, @@ -10,68 +8,10 @@ import { TableHeader, TableRow, } from "@midday/ui/table"; -import { useToast } from "@midday/ui/use-toast"; -import { useAction } from "next-safe-action/hook"; -import { useOptimistic } from "react"; import { DataTableRow } from "./data-table-row"; -export function DataTable({ data, teamId }) { - const { toast } = useToast(); - - const [optimisticData, setOptimisticData] = useOptimistic( - data, - (state, { action, ...item }) => { - switch (action) { - case "delete": - return state.filter(({ id }) => id !== item.id); - case "delete-folder": - return state.filter(({ name }) => name !== item.name); - case "update": - return state.map((d) => (d.id === item.id ? item : d)); - default: - return [...state, item]; - } - } - ); - - const deleteFile = useAction(deleteFileAction, { - onExecute: ({ id }) => { - setOptimisticData({ action: "delete", id }); - }, - onError: () => { - toast({ - duration: 3500, - variant: "error", - title: "Something went wrong pleaase try again.", - }); - }, - }); - - const deleteFolder = useAction(deleteFolderAction, { - onExecute: ({ id }) => { - setOptimisticData({ action: "delete-folder", id }); - }, - onError: () => { - toast({ - duration: 3500, - variant: "error", - title: "Something went wrong pleaase try again.", - }); - }, - }); - - const createFolder = useAction(createFolderAction, { - onExecute: () => { - setOptimisticData({ id: "new" }); - }, - onError: () => { - toast({ - duration: 3500, - title: - "The folder already exists in the current directory. Please use a different name.", - }); - }, - }); +export function DataTable({ teamId }) { + const data = useVaultContext((s) => s.data); return (
@@ -86,15 +26,8 @@ export function DataTable({ data, teamId }) { - {optimisticData?.map((row) => ( - deleteFile.execute(params)} - deleteFolder={(params) => deleteFolder.execute(params)} - createFolder={(params) => createFolder.execute(params)} - /> + {data?.map((row) => ( + ))}
diff --git a/apps/dashboard/src/components/tables/vault/index.tsx b/apps/dashboard/src/components/tables/vault/index.tsx index 3a7040c2bd..c302f62a96 100644 --- a/apps/dashboard/src/components/tables/vault/index.tsx +++ b/apps/dashboard/src/components/tables/vault/index.tsx @@ -1,9 +1,14 @@ +import { Breadcrumbs } from "@/components/breadcrumbs"; +import { VaultProvider } from "@/store/vault/provider"; import { getUser, getVault } from "@midday/supabase/cached-queries"; +import { CreateFolderButton } from "./create-folder-button"; import { DataTable } from "./data-table"; import { EmptyTable } from "./empty-table"; +import { UploadButton } from "./upload-button"; import { UploadZone } from "./upload-zone"; -export async function Table({ path }) { +export async function Table({ folders, disableActions }) { + const path = folders?.join("/"); const { data } = await getVault({ path: path && decodeURIComponent(path), }); @@ -11,11 +16,24 @@ export async function Table({ path }) { const { data: userData } = await getUser(); return ( -
- - - {data.length === 0 && } - +
+ +
+ + +
+ + +
+
+ +
+ + + {data.length === 0 && } + +
+
); } diff --git a/apps/dashboard/src/components/tables/vault/upload-zone.tsx b/apps/dashboard/src/components/tables/vault/upload-zone.tsx index aceafe3ad2..fe7e2941b4 100644 --- a/apps/dashboard/src/components/tables/vault/upload-zone.tsx +++ b/apps/dashboard/src/components/tables/vault/upload-zone.tsx @@ -1,7 +1,7 @@ "use client"; -import { createFolderAction } from "@/actions/create-folder-action"; import { invalidateCacheAction } from "@/actions/invalidate-cache-action"; +import { useVaultContext } from "@/store/vault/hook"; import { resumableUpload } from "@/utils/upload"; import { createClient } from "@midday/supabase/client"; import { getCurrentUserTeamQuery } from "@midday/supabase/queries"; @@ -13,7 +13,6 @@ import { } from "@midday/ui/context-menu"; import { useToast } from "@midday/ui/use-toast"; import { cn } from "@midday/ui/utils"; -import { useAction } from "next-safe-action/hook"; import { useParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useDropzone } from "react-dropzone"; @@ -26,8 +25,12 @@ export function UploadZone({ children }) { const uploadProgress = useRef([]); const params = useParams(); const folders = params?.folders ?? []; - const folderPath = folders.join("/"); const { toast, dismiss, update } = useToast(); + const { createFolder } = useVaultContext((s) => s); + + const isDefaultFolder = ["inbox", "exports", "transactions"].includes( + folders.at(0) + ); useEffect(() => { if (!toastId && showProgress) { @@ -48,21 +51,6 @@ export function UploadZone({ children }) { } }, [showProgress, progress, toastId]); - const isDefaultFolder = ["inbox", "exports", "transactions"].includes( - folders.at(0) - ); - - const createFolder = useAction(createFolderAction, { - onError: () => { - toast({ - duration: 2500, - variant: "error", - title: - "The folder already exists in the current directory. Please use a different name.", - }); - }, - }); - const onDrop = async (files) => { // Set default progress uploadProgress.current = files.map(() => 0); @@ -149,25 +137,7 @@ export function UploadZone({ children }) { {!isDefaultFolder && ( - - createFolder.execute({ - path: folderPath, - name: "Untitled folder", - }) - } - > - Upload file - - - - createFolder.execute({ - path: folderPath, - name: "Untitled folder", - }) - } - > + Create folder diff --git a/apps/dashboard/src/store/vault/hook.ts b/apps/dashboard/src/store/vault/hook.ts new file mode 100644 index 0000000000..b0fd06a4ba --- /dev/null +++ b/apps/dashboard/src/store/vault/hook.ts @@ -0,0 +1,13 @@ +import { useContext } from "react"; +import { useStore } from "zustand"; +import { VaultContext, VaultState } from "./store"; + +export function useVaultContext(selector: (state: VaultState) => T): T { + const store = useContext(VaultContext); + + if (!store) { + throw new Error("Missing VaultContext.Provider in the tree"); + } + + return useStore(store, selector); +} diff --git a/apps/dashboard/src/store/vault/provider.tsx b/apps/dashboard/src/store/vault/provider.tsx new file mode 100644 index 0000000000..3381cb6eb8 --- /dev/null +++ b/apps/dashboard/src/store/vault/provider.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { VaultContext, VaultProps, createVaultStore } from "./store"; + +type VaultProviderProps = React.PropsWithChildren; + +export function VaultProvider({ children, ...props }: VaultProviderProps) { + return ( + + {children} + + ); +} diff --git a/apps/dashboard/src/store/vault/store.ts b/apps/dashboard/src/store/vault/store.ts new file mode 100644 index 0000000000..862c487ffd --- /dev/null +++ b/apps/dashboard/src/store/vault/store.ts @@ -0,0 +1,64 @@ +import { createContext } from "react"; +import { createStore } from "zustand"; + +type Item = { + id?: string; + name: string; + isFolder?: boolean; + isEditing?: boolean; +}; + +export interface VaultProps { + data: Item[]; +} + +export interface VaultState extends VaultProps { + addItems: (items: Item[]) => void; + deleteItem: (id: string) => void; + createFolder: (item: Item) => void; + updateItem: (id: string, payload: Item) => void; +} + +export type VaultStore = ReturnType; +export const VaultContext = createContext(null); + +export const createVaultStore = (initProps?: Partial) => { + const DEFAULT_PROPS: VaultProps = { + data: [], + }; + + return createStore()((set) => ({ + ...DEFAULT_PROPS, + ...initProps, + + addItems: (items) => + set((state) => ({ + data: [...state.data, ...items], + })), + + deleteItem: (id) => + set((state) => ({ + data: state.data.filter((item) => + item.isFolder ? item.name !== id : item.id !== id + ), + })), + + createFolder: (item) => + set((state) => ({ + data: [ + ...state.data, + { + ...item, + isEditing: true, + isFolder: true, + id: item.name, + }, + ], + })), + + updateItem: (id, payload) => + set((state) => ({ + data: state.data.map((d) => (d.id === id ? payload : d)), + })), + })); +}; diff --git a/bun.lockb b/bun.lockb index 1d477e29ad..a4020ff576 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/jobs/package.json b/packages/jobs/package.json index 2d829ba571..c1dc3ffa09 100644 --- a/packages/jobs/package.json +++ b/packages/jobs/package.json @@ -10,9 +10,9 @@ "check:types": "tsc --noEmit" }, "dependencies": { - "@trigger.dev/react": "^2.3.5", - "@trigger.dev/sdk": "^2.3.5", - "@trigger.dev/supabase": "^2.3.5" + "@trigger.dev/react": "^2.3.6", + "@trigger.dev/sdk": "^2.3.6", + "@trigger.dev/supabase": "^2.3.6" } } \ No newline at end of file diff --git a/packages/supabase/src/queries/cached-queries.ts b/packages/supabase/src/queries/cached-queries.ts index 2f06e1001f..6658e91384 100644 --- a/packages/supabase/src/queries/cached-queries.ts +++ b/packages/supabase/src/queries/cached-queries.ts @@ -171,6 +171,7 @@ export const getVault = async (params) => { ["vault", teamId], { tags: [`vault_${teamId}`], + revalidate: 10, } )(params); }; diff --git a/packages/supabase/src/queries/index.ts b/packages/supabase/src/queries/index.ts index bf9dd671f5..9d8c036cb2 100644 --- a/packages/supabase/src/queries/index.ts +++ b/packages/supabase/src/queries/index.ts @@ -500,7 +500,9 @@ export async function getVaultQuery(supabase: Client, params: GetVaultParams) { basePath = `${basePath}/${path}`; } - const { data } = await supabase.storage.from("vault").list(basePath); + const { data } = await supabase.storage.from("vault").list(basePath, { + sortBy: { column: "name", order: "asc" }, + }); const filteredData = data diff --git a/packages/ui/package.json b/packages/ui/package.json index ea19ff40e7..656d574121 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "autoprefixer": "^10.4.16", - "typescript": "^5.3.2" + "typescript": "^5.3.3" }, "exports": { "./calendar": "./src/components/calendar.tsx", @@ -59,7 +59,7 @@ "./utils": "./src/utils" }, "dependencies": { - "@mui/icons-material": "^5.14.19", + "@mui/icons-material": "^5.15.0", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", @@ -84,7 +84,7 @@ "clsx": "^2.0.0", "cmdk": "^0.2.0", "date-fns": "^2.30.0", - "lucide-react": "^0.294.0", + "lucide-react": "^0.295.0", "react-day-picker": "^8.9.1", "react-icons": "^4.12.0", "tailwind-merge": "2.0.0",