Merge branch 'develop' of git.lumeweb.com:LumeWeb/portal-dashboard into develop

This commit is contained in:
Juan Di Toro 2024-03-22 11:43:16 +01:00
commit db91cb9590
9 changed files with 199 additions and 83 deletions

View File

@ -27,7 +27,7 @@ export function DataTable<TData extends BaseRecord, TValue>({
const table = useTable({ const table = useTable({
columns, columns,
refineCoreProps: { refineCoreProps: {
resource: "files" resource: "file"
} }
}) })

View File

@ -30,7 +30,7 @@ export const accountProvider: SdkProvider = {
if (ret) { if (ret) {
if (ret instanceof Error) { if (ret instanceof Error) {
return Promise.reject(ret) return Promise.reject(ret satisfies HttpError)
} }
} else { } else {
return Promise.reject(); return Promise.reject();

View File

@ -1,4 +1,4 @@
import type {AuthProvider, UpdatePasswordFormTypes} from "@refinedev/core" import type {AuthProvider, HttpError, UpdatePasswordFormTypes} from "@refinedev/core"
import type { import type {
AuthActionResponse, AuthActionResponse,
@ -8,10 +8,9 @@ import type {
SuccessNotificationResponse SuccessNotificationResponse
// @ts-ignore // @ts-ignore
} from "@refinedev/core/dist/interfaces/bindings/auth" } 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"; import type {AccountInfoResponse} from "@lumeweb/portal-sdk";
;
export type AuthFormRequest = { export type AuthFormRequest = {
email: string; email: string;
@ -54,12 +53,16 @@ export const createPortalAuthProvider = (sdk: Sdk): AuthProvider => {
successCb?: () => void; successCb?: () => void;
} }
interface CheckResponseResult extends ResponseResult {
authenticated?: boolean;
}
const handleResponse = (result: ResponseResult): AuthActionResponse => { const handleResponse = (result: ResponseResult): AuthActionResponse => {
if (result.ret) { if (result.ret) {
if (result.ret instanceof Error) { if (result.ret instanceof AccountError) {
return { return {
success: false, success: false,
error: result.ret, error: result.ret satisfies HttpError,
redirectTo: result.redirectToError 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 { return {
async login(params: AuthFormRequest): Promise<AuthActionResponse> { async login(params: AuthFormRequest): Promise<AuthActionResponse> {
const ret = await sdk.account().login({ const ret = await sdk.account().login({
@ -105,11 +119,11 @@ export const createPortalAuthProvider = (sdk: Sdk): AuthProvider => {
async check(params?: any): Promise<CheckResponse> { async check(params?: any): Promise<CheckResponse> {
const ret = await sdk.account().ping(); 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<OnErrorResponse> { async onError(error: any): Promise<OnErrorResponse> {
return {logout: true}; return {};
}, },
async register(params: RegisterFormRequest): Promise<AuthActionResponse> { async register(params: RegisterFormRequest): Promise<AuthActionResponse> {

View File

@ -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 = { async function getIsManifest(s5: S5Client, hash: string): Promise<boolean | number> {
getList: () => {
console.log("Not implemented"); let type: number | null;
return Promise.resolve({ try {
data: [], const abort = new AbortController();
total: 0, const resp = s5.downloadData(hash, {
}); onDownloadProgress: (progressEvent: AxiosProgressEvent) => {
}, if (progressEvent.loaded >= 10) {
getOne: () => { abort.abort();
console.log("Not implemented"); }
return Promise.resolve({ },
data: { httpConfig: {
id: 1 signal: abort.signal,
}, },
}); });
},
update: () => { const data = await resp;
console.log("Not implemented"); const unpacker = Unpacker.fromPacked(Buffer.from(data));
return Promise.resolve({ try {
data: {}, const magic = unpacker.unpackInt();
});
}, if (magic !== metadataMagicByte) {
create: () => { return false;
console.log("Not implemented"); }
return Promise.resolve({
data: {}, type = unpacker.unpackInt();
});
}, if (!type || !Object.values(METADATA_TYPES).includes(type)) {
deleteOne: () => { return false;
console.log("Not implemented"); }
return Promise.resolve({ } catch (e) {
data: {}, return false;
}); }
},
getApiUrl: () => "", } 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<S5Client>(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; } satisfies SdkProvider;

View File

@ -7,15 +7,16 @@ import type {LinksFunction} from "@remix-run/node";
import '@fontsource-variable/manrope'; import '@fontsource-variable/manrope';
import {Refine} from "@refinedev/core"; import {Refine} from "@refinedev/core";
import routerProvider from "@refinedev/remix-router"; import routerProvider from "@refinedev/remix-router";
import { notificationProvider } from "~/data/notification-provider"; import {notificationProvider} from "~/data/notification-provider";
import {SdkContextProvider} from "~/components/lib/sdk-context"; import {SdkContextProvider, useSdk} from "~/components/lib/sdk-context";
import { Toaster } from "~/components/ui/toaster"; import {Toaster} from "~/components/ui/toaster";
import {getProviders} from "~/data/providers.js"; import {getProviders} from "~/data/providers.js";
import {Sdk} from "@lumeweb/portal-sdk"; import {Sdk} from "@lumeweb/portal-sdk";
import resources from "~/data/resources.js"; import resources from "~/data/resources.js";
import {useMemo} from "react";
export const links: LinksFunction = () => [ export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet }, {rel: "stylesheet", href: stylesheet},
]; ];
export function Layout({children}: { children: React.ReactNode }) { export function Layout({children}: { children: React.ReactNode }) {
@ -29,7 +30,7 @@ export function Layout({children}: { children: React.ReactNode }) {
</head> </head>
<body> <body>
{children} {children}
<Toaster /> <Toaster/>
<ScrollRestoration/> <ScrollRestoration/>
<Scripts/> <Scripts/>
</body> </body>
@ -37,25 +38,32 @@ export function Layout({children}: { children: React.ReactNode }) {
); );
} }
export default function App() { function App() {
const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL) const sdk = useSdk();
const providers = getProviders(sdk); const providers = useMemo(() => getProviders(sdk as Sdk), [sdk]);
return ( return (
<Refine <Refine
authProvider={providers.auth} authProvider={providers.auth}
routerProvider={routerProvider} routerProvider={routerProvider}
notificationProvider={notificationProvider} notificationProvider={notificationProvider}
dataProvider={providers.default} dataProvider={providers}
resources={resources} resources={resources}
options={{disableTelemetry: true}} options={{disableTelemetry: true}}
> >
<SdkContextProvider sdk={sdk}> <Outlet/>
<Outlet/>
</SdkContextProvider>
</Refine> </Refine>
); );
} }
export default function Root() {
const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL)
return (
<SdkContextProvider sdk={sdk}>
<App/>
</SdkContextProvider>
);
}
export function HydrateFallback() { export function HydrateFallback() {
return <p>Loading...</p>; return <p>Loading...</p>;
} }

View File

@ -313,7 +313,7 @@ const ChangePasswordSchema = z
}); });
const ChangePasswordForm = () => { const ChangePasswordForm = () => {
const { mutate: updatePassword } = useUpdatePassword<{ password: string }>(); const { mutate: updatePassword } = useUpdatePassword<UpdatePasswordFormRequest>();
const [form, fields] = useForm({ const [form, fields] = useForm({
id: "login", id: "login",
constraint: getZodConstraint(ChangePasswordSchema), constraint: getZodConstraint(ChangePasswordSchema),
@ -327,6 +327,7 @@ const ChangePasswordForm = () => {
const data = Object.fromEntries(new FormData(e.currentTarget).entries()); const data = Object.fromEntries(new FormData(e.currentTarget).entries());
updatePassword({ updatePassword({
currentPassword: data.currentPassword.toString(),
password: data.newPassword.toString(), password: data.newPassword.toString(),
}); });
}, },

View File

@ -4,15 +4,8 @@ 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";
// This type is used to define the shape of our data. import {format} from "date-fns/fp";
// You can use a Zod schema here if you want.
export type File = {
name: string;
cid: string;
size: string;
createdOn: string;
};
declare module '@tanstack/table-core' { declare module '@tanstack/table-core' {
interface TableMeta<TData extends RowData> { interface TableMeta<TData extends RowData> {
@ -20,13 +13,13 @@ declare module '@tanstack/table-core' {
} }
} }
export const columns: ColumnDef<File>[] = [ 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")
@ -64,12 +57,12 @@ export const columns: ColumnDef<File>[] = [
header: "Size", header: "Size",
}, },
{ {
accessorKey: "createdOn", accessorKey: "pinned",
size: 200, size: 200,
header: "Created On", header: "Pinned On",
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{row.getValue("createdOn")} {format(row.getValue("pinned")) as unknown as string}
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger className={ <DropdownMenuTrigger className={
cn("hidden group-hover:block data-[state=open]:block", row.getIsSelected() && "block") cn("hidden group-hover:block data-[state=open]:block", row.getIsSelected() && "block")
@ -93,4 +86,4 @@ export const columns: ColumnDef<File>[] = [
</div> </div>
) )
} }
]; ];

View File

@ -1,14 +1,16 @@
import { GeneralLayout } from "~/components/general-layout"; import {GeneralLayout} from "~/components/general-layout";
import { FileCard, FileCardList, FileTypes } from "~/components/file-card"; import {FileCard, FileCardList, FileTypes} from "~/components/file-card";
import { DataTable } from "~/components/data-table"; import {DataTable} from "~/components/data-table";
import { columns } from "./columns"; import {columns} from "./columns";
import { Input } from "~/components/ui/input"; import {Input} from "~/components/ui/input";
import { Button } from "~/components/ui/button"; import {Button} from "~/components/ui/button";
import { AddIcon } from "~/components/icons"; import {AddIcon} from "~/components/icons";
import {Authenticated} from "@refinedev/core";
export default function FileManager() { export default function FileManager() {
return ( return (
<GeneralLayout> <Authenticated key="dashboard" v3LegacyAuthProviderCompatible>
<GeneralLayout>
<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
@ -57,5 +59,6 @@ export default function FileManager() {
columns={columns} columns={columns}
/> />
</GeneralLayout> </GeneralLayout>
</Authenticated>
); );
} }

View File

@ -16,7 +16,7 @@
"@conform-to/react": "^1.0.2", "@conform-to/react": "^1.0.2",
"@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-20240320165911", "@lumeweb/portal-sdk": "0.0.0-20240321203634",
"@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",
@ -42,6 +42,7 @@
"@visx/visx": "^3.10.2", "@visx/visx": "^3.10.2",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"date-fns": "^3.6.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",