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({
|
||||
columns,
|
||||
refineCoreProps: {
|
||||
resource: "files"
|
||||
resource: "file"
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<AuthActionResponse> {
|
||||
const ret = await sdk.account().login({
|
||||
|
@ -105,11 +119,11 @@ export const createPortalAuthProvider = (sdk: Sdk): AuthProvider => {
|
|||
async check(params?: any): Promise<CheckResponse> {
|
||||
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> {
|
||||
return {logout: true};
|
||||
return {};
|
||||
},
|
||||
|
||||
async register(params: RegisterFormRequest): Promise<AuthActionResponse> {
|
||||
|
|
|
@ -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<boolean | number> {
|
||||
|
||||
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<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;
|
||||
|
|
32
app/root.tsx
32
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 }) {
|
|||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<Toaster />
|
||||
<Toaster/>
|
||||
<ScrollRestoration/>
|
||||
<Scripts/>
|
||||
</body>
|
||||
|
@ -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 (
|
||||
<Refine
|
||||
authProvider={providers.auth}
|
||||
routerProvider={routerProvider}
|
||||
notificationProvider={notificationProvider}
|
||||
dataProvider={providers.default}
|
||||
dataProvider={providers}
|
||||
resources={resources}
|
||||
options={{disableTelemetry: true}}
|
||||
>
|
||||
<SdkContextProvider sdk={sdk}>
|
||||
<Outlet/>
|
||||
</SdkContextProvider>
|
||||
<Outlet/>
|
||||
</Refine>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Root() {
|
||||
const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL)
|
||||
return (
|
||||
<SdkContextProvider sdk={sdk}>
|
||||
<App/>
|
||||
</SdkContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function HydrateFallback() {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
|
|
@ -313,7 +313,7 @@ const ChangePasswordSchema = z
|
|||
});
|
||||
|
||||
const ChangePasswordForm = () => {
|
||||
const { mutate: updatePassword } = useUpdatePassword<{ password: string }>();
|
||||
const { mutate: updatePassword } = useUpdatePassword<UpdatePasswordFormRequest>();
|
||||
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(),
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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<TData extends RowData> {
|
||||
|
@ -20,13 +13,13 @@ declare module '@tanstack/table-core' {
|
|||
}
|
||||
}
|
||||
|
||||
export const columns: ColumnDef<File>[] = [
|
||||
export const columns: ColumnDef<FileItem>[] = [
|
||||
{
|
||||
id: "select",
|
||||
size: 20,
|
||||
header: ({ table }) => (
|
||||
<Checkbox
|
||||
|
||||
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
|
@ -64,12 +57,12 @@ export const columns: ColumnDef<File>[] = [
|
|||
header: "Size",
|
||||
},
|
||||
{
|
||||
accessorKey: "createdOn",
|
||||
accessorKey: "pinned",
|
||||
size: 200,
|
||||
header: "Created On",
|
||||
header: "Pinned On",
|
||||
cell: ({ row }) => (
|
||||
<div className="flex items-center justify-between">
|
||||
{row.getValue("createdOn")}
|
||||
{format(row.getValue("pinned")) as unknown as string}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger className={
|
||||
cn("hidden group-hover:block data-[state=open]:block", row.getIsSelected() && "block")
|
||||
|
@ -93,4 +86,4 @@ export const columns: ColumnDef<File>[] = [
|
|||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
];
|
||||
|
|
|
@ -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 (
|
||||
<GeneralLayout>
|
||||
<Authenticated key="dashboard" v3LegacyAuthProviderCompatible>
|
||||
<GeneralLayout>
|
||||
<h1 className="font-bold mb-4 text-lg">File Manager</h1>
|
||||
<FileCardList>
|
||||
<FileCard
|
||||
|
@ -57,5 +59,6 @@ export default function FileManager() {
|
|||
columns={columns}
|
||||
/>
|
||||
</GeneralLayout>
|
||||
</Authenticated>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue