Compare commits
3 Commits
5aa62f7d82
...
b646fc4887
Author | SHA1 | Date |
---|---|---|
Tania Gutierrez | b646fc4887 | |
Tania Gutierrez | 140a9d222f | |
Tania Gutierrez | 52fc50d480 |
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 };
|
|
@ -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");
|
||||||
|
|
|
@ -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;
|
|
@ -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 }> {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue