Refine Integration #13
|
@ -108,7 +108,7 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
|
|||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => logout()}>
|
||||
<ExitIcon className="mr-2" />
|
||||
Log Out
|
||||
Logout
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import React from "react";
|
||||
import {Sdk} from "@lumeweb/portal-sdk";
|
||||
|
||||
export const SdkContext = React.createContext<
|
||||
Partial<Sdk>
|
||||
>({});
|
||||
|
||||
export const SdkContextProvider: React.FC< {sdk: Sdk, children: React.ReactNode}> = ({sdk, children}) => {
|
||||
return (
|
||||
<SdkContext.Provider value={sdk}>
|
||||
{children}
|
||||
</SdkContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export function useSdk(): Partial<Sdk>{
|
||||
return React.useContext(SdkContext);
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import Uppy, {BasePlugin, DefaultPluginOptions} from '@uppy/core';
|
||||
import {PROTOCOL_S5, Sdk} from "@lumeweb/portal-sdk";
|
||||
import {S5Client} from "@lumeweb/s5-js";
|
||||
import {AxiosProgressEvent} from "axios";
|
||||
|
||||
|
||||
interface UppyFileUploadOptions extends DefaultPluginOptions {
|
||||
sdk: Sdk;
|
||||
}
|
||||
|
||||
export default class UppyFileUpload extends BasePlugin {
|
||||
private _sdk: Sdk;
|
||||
|
||||
constructor(uppy: Uppy, opts?: UppyFileUploadOptions) {
|
||||
super(uppy, opts);
|
||||
this.id = opts?.id || 'file-upload';
|
||||
this.type = 'uploader';
|
||||
this._sdk = opts?.sdk as Sdk;
|
||||
}
|
||||
|
||||
install() {
|
||||
this.uppy.addUploader(this.handleUpload.bind(this));
|
||||
}
|
||||
|
||||
private async handleUpload(fileIDs: string[]) {
|
||||
for (const fileID of fileIDs) {
|
||||
const file = this.uppy.getFile(fileID);
|
||||
if (!file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (file.uploader !== 'file') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uploadLimit = await this._sdk.account().uploadLimit();
|
||||
|
||||
let data = file.data;
|
||||
|
||||
if (file.data instanceof Blob) {
|
||||
data = new File([data], file.name, {type: file.type});
|
||||
}
|
||||
|
||||
try {
|
||||
await this._sdk.protocols().get<S5Client>(PROTOCOL_S5).getSdk().uploadFile(data as File, {
|
||||
largeFileSize: uploadLimit,
|
||||
onUploadProgress: (progressEvent: AxiosProgressEvent) => {
|
||||
this.uppy.emit('upload-progress', this.uppy.getFile(file.id), {
|
||||
uploader: this,
|
||||
bytesUploaded: progressEvent.loaded,
|
||||
bytesTotal: progressEvent.total,
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
this.uppy.emit('upload-success', file, {uploadURL: null});
|
||||
} catch (err) {
|
||||
this.uppy.emit('upload-error', file, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +1,14 @@
|
|||
import Uppy, { type State, debugLogger } from "@uppy/core"
|
||||
import Uppy, {debugLogger, type State, UppyFile} from "@uppy/core"
|
||||
|
||||
import Tus from "@uppy/tus"
|
||||
import toArray from "@uppy/utils/lib/toArray"
|
||||
|
||||
import {
|
||||
type ChangeEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from "react"
|
||||
import {type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState} from "react"
|
||||
import DropTarget, {type DropTargetOptions} from "./uppy-dropzone"
|
||||
import {useSdk} from "~/components/lib/sdk-context.js";
|
||||
import UppyFileUpload from "~/components/lib/uppy-file-upload.js";
|
||||
import {PROTOCOL_S5, Sdk} from "@lumeweb/portal-sdk";
|
||||
import {S5Client} from "@lumeweb/s5-js";
|
||||
|
||||
const LISTENING_EVENTS = [
|
||||
"upload",
|
||||
|
@ -23,12 +20,26 @@ const LISTENING_EVENTS = [
|
|||
] as const
|
||||
|
||||
export function useUppy({
|
||||
uploader,
|
||||
endpoint
|
||||
}: {
|
||||
uploader: "tus"
|
||||
endpoint: string
|
||||
}) {
|
||||
const sdk = useSdk()
|
||||
|
||||
const [uploadLimit, setUploadLimit] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
async function getUploadLimit() {
|
||||
try {
|
||||
const limit = await sdk.account!().uploadLimit();
|
||||
setUploadLimit(limit);
|
||||
} catch (err) {
|
||||
console.log('Error occured while fetching upload limit', err);
|
||||
}
|
||||
}
|
||||
getUploadLimit();
|
||||
}, []);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [targetRef, _setTargetRef] = useState<HTMLElement | null>(null)
|
||||
const uppyInstance = useRef<Uppy>()
|
||||
|
@ -82,7 +93,29 @@ export function useUppy({
|
|||
useEffect(() => {
|
||||
if (!targetRef) return
|
||||
|
||||
const uppy = new Uppy({ logger: debugLogger }).use(DropTarget, {
|
||||
const tusPreprocessor = async (fileIDs: string[]) => {
|
||||
for(const fileID of fileIDs) {
|
||||
const file = uppyInstance.current?.getFile(fileID) as UppyFile
|
||||
// @ts-ignore
|
||||
if (file.uploader === "tus") {
|
||||
uppyInstance.current?.setFileState(fileID, {
|
||||
tus: await sdk.protocols!().get<S5Client>(PROTOCOL_S5).getSdk().getTusOptions(file.data as File)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uppy = new Uppy({
|
||||
logger: debugLogger,
|
||||
onBeforeUpload: (files) => {
|
||||
for (const file of Object.entries(files)) {
|
||||
// @ts-ignore
|
||||
file[1].uploader = file[1].size > uploadLimit ? "tus" : "file";
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
}).use(DropTarget, {
|
||||
target: targetRef
|
||||
} as DropTargetOptions)
|
||||
|
||||
|
@ -97,6 +130,25 @@ export function useUppy({
|
|||
uppyInstance.current?.addFiles(files)
|
||||
}
|
||||
|
||||
uppy.iteratePlugins((plugin) => {
|
||||
uppy.removePlugin(plugin);
|
||||
});
|
||||
|
||||
uppy.use(UppyFileUpload, { sdk: sdk as Sdk })
|
||||
|
||||
let useTus = false;
|
||||
|
||||
uppyInstance.current?.getFiles().forEach((file) => {
|
||||
if (file.size > uploadLimit) {
|
||||
useTus = true;
|
||||
}
|
||||
})
|
||||
|
||||
if (useTus) {
|
||||
uppy.use(Tus, { endpoint: endpoint, limit: 6 })
|
||||
uppy.addPreProcessor(tusPreprocessor)
|
||||
}
|
||||
|
||||
// We clear the input after a file is selected, because otherwise
|
||||
// change event is not fired in Chrome and Safari when a file
|
||||
// with the same name is selected.
|
||||
|
@ -109,12 +161,7 @@ export function useUppy({
|
|||
}
|
||||
})
|
||||
|
||||
switch (uploader) {
|
||||
case "tus":
|
||||
uppy.use(Tus, { endpoint: endpoint, limit: 6 })
|
||||
break
|
||||
default:
|
||||
}
|
||||
|
||||
|
||||
uppy.on("complete", (result) => {
|
||||
if (result.failed.length === 0) {
|
||||
|
@ -148,7 +195,7 @@ export function useUppy({
|
|||
})
|
||||
}
|
||||
setState("idle")
|
||||
}, [targetRef, endpoint, uploader])
|
||||
}, [targetRef, endpoint, uploadLimit])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
|
@ -33,10 +33,8 @@ export type Identity = {
|
|||
}
|
||||
|
||||
export class PortalAuthProvider implements RequiredAuthProvider {
|
||||
private sdk: Sdk;
|
||||
|
||||
constructor(apiUrl: string) {
|
||||
this.sdk = Sdk.create(apiUrl);
|
||||
this._sdk = Sdk.create(apiUrl);
|
||||
|
||||
const methods: Array<keyof AuthProvider> = [
|
||||
'login',
|
||||
|
@ -55,9 +53,18 @@ export class PortalAuthProvider implements RequiredAuthProvider {
|
|||
});
|
||||
}
|
||||
|
||||
private _sdk: Sdk;
|
||||
|
||||
get sdk(): Sdk {
|
||||
return this._sdk;
|
||||
}
|
||||
|
||||
public static create(apiUrl: string): AuthProvider {
|
||||
return new PortalAuthProvider(apiUrl);
|
||||
}
|
||||
|
||||
async login(params: AuthFormRequest): Promise<AuthActionResponse> {
|
||||
const cookies = new Cookies();
|
||||
const ret = await this.sdk.account().login({
|
||||
const ret = await this._sdk.account().login({
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
})
|
||||
|
@ -65,11 +72,11 @@ export class PortalAuthProvider implements RequiredAuthProvider {
|
|||
let redirectTo: string | undefined;
|
||||
|
||||
if (ret) {
|
||||
cookies.set('jwt', this.sdk.account().jwtToken, {path: '/'});
|
||||
redirectTo = params.redirectTo;
|
||||
if (!redirectTo) {
|
||||
redirectTo = ret ? "/dashboard" : "/login";
|
||||
}
|
||||
this._sdk.setAuthToken(this._sdk.account().jwtToken);
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -79,38 +86,25 @@ export class PortalAuthProvider implements RequiredAuthProvider {
|
|||
}
|
||||
|
||||
async logout(params: any): Promise<AuthActionResponse> {
|
||||
let ret = await this.sdk.account().logout();
|
||||
if (ret) {
|
||||
const cookies = new Cookies();
|
||||
cookies.remove('jwt');
|
||||
}
|
||||
let ret = await this._sdk.account().logout();
|
||||
return {success: ret, redirectTo: "/login"};
|
||||
}
|
||||
|
||||
async check(params?: any): Promise<CheckResponse> {
|
||||
const cookies = new Cookies();
|
||||
this.maybeSetupAuth();
|
||||
|
||||
const jwtCookie = cookies.get('jwt');
|
||||
|
||||
if (jwtCookie) {
|
||||
this.sdk.setAuthToken(jwtCookie);
|
||||
}
|
||||
|
||||
const ret = await this.sdk.account().ping();
|
||||
|
||||
if (!ret) {
|
||||
cookies.remove('jwt');
|
||||
}
|
||||
const ret = await this._sdk.account().ping();
|
||||
|
||||
return {authenticated: ret, redirectTo: ret ? undefined : "/login"};
|
||||
}
|
||||
|
||||
async onError(error: any): Promise<OnErrorResponse> {
|
||||
const cookies = new Cookies();
|
||||
return {logout: true};
|
||||
}
|
||||
|
||||
async register(params: RegisterFormRequest): Promise<AuthActionResponse> {
|
||||
const ret = await this.sdk.account().register({
|
||||
const ret = await this._sdk.account().register({
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
first_name: params.firstName,
|
||||
|
@ -132,7 +126,8 @@ export class PortalAuthProvider implements RequiredAuthProvider {
|
|||
}
|
||||
|
||||
async getIdentity(params?: Identity): Promise<IdentityResponse> {
|
||||
const ret = await this.sdk.account().info();
|
||||
this.maybeSetupAuth();
|
||||
const ret = await this._sdk.account().info();
|
||||
|
||||
if (!ret) {
|
||||
return {identity: null};
|
||||
|
@ -148,12 +143,16 @@ export class PortalAuthProvider implements RequiredAuthProvider {
|
|||
};
|
||||
}
|
||||
|
||||
public static create(apiUrl: string): AuthProvider {
|
||||
return new PortalAuthProvider(apiUrl);
|
||||
maybeSetupAuth(): void {
|
||||
const cookies = new Cookies();
|
||||
const jwtCookie = cookies.get('auth_token');
|
||||
if (jwtCookie) {
|
||||
this._sdk.setAuthToken(jwtCookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface RequiredAuthProvider extends AuthProvider {
|
||||
export interface RequiredAuthProvider extends AuthProvider {
|
||||
login: AuthProvider['login'];
|
||||
logout: AuthProvider['logout'];
|
||||
check: AuthProvider['check'];
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
interface PinningStatus {
|
||||
id: string;
|
||||
progress: number;
|
||||
status: 'inprogress' | 'completed' | 'stale';
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/noStaticOnlyClass: <explanation>
|
||||
class PinningProcess {
|
||||
private static instances: Map<string, PinningStatus> = new Map();
|
||||
|
||||
static async pin(id: string): Promise<{ success: boolean; message: string }> {
|
||||
if (PinningProcess.instances.has(id)) {
|
||||
return { success: false, message: "ID is already being processed" };
|
||||
}
|
||||
|
||||
const pinningStatus: PinningStatus = { id, progress: 0, status: 'inprogress' };
|
||||
PinningProcess.instances.set(id, pinningStatus);
|
||||
|
||||
// Simulate async progress
|
||||
(async () => {
|
||||
for (let progress = 1; progress <= 100; progress++) {
|
||||
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * (500 - 100 + 1)) + 100)); // Simulate time passing with random duration between 100 and 500
|
||||
pinningStatus.progress = progress;
|
||||
if (progress === 100) {
|
||||
pinningStatus.status = 'completed';
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return { success: true, message: "Pinning process started" };
|
||||
}
|
||||
|
||||
static *pollProgress(id: string): Generator<PinningStatus | null, void, unknown> {
|
||||
let status = PinningProcess.instances.get(id);
|
||||
while (status && status.status !== 'completed') {
|
||||
yield status;
|
||||
status = PinningProcess.instances.get(id);
|
||||
}
|
||||
yield status ?? null; // Yield the final status, could be null if ID doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
// (async () => {
|
||||
// const { success, message } = await PinningProcess.pin("123");
|
||||
// console.log(message);
|
||||
// if (success) {
|
||||
// const progressGenerator = PinningProcess.pollProgress("123");
|
||||
// let result = progressGenerator.next();
|
||||
// while (!result.done) {
|
||||
// console.log(result.value); // Log the progress
|
||||
// result = progressGenerator.next();
|
||||
// }
|
||||
// }
|
||||
// })();
|
|
@ -15,6 +15,7 @@ import {Refine} from "@refinedev/core";
|
|||
import {PortalAuthProvider} from "~/data/auth-provider.js";
|
||||
import routerProvider from "@refinedev/remix-router";
|
||||
import { defaultProvider } from "./data/file-provider";
|
||||
import {SdkContextProvider} from "~/components/lib/sdk-context.js";
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{ rel: "stylesheet", href: stylesheet },
|
||||
|
@ -39,9 +40,10 @@ export function Layout({children}: { children: React.ReactNode }) {
|
|||
}
|
||||
|
||||
export default function App() {
|
||||
const auth = PortalAuthProvider.create("https://alpha.pinner.xyz")
|
||||
return (
|
||||
<Refine
|
||||
authProvider={PortalAuthProvider.create("https://alpha.pinner.xyz")}
|
||||
authProvider={auth}
|
||||
routerProvider={routerProvider}
|
||||
dataProvider={defaultProvider}
|
||||
resources={[
|
||||
|
@ -49,7 +51,9 @@ export default function App() {
|
|||
{ name: 'users' }
|
||||
]}
|
||||
>
|
||||
<SdkContextProvider sdk={(auth as PortalAuthProvider).sdk}>
|
||||
<Outlet/>
|
||||
</SdkContextProvider>
|
||||
</Refine>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
import { getFormProps, useForm } from "@conform-to/react";
|
||||
import { getZodConstraint, parseWithZod } from "@conform-to/zod";
|
||||
import { BaseKey, useGetIdentity, useUpdate, useUpdatePassword } from "@refinedev/core";
|
||||
import {
|
||||
BaseKey,
|
||||
useGetIdentity,
|
||||
useUpdate,
|
||||
useUpdatePassword,
|
||||
} from "@refinedev/core";
|
||||
import { useState } from "react";
|
||||
import { z } from "zod";
|
||||
import { Field } from "~/components/forms";
|
||||
import { GeneralLayout } from "~/components/general-layout";
|
||||
import { AddIcon, CloudIcon, CrownIcon } from "~/components/icons";
|
||||
import { ManagementCard, ManagementCardAvatar, ManagementCardContent, ManagementCardFooter, ManagementCardTitle } from "~/components/management-card";
|
||||
import {
|
||||
ManagementCard,
|
||||
ManagementCardAvatar,
|
||||
ManagementCardContent,
|
||||
ManagementCardFooter,
|
||||
ManagementCardTitle,
|
||||
} from "~/components/management-card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
|
@ -54,7 +65,9 @@ export default function MyAccount() {
|
|||
{identity?.email}
|
||||
</ManagementCardContent>
|
||||
<ManagementCardFooter>
|
||||
<Button className="h-12 gap-x-2" onClick={() => setModal({ ...openModal, changeEmail: true })}>
|
||||
<Button
|
||||
className="h-12 gap-x-2"
|
||||
onClick={() => setModal({ ...openModal, changeEmail: true })}>
|
||||
<AddIcon />
|
||||
Change Email Address
|
||||
</Button>
|
||||
|
@ -82,7 +95,9 @@ export default function MyAccount() {
|
|||
<PasswordDots className="mt-6" />
|
||||
</ManagementCardContent>
|
||||
<ManagementCardFooter>
|
||||
<Button className="h-12 gap-x-2" onClick={() => setModal({ ...openModal, changePassword: true })}>
|
||||
<Button
|
||||
className="h-12 gap-x-2"
|
||||
onClick={() => setModal({ ...openModal, changePassword: true })}>
|
||||
<AddIcon />
|
||||
Change Password
|
||||
</Button>
|
||||
|
@ -94,30 +109,22 @@ export default function MyAccount() {
|
|||
Improve security by enabling 2FA.
|
||||
</ManagementCardContent>
|
||||
<ManagementCardFooter>
|
||||
<Button className="h-12 gap-x-2" onClick={() => setModal({ ...openModal, setupTwoFactor: true })}>
|
||||
<Button
|
||||
className="h-12 gap-x-2"
|
||||
onClick={() => setModal({ ...openModal, setupTwoFactor: true })}>
|
||||
<AddIcon />
|
||||
Enable Two-Factor Authorization
|
||||
</Button>
|
||||
</ManagementCardFooter>
|
||||
</ManagementCard>
|
||||
<ManagementCard>
|
||||
<ManagementCardTitle>Backup Key</ManagementCardTitle>
|
||||
<ManagementCardContent>
|
||||
Never share this code with anyone.
|
||||
</ManagementCardContent>
|
||||
<ManagementCardFooter>
|
||||
<Button className="h-12 gap-x-2">
|
||||
<AddIcon />
|
||||
Export Backup Key
|
||||
</Button>
|
||||
</ManagementCardFooter>
|
||||
</ManagementCard>
|
||||
</div>
|
||||
<h2 className="font-bold my-8">More</h2>
|
||||
<div className="grid grid-cols-3 gap-x-8">
|
||||
<ManagementCard variant="accent">
|
||||
<ManagementCardTitle>Invite a Friend</ManagementCardTitle>
|
||||
<ManagementCardContent>Get 1 GB per friend invited for free (max 5 GB).</ManagementCardContent>
|
||||
<ManagementCardContent>
|
||||
Get 1 GB per friend invited for free (max 5 GB).
|
||||
</ManagementCardContent>
|
||||
<ManagementCardFooter>
|
||||
<Button variant="accent" className="h-12 gap-x-2">
|
||||
<AddIcon />
|
||||
|
@ -127,7 +134,9 @@ export default function MyAccount() {
|
|||
</ManagementCard>
|
||||
<ManagementCard>
|
||||
<ManagementCardTitle>Read our Resources</ManagementCardTitle>
|
||||
<ManagementCardContent>Navigate helpful articles or get assistance.</ManagementCardContent>
|
||||
<ManagementCardContent>
|
||||
Navigate helpful articles or get assistance.
|
||||
</ManagementCardContent>
|
||||
<ManagementCardFooter>
|
||||
<Button className="h-12 gap-x-2">
|
||||
<AddIcon />
|
||||
|
@ -137,7 +146,9 @@ export default function MyAccount() {
|
|||
</ManagementCard>
|
||||
<ManagementCard>
|
||||
<ManagementCardTitle>Delete Account</ManagementCardTitle>
|
||||
<ManagementCardContent>Once initiated, this action cannot be undone.</ManagementCardContent>
|
||||
<ManagementCardContent>
|
||||
Once initiated, this action cannot be undone.
|
||||
</ManagementCardContent>
|
||||
<ManagementCardFooter>
|
||||
<Button className="h-12 gap-x-2" variant="destructive">
|
||||
<AddIcon />
|
||||
|
@ -170,7 +181,8 @@ export default function MyAccount() {
|
|||
);
|
||||
}
|
||||
|
||||
const ChangeEmailSchema = z.object({
|
||||
const ChangeEmailSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
retypePassword: z.string(),
|
||||
|
@ -210,13 +222,13 @@ const ChangeEmailForm = ({
|
|||
const data = Object.fromEntries(new FormData(e.currentTarget).entries());
|
||||
console.log(identity);
|
||||
updateEmail({
|
||||
resource: 'users',
|
||||
resource: "users",
|
||||
id: identity?.id || "",
|
||||
values: {
|
||||
email: data.email.toString()
|
||||
}
|
||||
})
|
||||
}
|
||||
email: data.email.toString(),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -255,7 +267,8 @@ const ChangeEmailForm = ({
|
|||
);
|
||||
};
|
||||
|
||||
const ChangePasswordSchema = z.object({
|
||||
const ChangePasswordSchema = z
|
||||
.object({
|
||||
currentPassword: z.string().email(),
|
||||
newPassword: z.string(),
|
||||
retypePassword: z.string(),
|
||||
|
@ -292,9 +305,8 @@ const ChangePasswordForm = ({
|
|||
const data = Object.fromEntries(new FormData(e.currentTarget).entries());
|
||||
|
||||
updatePassword({
|
||||
password: data.newPassword.toString()
|
||||
password: data.newPassword.toString(),
|
||||
});
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -390,6 +402,7 @@ const SetupTwoFactorDialog = ({
|
|||
const PasswordDots = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
width="219"
|
||||
height="7"
|
||||
viewBox="0 0 219 7"
|
||||
|
|
|
@ -14,7 +14,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-20240314110748",
|
||||
"@lumeweb/portal-sdk": "0.0.0-20240318183202",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
|
@ -40,10 +40,10 @@
|
|||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^7.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwind-merge": "^2.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"universal-cookie": "^7.1.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -62,6 +62,7 @@
|
|||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^5.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.21.0",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
diff --git a/node_modules/@uppy/tus/lib/index.js b/node_modules/@uppy/tus/lib/index.js
|
||||
index 1e0a1bb..ba95bb5 100644
|
||||
--- a/node_modules/@uppy/tus/lib/index.js
|
||||
+++ b/node_modules/@uppy/tus/lib/index.js
|
||||
@@ -506,7 +506,7 @@ function _getCompanionClientArgs2(file) {
|
||||
}
|
||||
async function _uploadFiles2(files) {
|
||||
const filesFiltered = filterNonFailedFiles(files);
|
||||
- const filesToEmit = filterFilesToEmitUploadStarted(filesFiltered);
|
||||
+ const filesToEmit = filterFilesToEmitUploadStarted(filesFiltered).filter(file => file?.uploader == 'tus');
|
||||
this.uppy.emit('upload-start', filesToEmit);
|
||||
await Promise.allSettled(filesFiltered.map(file => {
|
||||
if (file.isRemote) {
|
|
@ -1,6 +1,7 @@
|
|||
import {vitePlugin as remix} from "@remix-run/dev"
|
||||
import {defineConfig} from "vite"
|
||||
import tsconfigPaths from "vite-tsconfig-paths"
|
||||
import {nodePolyfills} from 'vite-plugin-node-polyfills'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
|
@ -8,8 +9,12 @@ export default defineConfig({
|
|||
ssr: false,
|
||||
ignoredRouteFiles: ["**/*.css"]
|
||||
}),
|
||||
tsconfigPaths()
|
||||
tsconfigPaths(),
|
||||
nodePolyfills({protocolImports: false}),
|
||||
],
|
||||
build: {
|
||||
minify: false
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
// Restrict files that could be served by Vite's dev server. Accessing
|
||||
|
|
Loading…
Reference in New Issue