diff --git a/app/components/data-table.tsx b/app/components/data-table.tsx index 7059bde..54f9bd7 100644 --- a/app/components/data-table.tsx +++ b/app/components/data-table.tsx @@ -1,8 +1,8 @@ import { useMemo} from "react"; -import { BaseRecord } from "@refinedev/core"; +import type { BaseRecord } from "@refinedev/core"; import { useTable } from "@refinedev/react-table"; import { - ColumnDef, + type ColumnDef, flexRender, } from "@tanstack/react-table"; diff --git a/app/components/general-layout.tsx b/app/components/general-layout.tsx index 01025de..f4d79c3 100644 --- a/app/components/general-layout.tsx +++ b/app/components/general-layout.tsx @@ -29,10 +29,10 @@ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGr import { Avatar } from "@radix-ui/react-avatar"; import { cn } from "~/utils"; import { useGetIdentity, useLogout } from "@refinedev/core"; -import { Identity } from "~/data/auth-provider"; +import type { Identity } from "~/data/auth-provider"; -export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => { +export const GeneralLayout = ({ children }: React.PropsWithChildren) => { const location = useLocation(); const { data: identity } = useGetIdentity(); const{ mutate: logout } = useLogout() diff --git a/app/components/lib/uppy.ts b/app/components/lib/uppy.ts index 5fb54bc..e2da3d4 100644 --- a/app/components/lib/uppy.ts +++ b/app/components/lib/uppy.ts @@ -1,14 +1,21 @@ -import Uppy, {debugLogger, type State, UppyFile} from "@uppy/core" +import Uppy, { debugLogger, type State, type UppyFile } from "@uppy/core"; -import Tus from "@uppy/tus" -import toArray from "@uppy/utils/lib/toArray" +import Tus from "@uppy/tus"; +import toArray from "@uppy/utils/lib/toArray"; -import {type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState} from "react" -import DropTarget, {type DropTargetOptions} from "./uppy-dropzone" -import {useSdk} from "~/components/lib/sdk-context.js"; -import UppyFileUpload from "~/components/lib/uppy-file-upload.js"; -import {PROTOCOL_S5, Sdk} from "@lumeweb/portal-sdk"; -import {S5Client, HashProgressEvent} from "@lumeweb/s5-js"; +import { + type ChangeEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import DropTarget, { type DropTargetOptions } from "./uppy-dropzone"; +import { useSdk } from "~/components/lib/sdk-context"; +import UppyFileUpload from "~/components/lib/uppy-file-upload"; +import { PROTOCOL_S5, type Sdk } from "@lumeweb/portal-sdk"; +import type { S5Client, HashProgressEvent } from "@lumeweb/s5-js"; const LISTENING_EVENTS = [ "upload", @@ -16,46 +23,46 @@ const LISTENING_EVENTS = [ "upload-error", "file-added", "file-removed", - "files-added" -] as const + "files-added", +] as const; export function useUppy() { - const sdk = useSdk() + const sdk = useSdk(); - const [uploadLimit, setUploadLimit] = useState(0) + const [uploadLimit, setUploadLimit] = useState(0); - useEffect(() => { - async function getUploadLimit() { - try { - const limit = await sdk.account!().uploadLimit(); - setUploadLimit(limit); - } catch (err) { - console.log('Error occured while fetching upload limit', err); - } - } - getUploadLimit(); - }, []); + useEffect(() => { + async function getUploadLimit() { + try { + const limit = await sdk.account!().uploadLimit(); + setUploadLimit(limit); + } catch (err) { + console.log("Error occured while fetching upload limit", err); + } + } + getUploadLimit(); + }, [sdk.account]); - const inputRef = useRef(null) - const [targetRef, _setTargetRef] = useState(null) - const uppyInstance = useRef() + const inputRef = useRef(null); + const [targetRef, _setTargetRef] = useState(null); + const uppyInstance = useRef(); const setRef = useCallback( (element: HTMLElement | null) => _setTargetRef(element), - [] - ) - const [, setUppyState] = useState() + [], + ); + const [, setUppyState] = useState(); const [state, setState] = useState< "completed" | "idle" | "initializing" | "error" | "uploading" - >("initializing") + >("initializing"); const [inputProps, setInputProps] = useState< | { - ref: typeof inputRef - type: "file" - onChange: (event: ChangeEvent) => void + ref: typeof inputRef; + type: "file"; + onChange: (event: ChangeEvent) => void; } | object - >({}) + >({}); const getRootProps = useMemo( () => () => { return { @@ -63,103 +70,110 @@ export function useUppy() { onClick: () => { if (inputRef.current) { //@ts-expect-error -- dumb html - inputRef.current.value = null - inputRef.current.click() - console.log("clicked", { input: inputRef.current }) + inputRef.current.value = null; + inputRef.current.click(); + console.log("clicked", { input: inputRef.current }); } }, - role: "presentation" - } + role: "presentation", + }; }, - [setRef] - ) + [setRef], + ); const removeFile = useCallback( (id: string) => { - uppyInstance.current?.removeFile(id) + uppyInstance.current?.removeFile(id); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [targetRef, uppyInstance] - ) + [targetRef, uppyInstance], + ); const cancelAll = useCallback( () => uppyInstance.current?.cancelAll({ reason: "user" }), // eslint-disable-next-line react-hooks/exhaustive-deps - [targetRef, uppyInstance] - ) + [targetRef, uppyInstance], + ); useEffect(() => { - if (!targetRef) return + if (!targetRef) return; - const tusPreprocessor = async (fileIDs: string[]) => { - for(const fileID of fileIDs) { - const file = uppyInstance.current?.getFile(fileID) as UppyFile - // @ts-ignore - if (file.uploader === "tus") { - const hashProgressCb = (event: HashProgressEvent) => { - uppyInstance.current?.emit("preprocess-progress", file, { - uploadStarted: false, - bytesUploaded: 0, - preprocess: { - mode: "determinate", - message: "Hashing file...", - value: Math.round((event.total / event.total) * 100) - } - }) - } - const options = await sdk.protocols!().get(PROTOCOL_S5).getSdk().getTusOptions(file.data as File, {}, {onHashProgress: hashProgressCb}) - uppyInstance.current?.setFileState(fileID, { - tus: options, - meta: { - ...options.metadata, - ...file.meta, - } - }) - } + const tusPreprocessor = async (fileIDs: string[]) => { + for (const fileID of fileIDs) { + const file = uppyInstance.current?.getFile(fileID) as UppyFile; + // @ts-ignore + if (file.uploader === "tus") { + const hashProgressCb = (event: HashProgressEvent) => { + uppyInstance.current?.emit("preprocess-progress", file, { + uploadStarted: false, + bytesUploaded: 0, + preprocess: { + mode: "determinate", + message: "Hashing file...", + value: Math.round((event.total / event.total) * 100), + }, + }); + }; + const options = await sdk.protocols!() + .get(PROTOCOL_S5) + .getSdk() + .getTusOptions( + file.data as File, + {}, + { onHashProgress: hashProgressCb }, + ); + uppyInstance.current?.setFileState(fileID, { + tus: options, + meta: { + ...options.metadata, + ...file.meta, + }, + }); } } + }; const uppy = new Uppy({ - logger: debugLogger, - onBeforeUpload: (files) => { - for (const file of Object.entries(files)) { - // @ts-ignore - file[1].uploader = file[1].size > uploadLimit ? "tus" : "file"; - } + logger: debugLogger, + onBeforeUpload: (files) => { + for (const file of Object.entries(files)) { + // @ts-ignore + file[1].uploader = file[1].size > uploadLimit ? "tus" : "file"; + } - return true; - }, + return true; + }, }).use(DropTarget, { - target: targetRef - } as DropTargetOptions) + target: targetRef, + } as DropTargetOptions); - uppyInstance.current = uppy + uppyInstance.current = uppy; setInputProps({ ref: inputRef, type: "file", onChange: (event) => { - const files = toArray(event.target.files) + const files = toArray(event.target.files); if (files.length > 0) { - uppyInstance.current?.log("[DragDrop] Files selected through input") - uppyInstance.current?.addFiles(files) + uppyInstance.current?.log("[DragDrop] Files selected through input"); + uppyInstance.current?.addFiles(files); } uppy.iteratePlugins((plugin) => { - uppy.removePlugin(plugin); + uppy.removePlugin(plugin); }); - uppy.use(UppyFileUpload, { sdk: sdk as Sdk }) + uppy.use(UppyFileUpload, { sdk: sdk as Sdk }); - let useTus = false; + let useTus = false; - uppyInstance.current?.getFiles().forEach((file) => { - if (file.size > uploadLimit) { - useTus = true; - } - }) - - if (useTus) { - uppy.use(Tus, { limit: 6, parallelUploads: 10 }) - uppy.addPreProcessor(tusPreprocessor) + uppyInstance.current?.getFiles().forEach((file) => { + if (file.size > uploadLimit) { + useTus = true; } + }); + + if (useTus) { + uppy.use(Tus, { limit: 6, parallelUploads: 10 }); + uppy.addPreProcessor(tusPreprocessor); + } // We clear the input after a file is selected, because otherwise // change event is not fired in Chrome and Safari when a file @@ -169,54 +183,52 @@ export function useUppy() { // Chrome will not trigger change if we drop the same file twice (Issue #768). // @ts-expect-error TS freaks out, but this is fine // eslint-disable-next-line no-param-reassign - event.target.value = null - } - }) - - + event.target.value = null; + }, + }); uppy.on("complete", (result) => { if (result.failed.length === 0) { - console.log("Upload successful üòÄ") - setState("completed") + console.log("Upload successful üòÄ"); + setState("completed"); } else { - console.warn("Upload failed üòû") - setState("error") + console.warn("Upload failed üòû"); + setState("error"); } - console.log("successful files:", result.successful) - console.log("failed files:", result.failed) - }) + console.log("successful files:", result.successful); + console.log("failed files:", result.failed); + }); const setStateCb = (event: (typeof LISTENING_EVENTS)[number]) => { switch (event) { case "upload": - setState("uploading") - break + setState("uploading"); + break; case "upload-error": - setState("error") - break + setState("error"); + break; default: - break + break; } - setUppyState(uppy.getState()) - } + setUppyState(uppy.getState()); + }; for (const event of LISTENING_EVENTS) { uppy.on(event, function cb() { - setStateCb(event) - }) + setStateCb(event); + }); } - setState("idle") - }, [targetRef, uploadLimit]) + setState("idle"); + }, [targetRef, uploadLimit]); useEffect(() => { return () => { - uppyInstance.current?.cancelAll({ reason: "unmount" }) - uppyInstance.current?.logout() - uppyInstance.current?.close() - uppyInstance.current = undefined - } - }, []) + uppyInstance.current?.cancelAll({ reason: "unmount" }); + uppyInstance.current?.logout(); + uppyInstance.current?.close(); + uppyInstance.current = undefined; + }; + }, []); return { getFiles: () => uppyInstance.current?.getFiles() ?? [], error: uppyInstance.current?.getState, @@ -227,6 +239,6 @@ export function useUppy() { getInputProps: () => inputProps, getRootProps, removeFile, - cancelAll - } + cancelAll, + }; } diff --git a/app/components/ui/toast.tsx b/app/components/ui/toast.tsx index f8f37c9..9aaa1d7 100644 --- a/app/components/ui/toast.tsx +++ b/app/components/ui/toast.tsx @@ -60,7 +60,7 @@ const ToastAction = React.forwardRef< - {toasts.map(function ({ id, title, description, action, cancelMutation, ...props }) { + {toasts.map(({ id, title, description, action, cancelMutation, ...props }) => { const undoButton = cancelMutation ? Undo : undefined return ( diff --git a/app/root.tsx b/app/root.tsx index 6d099b6..522ae69 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -28,7 +28,7 @@ export function Layout({children}: { children: React.ReactNode }) { - + {children} diff --git a/app/routes/file-manager/columns.tsx b/app/routes/file-manager/columns.tsx index 5bd1aa0..d5fd059 100644 --- a/app/routes/file-manager/columns.tsx +++ b/app/routes/file-manager/columns.tsx @@ -2,88 +2,94 @@ import { DrawingPinIcon, TrashIcon } from "@radix-ui/react-icons"; import type { ColumnDef, RowData } from "@tanstack/react-table"; import { FileIcon, MoreIcon } from "~/components/icons"; import { Checkbox } from "~/components/ui/checkbox"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "~/components/ui/dropdown-menu"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "~/components/ui/dropdown-menu"; import { cn } from "~/utils"; -import {FileItem} from "~/data/file-provider.js"; -import {format} from "date-fns/fp"; +import type { FileItem } from "~/data/file-provider"; -declare module '@tanstack/table-core' { +declare module "@tanstack/table-core" { interface TableMeta { - hoveredRowId: string, + hoveredRowId: string; } } export const columns: ColumnDef[] = [ - { - id: "select", - size: 20, - header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - /> - ), - enableSorting: false, - enableHiding: false, - }, - { - accessorKey: "name", - header: "Name", - cell: ({ row }) => ( -
- - {row.getValue("name")} -
- ) - }, - { - accessorKey: "cid", - header: "CID", - }, - { - accessorKey: "size", - header: "Size", - }, - { - accessorKey: "pinned", - size: 200, - header: "Pinned On", - cell: ({ row }) => ( -
- {format(row.getValue("pinned")) as unknown as string} - - - - - - - - - Ping CID - - - - - Delete - - - - -
- ) - } + { + id: "select", + size: 20, + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "name", + header: "Name", + cell: ({ row }) => ( +
+ + {row.getValue("name")} +
+ ), + }, + { + accessorKey: "cid", + header: "CID", + }, + { + accessorKey: "size", + header: "Size", + }, + { + accessorKey: "pinned", + header: "Pinned On", + cell: ({ row }) => new Date(row.getValue("pinned")).toLocaleString(), + }, + { + accessorKey: "actions", + header: () => null, + size: 20, + cell: ({ row }) => ( +
+ + + + + + + + + Delete + + + + +
+ ), + }, ]; diff --git a/app/routes/file-manager/index.tsx b/app/routes/file-manager/index.tsx index 19bb1b4..82436b4 100644 --- a/app/routes/file-manager/index.tsx +++ b/app/routes/file-manager/index.tsx @@ -18,8 +18,8 @@ import { Field } from "~/components/forms"; export default function FileManager() { return ( - - + +

File Manager

-
- - Pinning Contnet - - -
- + + + Pin Content + + + - - - -
+ + + + +
); } diff --git a/app/routes/login.tsx b/app/routes/login.tsx index 90cee68..b87b001 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -47,7 +47,7 @@ export default function Login() { go({ to, type: "push" }); } } - }, [isAuthLoading, authData]); + }, [isAuthLoading, authData, parsed, go]); return (
@@ -109,16 +109,18 @@ const LoginForm = () => { return parseWithZod(formData, { schema: LoginSchema }); }, shouldValidate: "onSubmit", - onSubmit(e) { + onSubmit(e, { submission }) { e.preventDefault(); - const data = Object.fromEntries(new FormData(e.currentTarget).entries()); - login.mutate({ - email: data.email.toString(), - password: data.password.toString(), - rememberMe: data.rememberMe.toString() === "on", - redirectTo: parsed.params?.to, - }); + if (submission?.status === "success") { + const data = submission.value; + login.mutate({ + email: data.email, + password: data.password, + rememberMe: data.rememberMe ?? false, + redirectTo: parsed.params?.to, + }); + } }, }); diff --git a/app/tailwind.css b/app/tailwind.css index 477e95a..deb417f 100644 --- a/app/tailwind.css +++ b/app/tailwind.css @@ -4,8 +4,8 @@ @layer base { :root { - --background: 240, 33%, 3%; - --foreground: 0, 0%, 88%; + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; --card: 0 0% 0%; --card-foreground: 0 0% 3.9%;