Merge branch 'develop' of git.lumeweb.com:LumeWeb/portal-dashboard into develop
This commit is contained in:
commit
db91cb9590
|
@ -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"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -1,15 +1,109 @@
|
||||||
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) {
|
||||||
|
abort.abort();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getOne: () => {
|
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<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");
|
console.log("Not implemented");
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {
|
data: {
|
||||||
|
@ -17,23 +111,25 @@ export const fileProvider = {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
update: () => {
|
update() {
|
||||||
console.log("Not implemented");
|
console.log("Not implemented");
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
create: () => {
|
create() {
|
||||||
console.log("Not implemented");
|
console.log("Not implemented");
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteOne: () => {
|
deleteOne() {
|
||||||
console.log("Not implemented");
|
console.log("Not implemented");
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getApiUrl: () => "",
|
getApiUrl() {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
} satisfies SdkProvider;
|
} satisfies SdkProvider;
|
||||||
|
|
30
app/root.tsx
30
app/root.tsx
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,7 +13,7 @@ declare module '@tanstack/table-core' {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const columns: ColumnDef<File>[] = [
|
export const columns: ColumnDef<FileItem>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
size: 20,
|
size: 20,
|
||||||
|
@ -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")
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
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 (
|
||||||
|
<Authenticated key="dashboard" v3LegacyAuthProviderCompatible>
|
||||||
<GeneralLayout>
|
<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>
|
||||||
|
@ -57,5 +59,6 @@ export default function FileManager() {
|
||||||
columns={columns}
|
columns={columns}
|
||||||
/>
|
/>
|
||||||
</GeneralLayout>
|
</GeneralLayout>
|
||||||
|
</Authenticated>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue