Compare commits

...

3 Commits

13 changed files with 610 additions and 198 deletions

View File

@ -30,123 +30,129 @@ 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 { Identity } from "~/data/auth-provider";
import { PinningNetworkBanner } from "./pinnning-network-banner";
import { PinningProvider } from "~/providers/PinningProvider";
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()
return ( return (
<div className="h-full flex flex-row"> <PinningProvider>
<header className="p-10 pr-0 flex flex-col w-[240px] h-full scroll-m-0 overflow-hidden"> <div className="h-full flex flex-row">
<img src={logoPng} alt="Lume logo" className="h-10 w-32" /> <header className="p-10 pr-0 flex flex-col w-[240px] h-full scroll-m-0 overflow-hidden">
<img src={logoPng} alt="Lume logo" className="h-10 w-32" />
<nav className="my-10 flex-1"> <nav className="my-10 flex-1">
<ul> <ul>
<li> <li>
<Link to="/dashboard"> <Link to="/dashboard">
<NavigationButton <NavigationButton
active={location.pathname.includes("dashboard")}> active={location.pathname.includes("dashboard")}>
<ClockIcon className="w-5 h-5 mr-2" /> <ClockIcon className="w-5 h-5 mr-2" />
Dashboard Dashboard
</NavigationButton> </NavigationButton>
</Link> </Link>
</li> </li>
<li> <li>
<Link to="/file-manager"> <Link to="/file-manager">
<NavigationButton <NavigationButton
active={location.pathname.includes("file-manager")}> active={location.pathname.includes("file-manager")}>
<DriveIcon className="w-5 h-5 mr-2" /> <DriveIcon className="w-5 h-5 mr-2" />
File Manager File Manager
</NavigationButton> </NavigationButton>
</Link> </Link>
</li> </li>
<li> <li>
<Link to="/account"> <Link to="/account">
<NavigationButton <NavigationButton
active={location.pathname.includes("account")}> active={location.pathname.includes("account")}>
<CircleLockIcon className="w-5 h-5 mr-2" /> <CircleLockIcon className="w-5 h-5 mr-2" />
Account Account
</NavigationButton> </NavigationButton>
</Link> </Link>
</li> </li>
</ul> </ul>
</nav> </nav>
<span className="text-primary-2 mb-3 -space-y-1 opacity-40"> <span className="text-primary-2 mb-3 -space-y-1 opacity-40">
<p>Freedom</p> <p>Freedom</p>
<p>Privacy</p> <p>Privacy</p>
<p>Ownership</p> <p>Ownership</p>
</span> </span>
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button size={"lg"} className="w-[calc(100%-3rem)] font-semibold"> <Button size={"lg"} className="w-[calc(100%-3rem)] font-semibold">
<CloudUploadIcon className="w-5 h-5 mr-2" /> <CloudUploadIcon className="w-5 h-5 mr-2" />
Upload Files Upload Files
</Button>
</DialogTrigger>
<DialogContent className="border rounded-lg p-8">
<UploadFileForm />
</DialogContent>
</Dialog>
</header>
<div className="flex-1 overflow-y-auto p-10">
<div className="flex items-center gap-x-4 justify-end">
<Button variant="ghost" className="rounded-full w-fit">
<ThemeIcon className="text-ring" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button className="border rounded-full h-auto p-2 gap-x-2 text-ring font-semibold">
<Avatar className="bg-ring h-7 w-7 rounded-full" />
{`${identity?.firstName} ${identity?.lastName}`}
<ChevronDownIcon />
</Button> </Button>
</DropdownMenuTrigger> </DialogTrigger>
<DropdownMenuContent align="end"> <DialogContent className="border rounded-lg p-8">
<DropdownMenuGroup> <UploadFileForm />
<DropdownMenuItem onClick={() => logout()}> </DialogContent>
<ExitIcon className="mr-2" /> </Dialog>
Logout </header>
</DropdownMenuItem>
</DropdownMenuGroup> <div className="flex-1 overflow-y-auto p-10">
</DropdownMenuContent> <div className="flex items-center gap-x-4 justify-end">
</DropdownMenu> <Button variant="ghost" className="rounded-full w-fit">
<ThemeIcon className="text-ring" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button className="border rounded-full h-auto p-2 gap-x-2 text-ring font-semibold">
<Avatar className="bg-ring h-7 w-7 rounded-full" />
{`${identity?.firstName} ${identity?.lastName}`}
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem onClick={() => logout()}>
<ExitIcon className="mr-2" />
Logout
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
{children}
<footer className="mt-5">
<ul className="flex flex-row">
<li>
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder">
<img
className="h-5"
src={discordLogoPng}
alt="Discord Logo"
/>
Connect with us
</Button>
</Link>
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder">
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div> </div>
{children}
<footer className="mt-5">
<ul className="flex flex-row">
<li>
<Link to="https://discord.lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder">
<img
className="h-5"
src={discordLogoPng}
alt="Discord Logo"
/>
Connect with us
</Button>
</Link>
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder">
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div> </div>
</div> <PinningNetworkBanner />
</PinningProvider>
); );
}; };

View File

@ -0,0 +1,107 @@
import { useEffect, useMemo } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "./ui/accordion";
import { Progress } from "./ui/progress";
import { usePinning } from "~/hooks/usePinnning";
import { IPinningData } from "~/providers/PinningProvider";
import { Tabs, TabsTrigger, TabsList, TabsContent } from "./ui/tabs";
import { Button } from "./ui/button";
import { Cross2Icon } from "@radix-ui/react-icons";
export const PinningNetworkBanner = () => {
const { cidList } = usePinning();
const itemsLeft = useMemo(
() => cidList.filter((item) => item.progress < 100),
[cidList],
);
const completedItems = useMemo(
() => cidList.filter((item) => item.progress === 100),
[cidList],
);
return (
<div
className={`border border-border rounded-lg absolute w-1/3 bottom-4 right-4 ${
!cidList.length ? "hidden" : "block"
}`}>
<Accordion type="single" defaultValue="item-1" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger className="font-bold bg-primary px-4 rounded-tr-lg rounded-tl-lg">
{itemsLeft.length > 0 ? `${itemsLeft.length} left` : "Completed"}
</AccordionTrigger>
<AccordionContent>
<Tabs className="w-full" defaultValue="inProgress">
<TabsList className="rounded-none">
<TabsTrigger value="inProgress">In Progress</TabsTrigger>
<TabsTrigger value="completed">Completed</TabsTrigger>
</TabsList>
<TabsContent value="inProgress">
{itemsLeft.length ? (
itemsLeft.map((cid) => (
<PinCidItem key={cid.cid} item={cid} />
))
) : (
<div className="text-primary-2 text-sm flex justify-center items-center h-10">
Nothing yet.
</div>
)}
</TabsContent>
<TabsContent value="completed">
{completedItems.length ? (
completedItems.map((cid) => (
<PinCidItem key={cid.cid} item={cid} />
))
) : (
<div className="text-muted text-sm flex justify-center items-center h-10">
Nothing yet.
</div>
)}
</TabsContent>
</Tabs>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
};
const PinCidItem = ({ item }: { item: IPinningData }) => {
const { getProgress, onRemoveCidFromList } = usePinning();
useEffect(() => {
if (item.progress < 100) {
const intervalId = setInterval(() => {
getProgress(item.cid);
}, 1000); // Adjust the interval time (1000ms = 1 second) as needed
return () => clearInterval(intervalId); // Clear interval on component unmount
}
}, [getProgress, item]); // Add dependencies to ensure the effect runs correctly
return (
<div className="px-4 mb-4">
<div className="flex justify-between items-center rounded-lg h-10 py-2 px-4 hover:bg-primary/50 group">
<span className="font-semibold">{item.cid}</span>
<span className="group-hover:hidden">{item.progress}%</span>
<div className="gap-x-2 hidden group-hover:flex">
<Button
variant="ghost"
className="p-2 rounded-full h-6"
onClick={() => onRemoveCidFromList(item.cid)}>
<Cross2Icon />
</Button>
</div>
</div>
<Progress
value={item.progress}
className="h-2 w-[calc(100%-1rem)] ml-2"
/>
</div>
);
};

View File

@ -0,0 +1,58 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { cn } from "~/utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
className,
)}
{...props}>
{children}
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export {
Accordion,
AccordionItem,
AccordionTrigger,
AccordionContent,
};

View File

@ -0,0 +1,53 @@
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "~/utils";
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-primary-dark p-1 text-muted-foreground",
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-primary-1 data-[state=active]:shadow",
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@ -4,10 +4,23 @@ import { SdkProvider } from "~/data/sdk-provider.js";
export const fileProvider = { export const fileProvider = {
getList: () => { getList: () => {
console.log("Not implemented"); console.log("Not implemented");
return Promise.resolve({ return {
data: [], data: [
total: 0, {
}); name: "whirly-final-draft.psd",
cid: "0xB45165ED3CD437B",
size: "1.89 MB",
createdOn: " 03/02/2024 at 13:29 PM",
},
{
name: "whirly-final-draft.psd",
cid: "0xB45165ED3CD437B",
size: "1.89 MB",
createdOn: " 03/02/2024 at 13:29 PM",
},
],
total: 2
}
}, },
getOne: () => { getOne: () => {
console.log("Not implemented"); console.log("Not implemented");

View File

@ -0,0 +1,66 @@
import { SdkProvider } from "~/data/sdk-provider.js";
import { PinningProcess } from "./pinning";
export const pinningProvider = {
getList: () => {
console.log("Not implemented");
return {
data: [],
total: 0,
};
},
getOne: () => {
console.log("Not implemented");
return Promise.resolve({
data: {
id: 1,
},
});
},
update: () => {
console.log("Not implemented");
return Promise.resolve({
data: {},
});
},
create: () => {
console.log("Not implemented");
return Promise.resolve({
data: {},
});
},
deleteOne: () => {
console.log("Not implemented");
return Promise.resolve({
data: {},
});
},
getApiUrl: () => "",
custom: () => {
const pinCid = async (cid: string) => {
return await PinningProcess.pin(cid);
}
const unpinCid = async (cid: string) => {
console.log("Not Implemented");
}
const checkCid = async (cid: string) => {
console.log("Not Implemented");
}
const checkCidProgress = (cid: string) => {
const progressGenerator = PinningProcess.pollProgress(cid);
return progressGenerator.next();
}
return {
pinCid,
unpinCid,
checkCid,
checkCidProgress
}
},
} satisfies SdkProvider;

View File

@ -5,7 +5,7 @@ interface PinningStatus {
} }
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation> // biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
class PinningProcess { export class PinningProcess {
private static instances: Map<string, PinningStatus> = new Map(); private static instances: Map<string, PinningStatus> = new Map();
static async pin(id: string): Promise<{ success: boolean; message: string }> { static async pin(id: string): Promise<{ success: boolean; message: string }> {

View File

@ -1,14 +1,14 @@
import type {AuthProvider, DataProvider} from "@refinedev/core"; import type {AuthProvider} from "@refinedev/core";
import {fileProvider} from "~/data/file-provider.js"; import {fileProvider} from "~/data/file-provider.js";
import {Sdk} from "@lumeweb/portal-sdk"; import {Sdk} from "@lumeweb/portal-sdk";
import {accountProvider} from "~/data/account-provider.js"; import {accountProvider} from "~/data/account-provider.js";
import type {SdkProvider} from "~/data/sdk-provider.js"; import type {SdkProvider} from "~/data/sdk-provider.js";
import {createPortalAuthProvider} from "~/data/auth-provider.js"; import {createPortalAuthProvider} from "~/data/auth-provider.js";
import { pinningProvider } from "./pinning-provider";
interface DataProviders { interface DataProviders {
default: SdkProvider; default: SdkProvider;
auth: AuthProvider; auth: AuthProvider;
[key: string]: SdkProvider | AuthProvider; [key: string]: SdkProvider | AuthProvider;
} }
@ -25,6 +25,7 @@ export function getProviders(sdk: Sdk) {
default: accountProvider, default: accountProvider,
auth: createPortalAuthProvider(sdk), auth: createPortalAuthProvider(sdk),
files: fileProvider, files: fileProvider,
pinning: pinningProvider
}; };
return providers; return providers;

62
app/hooks/usePinnning.ts Normal file
View File

@ -0,0 +1,62 @@
import { useCustom, useNotification } from "@refinedev/core";
import { usePinningContext } from "~/providers/PinningProvider";
export const usePinning = () => {
const { open } = useNotification();
const { data: cidList, setData } = usePinningContext();
const { data } = useCustom({
// useCustom requires URL and METHOD params
url: "",
method: "get",
dataProviderName: "pinning",
});
const onPin = async (cid: string) => {
if (!data?.pinCid) return;
const response = await data?.pinCid(cid);
if (response.success) {
setData((prev) => [...prev, { cid, progress: 0 }]);
} else {
open?.({
type: "destrunctive",
message: "Erorr pinning " + cid,
description: response.message,
});
}
};
const getProgress = (cid: string) => {
if (!data?.checkCidProgress) return;
const response = data?.checkCidProgress(cid);
if (!response.done) {
setData((prev) =>
prev.map((cidInfo) => {
const newData = cidInfo;
if (cidInfo.cid === cid) {
newData.progress = response.value.progress;
return newData;
}
return cidInfo;
}),
);
}
};
const onRemoveCidFromList = (cid: string) => {
setData((prev) => prev.filter((item) => item.cid !== cid));
};
return {
cidList,
onPin,
getProgress,
onRemoveCidFromList,
};
};

View File

@ -0,0 +1,25 @@
import { Dispatch, SetStateAction, createContext, useContext, useState } from "react";
export interface IPinningData {
cid: string;
progress: number
}
export interface IPinningContextType {
data: IPinningData[],
setData: Dispatch<SetStateAction<IPinningData[]>>
}
export const PinningContext = createContext<IPinningContextType>({} as IPinningContextType);
export const usePinningContext = () => useContext(PinningContext);
export const PinningProvider = ({ children }: React.PropsWithChildren) => {
const [data, setData] = useState<IPinningData[]>([]);
return (
<PinningContext.Provider value={{ data, setData }}>
{children}
</PinningContext.Provider>
)
}

View File

@ -38,6 +38,7 @@ export function Layout({children}: { children: React.ReactNode }) {
} }
export default function App() { export default function App() {
console.log(import.meta.env.VITE_PORTAL_URL);
const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL) const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL)
const providers = getProviders(sdk); const providers = getProviders(sdk);
return ( return (
@ -45,7 +46,10 @@ export default function App() {
authProvider={providers.auth} authProvider={providers.auth}
routerProvider={routerProvider} routerProvider={routerProvider}
notificationProvider={notificationProvider} notificationProvider={notificationProvider}
dataProvider={providers.default} dataProvider={{
default: providers.default,
pinning: providers.pinning
}}
resources={resources} resources={resources}
options={{disableTelemetry: true}} options={{disableTelemetry: true}}
> >

View File

@ -1,96 +1,111 @@
import { DrawingPinIcon, TrashIcon } from "@radix-ui/react-icons"; import { DrawingPinIcon, TrashIcon } from "@radix-ui/react-icons";
import type { ColumnDef, RowData } from "@tanstack/react-table"; import type { ColumnDef, Row } 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 { usePinning } from "~/hooks/usePinnning";
import { cn } from "~/utils"; import { cn } from "~/utils";
// This type is used to define the shape of our data. // This type is used to define the shape of our data.
// You can use a Zod schema here if you want. // You can use a Zod schema here if you want.
export type File = { export type File = {
name: string; name: string;
cid: string; cid: string;
size: string; size: string;
createdOn: string; createdOn: string;
}; };
declare module '@tanstack/table-core' { const CreatedOnCell = ({ row }: { row: Row<File> }) => {
interface TableMeta<TData extends RowData> { // const { open } = useNotification();
hoveredRowId: string, const { onPin } = usePinning();
}
} return (
<div className="flex items-center justify-between">
{row.getValue("createdOn")}
<DropdownMenu>
<DropdownMenuTrigger
className={cn(
"hidden group-hover:block data-[state=open]:block",
row.getIsSelected() && "block",
)}>
<MoreIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() => {
console.log(`Adding ${row.getValue("cid")} for pinning...`);
onPin(row.getValue("cid"));
}}>
<DrawingPinIcon className="mr-2" />
Ping CID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<TrashIcon className="mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
};
export const columns: ColumnDef<File>[] = [ export const columns: ColumnDef<File>[] = [
{ {
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: "createdOn",
accessorKey: "createdOn", size: 200,
size: 200, header: "Created On",
header: "Created On", cell: ({ row }) => <CreatedOnCell row={row} />,
cell: ({ row }) => ( },
<div className="flex items-center justify-between"> ];
{row.getValue("createdOn")}
<DropdownMenu>
<DropdownMenuTrigger className={
cn("hidden group-hover:block data-[state=open]:block", row.getIsSelected() && "block")
}>
<MoreIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem>
<DrawingPinIcon className="mr-2" />
Ping CID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<TrashIcon className="mr-2" />
Delete
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}
];

View File

@ -17,6 +17,7 @@
"@conform-to/zod": "^1.0.2", "@conform-to/zod": "^1.0.2",
"@fontsource-variable/manrope": "^5.0.19", "@fontsource-variable/manrope": "^5.0.19",
"@lumeweb/portal-sdk": "0.0.0-20240319140708", "@lumeweb/portal-sdk": "0.0.0-20240319140708",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
@ -26,6 +27,7 @@
"@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toast": "^1.1.5",
"@refinedev/cli": "^2.16.1", "@refinedev/cli": "^2.16.1",
"@refinedev/core": "https://gitpkg.now.sh/LumeWeb/refine/packages/core?remix", "@refinedev/core": "https://gitpkg.now.sh/LumeWeb/refine/packages/core?remix",