diff --git a/app/components/data-table.tsx b/app/components/data-table.tsx index 79f9b5b..7059bde 100644 --- a/app/components/data-table.tsx +++ b/app/components/data-table.tsx @@ -27,7 +27,7 @@ export function DataTable({ const table = useTable({ columns, refineCoreProps: { - resource: "files" + resource: "file" } }) diff --git a/app/data/account-provider.ts b/app/data/account-provider.ts index 45cb5ac..23276b1 100644 --- a/app/data/account-provider.ts +++ b/app/data/account-provider.ts @@ -30,7 +30,7 @@ export const accountProvider: SdkProvider = { if (ret) { if (ret instanceof Error) { - return Promise.reject(ret) + return Promise.reject(ret satisfies HttpError) } } else { return Promise.reject(); diff --git a/app/data/auth-provider.ts b/app/data/auth-provider.ts index 25acbc6..b3a56fe 100644 --- a/app/data/auth-provider.ts +++ b/app/data/auth-provider.ts @@ -1,4 +1,4 @@ -import type {AuthProvider, UpdatePasswordFormTypes} from "@refinedev/core" +import type {AuthProvider, HttpError, UpdatePasswordFormTypes} from "@refinedev/core" import type { AuthActionResponse, @@ -8,10 +8,9 @@ import type { SuccessNotificationResponse // @ts-ignore } from "@refinedev/core/dist/interfaces/bindings/auth" -import {Sdk} from "@lumeweb/portal-sdk"; +import {Sdk, AccountError} from "@lumeweb/portal-sdk"; import type {AccountInfoResponse} from "@lumeweb/portal-sdk"; -; export type AuthFormRequest = { email: string; @@ -54,12 +53,16 @@ export const createPortalAuthProvider = (sdk: Sdk): AuthProvider => { successCb?: () => void; } + interface CheckResponseResult extends ResponseResult { + authenticated?: boolean; + } + const handleResponse = (result: ResponseResult): AuthActionResponse => { if (result.ret) { - if (result.ret instanceof Error) { + if (result.ret instanceof AccountError) { return { success: false, - error: result.ret, + error: result.ret satisfies HttpError, redirectTo: result.redirectToError } } @@ -79,6 +82,17 @@ export const createPortalAuthProvider = (sdk: Sdk): AuthProvider => { } } + const handleCheckResponse = (result: CheckResponseResult): CheckResponse => { + const response = handleResponse(result); + const success = response.success; + delete response.success; + + return { + ...response, + authenticated: success + } + } + return { async login(params: AuthFormRequest): Promise { const ret = await sdk.account().login({ @@ -105,11 +119,11 @@ export const createPortalAuthProvider = (sdk: Sdk): AuthProvider => { async check(params?: any): Promise { const ret = await sdk.account().ping(); - return handleResponse({ret, redirectToError: "/login", successCb: maybeSetupAuth}); + return handleCheckResponse({ret, redirectToError: "/login", successCb: maybeSetupAuth}); }, async onError(error: any): Promise { - return {logout: true}; + return {}; }, async register(params: RegisterFormRequest): Promise { diff --git a/app/data/file-provider.ts b/app/data/file-provider.ts index dc6528a..4206821 100644 --- a/app/data/file-provider.ts +++ b/app/data/file-provider.ts @@ -1,39 +1,135 @@ -import type { DataProvider } from "@refinedev/core"; -import { SdkProvider } from "~/data/sdk-provider.js"; +import {SdkProvider} from "~/data/sdk-provider.js"; +import {S5Client} from "@lumeweb/s5-js"; +import {PROTOCOL_S5} from "@lumeweb/portal-sdk"; +import {Multihash} from "@lumeweb/libs5/lib/multihash.js"; +import {AxiosProgressEvent} from "axios"; +import {CID, CID_TYPES, METADATA_TYPES, metadataMagicByte, Unpacker} from "@lumeweb/libs5"; -export const fileProvider = { - getList: () => { - console.log("Not implemented"); - return Promise.resolve({ - 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: () => "", +async function getIsManifest(s5: S5Client, hash: string): Promise { + + let type: number | null; + try { + const abort = new AbortController(); + const resp = s5.downloadData(hash, { + onDownloadProgress: (progressEvent: AxiosProgressEvent) => { + if (progressEvent.loaded >= 10) { + abort.abort(); + } + }, + httpConfig: { + signal: abort.signal, + }, + }); + + const data = await resp; + const unpacker = Unpacker.fromPacked(Buffer.from(data)); + try { + const magic = unpacker.unpackInt(); + + if (magic !== metadataMagicByte) { + return false; + } + + type = unpacker.unpackInt(); + + if (!type || !Object.values(METADATA_TYPES).includes(type)) { + return false; + } + } catch (e) { + return false; + } + + } catch (e) { + return false; + } + + switch (type) { + case METADATA_TYPES.DIRECTORY: + return CID_TYPES.DIRECTORY; + case METADATA_TYPES.WEBAPP: + return CID_TYPES.METADATA_WEBAPP; + case METADATA_TYPES.MEDIA: + return CID_TYPES.METADATA_MEDIA; + case METADATA_TYPES.USER_IDENTITY: + return CID_TYPES.USER_IDENTITY; + } + + return 0; +} + +export interface FileItem { + cid: string; + type: string; + size: number; + mimeType: string; + pinned: string; +} + +export const fileProvider: SdkProvider = { + sdk: undefined, + async getList() { + const items: FileItem[] = []; + try { + const s5 = fileProvider.sdk?.protocols().get(PROTOCOL_S5)!.getSdk()!; + const pinList = await s5.accountPins(); + for (const pin of pinList!.pins) { + const manifest = await getIsManifest(s5, pin.hash) as number; + + if (manifest) { + const mHash = Multihash.fromBase64Url(pin.hash); + items.push({ + cid: new CID(manifest, mHash, pin.size).toString(), + type: "manifest", + mimeType: "application/octet-stream", + size: pin.size, + pinned: pin.pinned_at, + }); + } else { + items.push({ + cid: new CID(CID_TYPES.RAW, Multihash.fromBase64Url(pin.hash), pin.size).toString(), + type: "raw", + mimeType: pin.mime_type, + size: pin.size, + pinned: pin.pinned_at, + }); + } + } + } catch (e) { + return Promise.reject(e); + } + + return { + data: items, + total: items.length, + }; + }, + 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() { + return ""; + }, } satisfies SdkProvider; diff --git a/app/root.tsx b/app/root.tsx index 5f17966..6d099b6 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -7,15 +7,16 @@ import type {LinksFunction} from "@remix-run/node"; import '@fontsource-variable/manrope'; import {Refine} from "@refinedev/core"; import routerProvider from "@refinedev/remix-router"; -import { notificationProvider } from "~/data/notification-provider"; -import {SdkContextProvider} from "~/components/lib/sdk-context"; -import { Toaster } from "~/components/ui/toaster"; +import {notificationProvider} from "~/data/notification-provider"; +import {SdkContextProvider, useSdk} from "~/components/lib/sdk-context"; +import {Toaster} from "~/components/ui/toaster"; import {getProviders} from "~/data/providers.js"; import {Sdk} from "@lumeweb/portal-sdk"; import resources from "~/data/resources.js"; +import {useMemo} from "react"; export const links: LinksFunction = () => [ - { rel: "stylesheet", href: stylesheet }, + {rel: "stylesheet", href: stylesheet}, ]; export function Layout({children}: { children: React.ReactNode }) { @@ -29,7 +30,7 @@ export function Layout({children}: { children: React.ReactNode }) { {children} - + @@ -37,25 +38,32 @@ export function Layout({children}: { children: React.ReactNode }) { ); } -export default function App() { - const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL) - const providers = getProviders(sdk); +function App() { + const sdk = useSdk(); + const providers = useMemo(() => getProviders(sdk as Sdk), [sdk]); return ( - - - + ); } +export default function Root() { + const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL) + return ( + + + + ); +} + export function HydrateFallback() { return

Loading...

; } diff --git a/app/routes/account.tsx b/app/routes/account.tsx index dedf176..6b6318c 100644 --- a/app/routes/account.tsx +++ b/app/routes/account.tsx @@ -313,7 +313,7 @@ const ChangePasswordSchema = z }); const ChangePasswordForm = () => { - const { mutate: updatePassword } = useUpdatePassword<{ password: string }>(); + const { mutate: updatePassword } = useUpdatePassword(); const [form, fields] = useForm({ id: "login", constraint: getZodConstraint(ChangePasswordSchema), @@ -327,6 +327,7 @@ const ChangePasswordForm = () => { const data = Object.fromEntries(new FormData(e.currentTarget).entries()); updatePassword({ + currentPassword: data.currentPassword.toString(), password: data.newPassword.toString(), }); }, diff --git a/app/routes/file-manager/columns.tsx b/app/routes/file-manager/columns.tsx index b4a1f2b..5bd1aa0 100644 --- a/app/routes/file-manager/columns.tsx +++ b/app/routes/file-manager/columns.tsx @@ -4,15 +4,8 @@ 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 { cn } from "~/utils"; - -// This type is used to define the shape of our data. -// You can use a Zod schema here if you want. -export type File = { - name: string; - cid: string; - size: string; - createdOn: string; -}; +import {FileItem} from "~/data/file-provider.js"; +import {format} from "date-fns/fp"; declare module '@tanstack/table-core' { interface TableMeta { @@ -20,13 +13,13 @@ declare module '@tanstack/table-core' { } } -export const columns: ColumnDef[] = [ +export const columns: ColumnDef[] = [ { id: "select", size: 20, header: ({ table }) => ( [] = [ header: "Size", }, { - accessorKey: "createdOn", + accessorKey: "pinned", size: 200, - header: "Created On", + header: "Pinned On", cell: ({ row }) => (
- {row.getValue("createdOn")} + {format(row.getValue("pinned")) as unknown as string} [] = [
) } -]; \ No newline at end of file +]; diff --git a/app/routes/file-manager/index.tsx b/app/routes/file-manager/index.tsx index 96a5777..31e3a3a 100644 --- a/app/routes/file-manager/index.tsx +++ b/app/routes/file-manager/index.tsx @@ -1,14 +1,16 @@ -import { GeneralLayout } from "~/components/general-layout"; -import { FileCard, FileCardList, FileTypes } from "~/components/file-card"; -import { DataTable } from "~/components/data-table"; -import { columns } from "./columns"; -import { Input } from "~/components/ui/input"; -import { Button } from "~/components/ui/button"; -import { AddIcon } from "~/components/icons"; +import {GeneralLayout} from "~/components/general-layout"; +import {FileCard, FileCardList, FileTypes} from "~/components/file-card"; +import {DataTable} from "~/components/data-table"; +import {columns} from "./columns"; +import {Input} from "~/components/ui/input"; +import {Button} from "~/components/ui/button"; +import {AddIcon} from "~/components/icons"; +import {Authenticated} from "@refinedev/core"; export default function FileManager() { return ( - + +

File Manager

+
); } diff --git a/package.json b/package.json index 7a50c58..8acf6d0 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@conform-to/react": "^1.0.2", "@conform-to/zod": "^1.0.2", "@fontsource-variable/manrope": "^5.0.19", - "@lumeweb/portal-sdk": "0.0.0-20240320165911", + "@lumeweb/portal-sdk": "0.0.0-20240321203634", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", @@ -42,6 +42,7 @@ "@visx/visx": "^3.10.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "date-fns": "^3.6.0", "react": "^18.2.0", "react-dom": "^18.2.0", "tailwind-merge": "^2.2.1",