feat: Created Pinning banner to show inprogress and completed PinningProcess
This commit is contained in:
parent
140a9d222f
commit
b646fc4887
|
@ -30,13 +30,17 @@ import { Avatar } from "@radix-ui/react-avatar";
|
|||
import { cn } from "~/utils";
|
||||
import { useGetIdentity, useLogout } from "@refinedev/core";
|
||||
import { Identity } from "~/data/auth-provider";
|
||||
import { PinningNetworkBanner } from "./pinnning-network-banner";
|
||||
import { PinningProvider } from "~/providers/PinningProvider";
|
||||
|
||||
|
||||
export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
|
||||
const location = useLocation();
|
||||
const { data: identity } = useGetIdentity<Identity>();
|
||||
const{ mutate: logout } = useLogout()
|
||||
|
||||
return (
|
||||
<PinningProvider>
|
||||
<div className="h-full flex flex-row">
|
||||
<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" />
|
||||
|
@ -147,6 +151,8 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
|
|||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<PinningNetworkBanner />
|
||||
</PinningProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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,
|
||||
};
|
|
@ -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 };
|
|
@ -1,8 +1,17 @@
|
|||
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 { 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";
|
||||
|
||||
// This type is used to define the shape of our data.
|
||||
|
@ -14,11 +23,42 @@ export type File = {
|
|||
createdOn: string;
|
||||
};
|
||||
|
||||
declare module '@tanstack/table-core' {
|
||||
interface TableMeta<TData extends RowData> {
|
||||
hoveredRowId: string,
|
||||
}
|
||||
}
|
||||
const CreatedOnCell = ({ row }: { row: Row<File> }) => {
|
||||
// const { open } = useNotification();
|
||||
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>[] = [
|
||||
{
|
||||
|
@ -26,7 +66,6 @@ export const columns: ColumnDef<File>[] = [
|
|||
size: 20,
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
|
@ -53,7 +92,7 @@ export const columns: ColumnDef<File>[] = [
|
|||
<FileIcon />
|
||||
{row.getValue("name")}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "cid",
|
||||
|
@ -67,30 +106,6 @@ export const columns: ColumnDef<File>[] = [
|
|||
accessorKey: "createdOn",
|
||||
size: 200,
|
||||
header: "Created On",
|
||||
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>
|
||||
)
|
||||
}
|
||||
cell: ({ row }) => <CreatedOnCell row={row} />,
|
||||
},
|
||||
];
|
|
@ -17,6 +17,7 @@
|
|||
"@conform-to/zod": "^1.0.2",
|
||||
"@fontsource-variable/manrope": "^5.0.19",
|
||||
"@lumeweb/portal-sdk": "0.0.0-20240319140708",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
|
@ -26,6 +27,7 @@
|
|||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@refinedev/cli": "^2.16.1",
|
||||
"@refinedev/core": "https://gitpkg.now.sh/LumeWeb/refine/packages/core?remix",
|
||||
|
|
Loading…
Reference in New Issue