fix: UX changes regarding showing dates on table and actions pannel
This commit is contained in:
parent
9d9aa4e9c9
commit
e401889b04
|
@ -1,8 +1,8 @@
|
||||||
import { useMemo} from "react";
|
import { useMemo} from "react";
|
||||||
import { BaseRecord } from "@refinedev/core";
|
import type { BaseRecord } from "@refinedev/core";
|
||||||
import { useTable } from "@refinedev/react-table";
|
import { useTable } from "@refinedev/react-table";
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
type ColumnDef,
|
||||||
flexRender,
|
flexRender,
|
||||||
} from "@tanstack/react-table";
|
} from "@tanstack/react-table";
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,10 @@ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGr
|
||||||
import { Avatar } from "@radix-ui/react-avatar";
|
import { Avatar } from "@radix-ui/react-avatar";
|
||||||
import { cn } from "~/utils";
|
import { cn } from "~/utils";
|
||||||
import { useGetIdentity, useLogout } from "@refinedev/core";
|
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 location = useLocation();
|
||||||
const { data: identity } = useGetIdentity<Identity>();
|
const { data: identity } = useGetIdentity<Identity>();
|
||||||
const{ mutate: logout } = useLogout()
|
const{ mutate: logout } = useLogout()
|
||||||
|
|
|
@ -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 Tus from "@uppy/tus";
|
||||||
import toArray from "@uppy/utils/lib/toArray"
|
import toArray from "@uppy/utils/lib/toArray";
|
||||||
|
|
||||||
import {type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState} from "react"
|
import {
|
||||||
import DropTarget, {type DropTargetOptions} from "./uppy-dropzone"
|
type ChangeEvent,
|
||||||
import {useSdk} from "~/components/lib/sdk-context.js";
|
useCallback,
|
||||||
import UppyFileUpload from "~/components/lib/uppy-file-upload.js";
|
useEffect,
|
||||||
import {PROTOCOL_S5, Sdk} from "@lumeweb/portal-sdk";
|
useMemo,
|
||||||
import {S5Client, HashProgressEvent} from "@lumeweb/s5-js";
|
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 = [
|
const LISTENING_EVENTS = [
|
||||||
"upload",
|
"upload",
|
||||||
|
@ -16,46 +23,46 @@ const LISTENING_EVENTS = [
|
||||||
"upload-error",
|
"upload-error",
|
||||||
"file-added",
|
"file-added",
|
||||||
"file-removed",
|
"file-removed",
|
||||||
"files-added"
|
"files-added",
|
||||||
] as const
|
] as const;
|
||||||
|
|
||||||
export function useUppy() {
|
export function useUppy() {
|
||||||
const sdk = useSdk()
|
const sdk = useSdk();
|
||||||
|
|
||||||
const [uploadLimit, setUploadLimit] = useState<number>(0)
|
const [uploadLimit, setUploadLimit] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getUploadLimit() {
|
async function getUploadLimit() {
|
||||||
try {
|
try {
|
||||||
const limit = await sdk.account!().uploadLimit();
|
const limit = await sdk.account!().uploadLimit();
|
||||||
setUploadLimit(limit);
|
setUploadLimit(limit);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error occured while fetching upload limit', err);
|
console.log("Error occured while fetching upload limit", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getUploadLimit();
|
getUploadLimit();
|
||||||
}, []);
|
}, [sdk.account]);
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [targetRef, _setTargetRef] = useState<HTMLElement | null>(null)
|
const [targetRef, _setTargetRef] = useState<HTMLElement | null>(null);
|
||||||
const uppyInstance = useRef<Uppy>()
|
const uppyInstance = useRef<Uppy>();
|
||||||
const setRef = useCallback(
|
const setRef = useCallback(
|
||||||
(element: HTMLElement | null) => _setTargetRef(element),
|
(element: HTMLElement | null) => _setTargetRef(element),
|
||||||
[]
|
[],
|
||||||
)
|
);
|
||||||
const [, setUppyState] = useState<State>()
|
const [, setUppyState] = useState<State>();
|
||||||
const [state, setState] = useState<
|
const [state, setState] = useState<
|
||||||
"completed" | "idle" | "initializing" | "error" | "uploading"
|
"completed" | "idle" | "initializing" | "error" | "uploading"
|
||||||
>("initializing")
|
>("initializing");
|
||||||
|
|
||||||
const [inputProps, setInputProps] = useState<
|
const [inputProps, setInputProps] = useState<
|
||||||
| {
|
| {
|
||||||
ref: typeof inputRef
|
ref: typeof inputRef;
|
||||||
type: "file"
|
type: "file";
|
||||||
onChange: (event: ChangeEvent<HTMLInputElement>) => void
|
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
}
|
}
|
||||||
| object
|
| object
|
||||||
>({})
|
>({});
|
||||||
const getRootProps = useMemo(
|
const getRootProps = useMemo(
|
||||||
() => () => {
|
() => () => {
|
||||||
return {
|
return {
|
||||||
|
@ -63,103 +70,110 @@ export function useUppy() {
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
//@ts-expect-error -- dumb html
|
//@ts-expect-error -- dumb html
|
||||||
inputRef.current.value = null
|
inputRef.current.value = null;
|
||||||
inputRef.current.click()
|
inputRef.current.click();
|
||||||
console.log("clicked", { input: inputRef.current })
|
console.log("clicked", { input: inputRef.current });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
role: "presentation"
|
role: "presentation",
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
[setRef]
|
[setRef],
|
||||||
)
|
);
|
||||||
const removeFile = useCallback(
|
const removeFile = useCallback(
|
||||||
(id: string) => {
|
(id: string) => {
|
||||||
uppyInstance.current?.removeFile(id)
|
uppyInstance.current?.removeFile(id);
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[targetRef, uppyInstance]
|
[targetRef, uppyInstance],
|
||||||
)
|
);
|
||||||
const cancelAll = useCallback(
|
const cancelAll = useCallback(
|
||||||
() => uppyInstance.current?.cancelAll({ reason: "user" }),
|
() => uppyInstance.current?.cancelAll({ reason: "user" }),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[targetRef, uppyInstance]
|
[targetRef, uppyInstance],
|
||||||
)
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!targetRef) return
|
if (!targetRef) return;
|
||||||
|
|
||||||
const tusPreprocessor = async (fileIDs: string[]) => {
|
const tusPreprocessor = async (fileIDs: string[]) => {
|
||||||
for(const fileID of fileIDs) {
|
for (const fileID of fileIDs) {
|
||||||
const file = uppyInstance.current?.getFile(fileID) as UppyFile
|
const file = uppyInstance.current?.getFile(fileID) as UppyFile;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (file.uploader === "tus") {
|
if (file.uploader === "tus") {
|
||||||
const hashProgressCb = (event: HashProgressEvent) => {
|
const hashProgressCb = (event: HashProgressEvent) => {
|
||||||
uppyInstance.current?.emit("preprocess-progress", file, {
|
uppyInstance.current?.emit("preprocess-progress", file, {
|
||||||
uploadStarted: false,
|
uploadStarted: false,
|
||||||
bytesUploaded: 0,
|
bytesUploaded: 0,
|
||||||
preprocess: {
|
preprocess: {
|
||||||
mode: "determinate",
|
mode: "determinate",
|
||||||
message: "Hashing file...",
|
message: "Hashing file...",
|
||||||
value: Math.round((event.total / event.total) * 100)
|
value: Math.round((event.total / event.total) * 100),
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
const options = await sdk.protocols!().get<S5Client>(PROTOCOL_S5).getSdk().getTusOptions(file.data as File, {}, {onHashProgress: hashProgressCb})
|
const options = await sdk.protocols!()
|
||||||
uppyInstance.current?.setFileState(fileID, {
|
.get<S5Client>(PROTOCOL_S5)
|
||||||
tus: options,
|
.getSdk()
|
||||||
meta: {
|
.getTusOptions(
|
||||||
...options.metadata,
|
file.data as File,
|
||||||
...file.meta,
|
{},
|
||||||
}
|
{ onHashProgress: hashProgressCb },
|
||||||
})
|
);
|
||||||
}
|
uppyInstance.current?.setFileState(fileID, {
|
||||||
|
tus: options,
|
||||||
|
meta: {
|
||||||
|
...options.metadata,
|
||||||
|
...file.meta,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const uppy = new Uppy({
|
const uppy = new Uppy({
|
||||||
logger: debugLogger,
|
logger: debugLogger,
|
||||||
onBeforeUpload: (files) => {
|
onBeforeUpload: (files) => {
|
||||||
for (const file of Object.entries(files)) {
|
for (const file of Object.entries(files)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
file[1].uploader = file[1].size > uploadLimit ? "tus" : "file";
|
file[1].uploader = file[1].size > uploadLimit ? "tus" : "file";
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
}).use(DropTarget, {
|
}).use(DropTarget, {
|
||||||
target: targetRef
|
target: targetRef,
|
||||||
} as DropTargetOptions)
|
} as DropTargetOptions);
|
||||||
|
|
||||||
uppyInstance.current = uppy
|
uppyInstance.current = uppy;
|
||||||
setInputProps({
|
setInputProps({
|
||||||
ref: inputRef,
|
ref: inputRef,
|
||||||
type: "file",
|
type: "file",
|
||||||
onChange: (event) => {
|
onChange: (event) => {
|
||||||
const files = toArray(event.target.files)
|
const files = toArray(event.target.files);
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
uppyInstance.current?.log("[DragDrop] Files selected through input")
|
uppyInstance.current?.log("[DragDrop] Files selected through input");
|
||||||
uppyInstance.current?.addFiles(files)
|
uppyInstance.current?.addFiles(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
uppy.iteratePlugins((plugin) => {
|
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) => {
|
uppyInstance.current?.getFiles().forEach((file) => {
|
||||||
if (file.size > uploadLimit) {
|
if (file.size > uploadLimit) {
|
||||||
useTus = true;
|
useTus = true;
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (useTus) {
|
|
||||||
uppy.use(Tus, { limit: 6, parallelUploads: 10 })
|
|
||||||
uppy.addPreProcessor(tusPreprocessor)
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (useTus) {
|
||||||
|
uppy.use(Tus, { limit: 6, parallelUploads: 10 });
|
||||||
|
uppy.addPreProcessor(tusPreprocessor);
|
||||||
|
}
|
||||||
|
|
||||||
// We clear the input after a file is selected, because otherwise
|
// We clear the input after a file is selected, because otherwise
|
||||||
// change event is not fired in Chrome and Safari when a file
|
// 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).
|
// Chrome will not trigger change if we drop the same file twice (Issue #768).
|
||||||
// @ts-expect-error TS freaks out, but this is fine
|
// @ts-expect-error TS freaks out, but this is fine
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
event.target.value = null
|
event.target.value = null;
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
uppy.on("complete", (result) => {
|
uppy.on("complete", (result) => {
|
||||||
if (result.failed.length === 0) {
|
if (result.failed.length === 0) {
|
||||||
console.log("Upload successful üòÄ")
|
console.log("Upload successful üòÄ");
|
||||||
setState("completed")
|
setState("completed");
|
||||||
} else {
|
} else {
|
||||||
console.warn("Upload failed üòû")
|
console.warn("Upload failed üòû");
|
||||||
setState("error")
|
setState("error");
|
||||||
}
|
}
|
||||||
console.log("successful files:", result.successful)
|
console.log("successful files:", result.successful);
|
||||||
console.log("failed files:", result.failed)
|
console.log("failed files:", result.failed);
|
||||||
})
|
});
|
||||||
|
|
||||||
const setStateCb = (event: (typeof LISTENING_EVENTS)[number]) => {
|
const setStateCb = (event: (typeof LISTENING_EVENTS)[number]) => {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case "upload":
|
case "upload":
|
||||||
setState("uploading")
|
setState("uploading");
|
||||||
break
|
break;
|
||||||
case "upload-error":
|
case "upload-error":
|
||||||
setState("error")
|
setState("error");
|
||||||
break
|
break;
|
||||||
default:
|
default:
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
setUppyState(uppy.getState())
|
setUppyState(uppy.getState());
|
||||||
}
|
};
|
||||||
|
|
||||||
for (const event of LISTENING_EVENTS) {
|
for (const event of LISTENING_EVENTS) {
|
||||||
uppy.on(event, function cb() {
|
uppy.on(event, function cb() {
|
||||||
setStateCb(event)
|
setStateCb(event);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
setState("idle")
|
setState("idle");
|
||||||
}, [targetRef, uploadLimit])
|
}, [targetRef, uploadLimit]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
uppyInstance.current?.cancelAll({ reason: "unmount" })
|
uppyInstance.current?.cancelAll({ reason: "unmount" });
|
||||||
uppyInstance.current?.logout()
|
uppyInstance.current?.logout();
|
||||||
uppyInstance.current?.close()
|
uppyInstance.current?.close();
|
||||||
uppyInstance.current = undefined
|
uppyInstance.current = undefined;
|
||||||
}
|
};
|
||||||
}, [])
|
}, []);
|
||||||
return {
|
return {
|
||||||
getFiles: () => uppyInstance.current?.getFiles() ?? [],
|
getFiles: () => uppyInstance.current?.getFiles() ?? [],
|
||||||
error: uppyInstance.current?.getState,
|
error: uppyInstance.current?.getState,
|
||||||
|
@ -227,6 +239,6 @@ export function useUppy() {
|
||||||
getInputProps: () => inputProps,
|
getInputProps: () => inputProps,
|
||||||
getRootProps,
|
getRootProps,
|
||||||
removeFile,
|
removeFile,
|
||||||
cancelAll
|
cancelAll,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ const ToastAction = React.forwardRef<
|
||||||
<ToastPrimitives.Action
|
<ToastPrimitives.Action
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-background px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export function Toaster() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
{toasts.map(function ({ id, title, description, action, cancelMutation, ...props }) {
|
{toasts.map(({ id, title, description, action, cancelMutation, ...props }) => {
|
||||||
const undoButton = cancelMutation ? <ToastAction altText="Undo" onClick={cancelMutation}>Undo</ToastAction> : undefined
|
const undoButton = cancelMutation ? <ToastAction altText="Undo" onClick={cancelMutation}>Undo</ToastAction> : undefined
|
||||||
return (
|
return (
|
||||||
<Toast key={id} {...props}>
|
<Toast key={id} {...props}>
|
||||||
|
|
|
@ -28,7 +28,7 @@ export function Layout({children}: { children: React.ReactNode }) {
|
||||||
<Meta/>
|
<Meta/>
|
||||||
<Links/>
|
<Links/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body className="overflow-hidden">
|
||||||
{children}
|
{children}
|
||||||
<Toaster/>
|
<Toaster/>
|
||||||
<ScrollRestoration/>
|
<ScrollRestoration/>
|
||||||
|
|
|
@ -2,88 +2,94 @@ import { DrawingPinIcon, TrashIcon } from "@radix-ui/react-icons";
|
||||||
import type { ColumnDef, RowData } from "@tanstack/react-table";
|
import type { ColumnDef, RowData } from "@tanstack/react-table";
|
||||||
import { FileIcon, MoreIcon } from "~/components/icons";
|
import { FileIcon, MoreIcon } from "~/components/icons";
|
||||||
import { Checkbox } from "~/components/ui/checkbox";
|
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 { cn } from "~/utils";
|
||||||
import {FileItem} from "~/data/file-provider.js";
|
import type { FileItem } from "~/data/file-provider";
|
||||||
import {format} from "date-fns/fp";
|
|
||||||
|
|
||||||
declare module '@tanstack/table-core' {
|
declare module "@tanstack/table-core" {
|
||||||
interface TableMeta<TData extends RowData> {
|
interface TableMeta<TData extends RowData> {
|
||||||
hoveredRowId: string,
|
hoveredRowId: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const columns: ColumnDef<FileItem>[] = [
|
export const columns: ColumnDef<FileItem>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
size: 20,
|
size: 20,
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
checked={
|
||||||
checked={
|
table.getIsAllPageRowsSelected() ||
|
||||||
table.getIsAllPageRowsSelected() ||
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
}
|
||||||
}
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
aria-label="Select all"
|
||||||
aria-label="Select all"
|
/>
|
||||||
/>
|
),
|
||||||
),
|
cell: ({ row }) => (
|
||||||
cell: ({ row }) => (
|
<Checkbox
|
||||||
<Checkbox
|
checked={row.getIsSelected()}
|
||||||
checked={row.getIsSelected()}
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
aria-label="Select row"
|
||||||
aria-label="Select row"
|
/>
|
||||||
/>
|
),
|
||||||
),
|
enableSorting: false,
|
||||||
enableSorting: false,
|
enableHiding: false,
|
||||||
enableHiding: false,
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: "name",
|
||||||
accessorKey: "name",
|
header: "Name",
|
||||||
header: "Name",
|
cell: ({ row }) => (
|
||||||
cell: ({ row }) => (
|
<div className="flex items-center gap-x-2">
|
||||||
<div className="flex items-center gap-x-2">
|
<FileIcon />
|
||||||
<FileIcon />
|
{row.getValue("name")}
|
||||||
{row.getValue("name")}
|
</div>
|
||||||
</div>
|
),
|
||||||
)
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: "cid",
|
||||||
accessorKey: "cid",
|
header: "CID",
|
||||||
header: "CID",
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: "size",
|
||||||
accessorKey: "size",
|
header: "Size",
|
||||||
header: "Size",
|
},
|
||||||
},
|
{
|
||||||
{
|
accessorKey: "pinned",
|
||||||
accessorKey: "pinned",
|
header: "Pinned On",
|
||||||
size: 200,
|
cell: ({ row }) => new Date(row.getValue("pinned")).toLocaleString(),
|
||||||
header: "Pinned On",
|
},
|
||||||
cell: ({ row }) => (
|
{
|
||||||
<div className="flex items-center justify-between">
|
accessorKey: "actions",
|
||||||
{format(row.getValue("pinned")) as unknown as string}
|
header: () => null,
|
||||||
<DropdownMenu>
|
size: 20,
|
||||||
<DropdownMenuTrigger className={
|
cell: ({ row }) => (
|
||||||
cn("hidden group-hover:block data-[state=open]:block", row.getIsSelected() && "block")
|
<div className="flex w-5 items-center justify-between">
|
||||||
}>
|
<DropdownMenu>
|
||||||
<MoreIcon />
|
<DropdownMenuTrigger
|
||||||
</DropdownMenuTrigger>
|
className={cn(
|
||||||
<DropdownMenuContent align="end">
|
"hidden group-hover:block data-[state=open]:block",
|
||||||
<DropdownMenuGroup>
|
row.getIsSelected() && "block",
|
||||||
<DropdownMenuItem>
|
)}>
|
||||||
<DrawingPinIcon className="mr-2" />
|
<MoreIcon />
|
||||||
Ping CID
|
</DropdownMenuTrigger>
|
||||||
</DropdownMenuItem>
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuItem variant="destructive">
|
<DropdownMenuItem variant="destructive">
|
||||||
<TrashIcon className="mr-2" />
|
<TrashIcon className="mr-2" />
|
||||||
Delete
|
Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -18,8 +18,8 @@ import { Field } from "~/components/forms";
|
||||||
export default function FileManager() {
|
export default function FileManager() {
|
||||||
return (
|
return (
|
||||||
<Authenticated key="dashboard" v3LegacyAuthProviderCompatible>
|
<Authenticated key="dashboard" v3LegacyAuthProviderCompatible>
|
||||||
<Dialog>
|
<GeneralLayout>
|
||||||
<GeneralLayout>
|
<Dialog>
|
||||||
<h1 className="font-bold mb-4 text-lg">File Manager</h1>
|
<h1 className="font-bold mb-4 text-lg">File Manager</h1>
|
||||||
<FileCardList>
|
<FileCardList>
|
||||||
<FileCard
|
<FileCard
|
||||||
|
@ -67,23 +67,26 @@ export default function FileManager() {
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
</div>
|
</div>
|
||||||
<DataTable columns={columns} />
|
<DataTable columns={columns} />
|
||||||
</GeneralLayout>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Pinning Contnet</DialogTitle>
|
<DialogTitle>Pin Content</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogContent>
|
<form action="" className="w-full flex flex-col gap-y-4">
|
||||||
<form action="" className="w-full flex flex-col gap-y-4">
|
<Field
|
||||||
<Field
|
inputProps={{
|
||||||
inputProps={{ name: "cids", placeholder: "Comma separated CIDs" }}
|
name: "cids",
|
||||||
labelProps={{ htmlFor: "cids", children: "Content to Pin" }}
|
placeholder: "Comma separated CIDs",
|
||||||
/>
|
}}
|
||||||
|
labelProps={{ htmlFor: "cids", children: "Content to Pin" }}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button type="submit" className="w-full">
|
<Button type="submit" className="w-full">
|
||||||
Pin Content
|
Pin Content
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
</GeneralLayout>
|
||||||
</Authenticated>
|
</Authenticated>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default function Login() {
|
||||||
go({ to, type: "push" });
|
go({ to, type: "push" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isAuthLoading, authData]);
|
}, [isAuthLoading, authData, parsed, go]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-10 h-screen relative">
|
<div className="p-10 h-screen relative">
|
||||||
|
@ -109,16 +109,18 @@ const LoginForm = () => {
|
||||||
return parseWithZod(formData, { schema: LoginSchema });
|
return parseWithZod(formData, { schema: LoginSchema });
|
||||||
},
|
},
|
||||||
shouldValidate: "onSubmit",
|
shouldValidate: "onSubmit",
|
||||||
onSubmit(e) {
|
onSubmit(e, { submission }) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const data = Object.fromEntries(new FormData(e.currentTarget).entries());
|
if (submission?.status === "success") {
|
||||||
login.mutate({
|
const data = submission.value;
|
||||||
email: data.email.toString(),
|
login.mutate({
|
||||||
password: data.password.toString(),
|
email: data.email,
|
||||||
rememberMe: data.rememberMe.toString() === "on",
|
password: data.password,
|
||||||
redirectTo: parsed.params?.to,
|
rememberMe: data.rememberMe ?? false,
|
||||||
});
|
redirectTo: parsed.params?.to,
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 240, 33%, 3%;
|
--background: 0 0% 3.9%;
|
||||||
--foreground: 0, 0%, 88%;
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
--card: 0 0% 0%;
|
--card: 0 0% 0%;
|
||||||
--card-foreground: 0 0% 3.9%;
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
|
Loading…
Reference in New Issue