Merge branch 'develop' into riobuenoDevelops/refine-integration

This commit is contained in:
Tania Gutierrez 2024-03-18 15:52:14 -04:00
commit 43ac8560cb
Signed by: riobuenoDevelops
GPG Key ID: 53133EB28EB7E801
11 changed files with 360 additions and 142 deletions

View File

@ -108,7 +108,7 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => {
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem onClick={() => logout()}> <DropdownMenuItem onClick={() => logout()}>
<ExitIcon className="mr-2" /> <ExitIcon className="mr-2" />
Log Out Logout
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
</DropdownMenuContent> </DropdownMenuContent>

View File

@ -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);
}

View File

@ -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);
}
}
}
}

View File

@ -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 Tus from "@uppy/tus"
import toArray from "@uppy/utils/lib/toArray" import toArray from "@uppy/utils/lib/toArray"
import { import {type ChangeEvent, useCallback, useEffect, useMemo, useRef, useState} from "react"
type ChangeEvent, import DropTarget, {type DropTargetOptions} from "./uppy-dropzone"
useCallback, import {useSdk} from "~/components/lib/sdk-context.js";
useEffect, import UppyFileUpload from "~/components/lib/uppy-file-upload.js";
useMemo, import {PROTOCOL_S5, Sdk} from "@lumeweb/portal-sdk";
useRef, import {S5Client} from "@lumeweb/s5-js";
useState
} from "react"
import DropTarget, { type DropTargetOptions } from "./uppy-dropzone"
const LISTENING_EVENTS = [ const LISTENING_EVENTS = [
"upload", "upload",
@ -23,12 +20,26 @@ const LISTENING_EVENTS = [
] as const ] as const
export function useUppy({ export function useUppy({
uploader,
endpoint endpoint
}: { }: {
uploader: "tus"
endpoint: string 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 inputRef = useRef<HTMLInputElement>(null)
const [targetRef, _setTargetRef] = useState<HTMLElement | null>(null) const [targetRef, _setTargetRef] = useState<HTMLElement | null>(null)
const uppyInstance = useRef<Uppy>() const uppyInstance = useRef<Uppy>()
@ -82,7 +93,29 @@ export function useUppy({
useEffect(() => { useEffect(() => {
if (!targetRef) return 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 target: targetRef
} as DropTargetOptions) } as DropTargetOptions)
@ -97,6 +130,25 @@ export function useUppy({
uppyInstance.current?.addFiles(files) 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 // We clear the input after a file is selected, because otherwise
// change event is not fired in Chrome and Safari when a file // change event is not fired in Chrome and Safari when a file
// with the same name is selected. // 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) => { uppy.on("complete", (result) => {
if (result.failed.length === 0) { if (result.failed.length === 0) {
@ -148,7 +195,7 @@ export function useUppy({
}) })
} }
setState("idle") setState("idle")
}, [targetRef, endpoint, uploader]) }, [targetRef, endpoint, uploadLimit])
useEffect(() => { useEffect(() => {
return () => { return () => {

View File

@ -33,10 +33,8 @@ export type Identity = {
} }
export class PortalAuthProvider implements RequiredAuthProvider { export class PortalAuthProvider implements RequiredAuthProvider {
private sdk: Sdk;
constructor(apiUrl: string) { constructor(apiUrl: string) {
this.sdk = Sdk.create(apiUrl); this._sdk = Sdk.create(apiUrl);
const methods: Array<keyof AuthProvider> = [ const methods: Array<keyof AuthProvider> = [
'login', '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> { 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, email: params.email,
password: params.password, password: params.password,
}) })
@ -65,11 +72,11 @@ export class PortalAuthProvider implements RequiredAuthProvider {
let redirectTo: string | undefined; let redirectTo: string | undefined;
if (ret) { if (ret) {
cookies.set('jwt', this.sdk.account().jwtToken, {path: '/'});
redirectTo = params.redirectTo; redirectTo = params.redirectTo;
if (!redirectTo) { if (!redirectTo) {
redirectTo = ret ? "/dashboard" : "/login"; redirectTo = ret ? "/dashboard" : "/login";
} }
this._sdk.setAuthToken(this._sdk.account().jwtToken);
} }
return { return {
@ -79,38 +86,25 @@ export class PortalAuthProvider implements RequiredAuthProvider {
} }
async logout(params: any): Promise<AuthActionResponse> { async logout(params: any): Promise<AuthActionResponse> {
let ret = await this.sdk.account().logout(); let ret = await this._sdk.account().logout();
if (ret) {
const cookies = new Cookies();
cookies.remove('jwt');
}
return {success: ret, redirectTo: "/login"}; return {success: ret, redirectTo: "/login"};
} }
async check(params?: any): Promise<CheckResponse> { async check(params?: any): Promise<CheckResponse> {
const cookies = new Cookies(); this.maybeSetupAuth();
const jwtCookie = cookies.get('jwt'); const ret = await this._sdk.account().ping();
if (jwtCookie) {
this.sdk.setAuthToken(jwtCookie);
}
const ret = await this.sdk.account().ping();
if (!ret) {
cookies.remove('jwt');
}
return {authenticated: ret, redirectTo: ret ? undefined : "/login"}; return {authenticated: ret, redirectTo: ret ? undefined : "/login"};
} }
async onError(error: any): Promise<OnErrorResponse> { async onError(error: any): Promise<OnErrorResponse> {
const cookies = new Cookies();
return {logout: true}; return {logout: true};
} }
async register(params: RegisterFormRequest): Promise<AuthActionResponse> { async register(params: RegisterFormRequest): Promise<AuthActionResponse> {
const ret = await this.sdk.account().register({ const ret = await this._sdk.account().register({
email: params.email, email: params.email,
password: params.password, password: params.password,
first_name: params.firstName, first_name: params.firstName,
@ -132,7 +126,8 @@ export class PortalAuthProvider implements RequiredAuthProvider {
} }
async getIdentity(params?: Identity): Promise<IdentityResponse> { async getIdentity(params?: Identity): Promise<IdentityResponse> {
const ret = await this.sdk.account().info(); this.maybeSetupAuth();
const ret = await this._sdk.account().info();
if (!ret) { if (!ret) {
return {identity: null}; return {identity: null};
@ -148,12 +143,16 @@ export class PortalAuthProvider implements RequiredAuthProvider {
}; };
} }
public static create(apiUrl: string): AuthProvider { maybeSetupAuth(): void {
return new PortalAuthProvider(apiUrl); 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']; login: AuthProvider['login'];
logout: AuthProvider['logout']; logout: AuthProvider['logout'];
check: AuthProvider['check']; check: AuthProvider['check'];

55
app/data/pinning.ts Normal file
View File

@ -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();
// }
// }
// })();

View File

@ -15,6 +15,7 @@ import {Refine} from "@refinedev/core";
import {PortalAuthProvider} from "~/data/auth-provider.js"; import {PortalAuthProvider} from "~/data/auth-provider.js";
import routerProvider from "@refinedev/remix-router"; import routerProvider from "@refinedev/remix-router";
import { defaultProvider } from "./data/file-provider"; import { defaultProvider } from "./data/file-provider";
import {SdkContextProvider} from "~/components/lib/sdk-context.js";
export const links: LinksFunction = () => [ export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet }, { rel: "stylesheet", href: stylesheet },
@ -39,9 +40,10 @@ export function Layout({children}: { children: React.ReactNode }) {
} }
export default function App() { export default function App() {
const auth = PortalAuthProvider.create("https://alpha.pinner.xyz")
return ( return (
<Refine <Refine
authProvider={PortalAuthProvider.create("https://alpha.pinner.xyz")} authProvider={auth}
routerProvider={routerProvider} routerProvider={routerProvider}
dataProvider={defaultProvider} dataProvider={defaultProvider}
resources={[ resources={[
@ -49,7 +51,9 @@ export default function App() {
{ name: 'users' } { name: 'users' }
]} ]}
> >
<Outlet/> <SdkContextProvider sdk={(auth as PortalAuthProvider).sdk}>
<Outlet/>
</SdkContextProvider>
</Refine> </Refine>
); );
} }

View File

@ -1,12 +1,23 @@
import { getFormProps, useForm } from "@conform-to/react"; import { getFormProps, useForm } from "@conform-to/react";
import { getZodConstraint, parseWithZod } from "@conform-to/zod"; 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 { useState } from "react";
import { z } from "zod"; import { z } from "zod";
import { Field } from "~/components/forms"; import { Field } from "~/components/forms";
import { GeneralLayout } from "~/components/general-layout"; import { GeneralLayout } from "~/components/general-layout";
import { AddIcon, CloudIcon, CrownIcon } from "~/components/icons"; 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 { Button } from "~/components/ui/button";
import { import {
Dialog, Dialog,
@ -51,10 +62,12 @@ export default function MyAccount() {
<ManagementCard> <ManagementCard>
<ManagementCardTitle>Email Address</ManagementCardTitle> <ManagementCardTitle>Email Address</ManagementCardTitle>
<ManagementCardContent className="text-ring font-semibold"> <ManagementCardContent className="text-ring font-semibold">
{identity?.email} {identity?.email}
</ManagementCardContent> </ManagementCardContent>
<ManagementCardFooter> <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 /> <AddIcon />
Change Email Address Change Email Address
</Button> </Button>
@ -63,8 +76,8 @@ export default function MyAccount() {
<ManagementCard> <ManagementCard>
<ManagementCardTitle>Account Type</ManagementCardTitle> <ManagementCardTitle>Account Type</ManagementCardTitle>
<ManagementCardContent className="text-ring font-semibold flex gap-x-2"> <ManagementCardContent className="text-ring font-semibold flex gap-x-2">
Lite Premium Account Lite Premium Account
<CrownIcon /> <CrownIcon />
</ManagementCardContent> </ManagementCardContent>
<ManagementCardFooter> <ManagementCardFooter>
<Button className="h-12 gap-x-2"> <Button className="h-12 gap-x-2">
@ -82,7 +95,9 @@ export default function MyAccount() {
<PasswordDots className="mt-6" /> <PasswordDots className="mt-6" />
</ManagementCardContent> </ManagementCardContent>
<ManagementCardFooter> <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 /> <AddIcon />
Change Password Change Password
</Button> </Button>
@ -91,35 +106,27 @@ export default function MyAccount() {
<ManagementCard> <ManagementCard>
<ManagementCardTitle>Two-Factor Authentication</ManagementCardTitle> <ManagementCardTitle>Two-Factor Authentication</ManagementCardTitle>
<ManagementCardContent> <ManagementCardContent>
Improve security by enabling 2FA. Improve security by enabling 2FA.
</ManagementCardContent> </ManagementCardContent>
<ManagementCardFooter> <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 /> <AddIcon />
Enable Two-Factor Authorization Enable Two-Factor Authorization
</Button> </Button>
</ManagementCardFooter> </ManagementCardFooter>
</ManagementCard> </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> </div>
<h2 className="font-bold my-8">More</h2> <h2 className="font-bold my-8">More</h2>
<div className="grid grid-cols-3 gap-x-8"> <div className="grid grid-cols-3 gap-x-8">
<ManagementCard variant="accent"> <ManagementCard variant="accent">
<ManagementCardTitle>Invite a Friend</ManagementCardTitle> <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> <ManagementCardFooter>
<Button variant="accent" className="h-12 gap-x-2"> <Button variant="accent" className="h-12 gap-x-2">
<AddIcon /> <AddIcon />
Send Invitation Send Invitation
</Button> </Button>
@ -127,7 +134,9 @@ export default function MyAccount() {
</ManagementCard> </ManagementCard>
<ManagementCard> <ManagementCard>
<ManagementCardTitle>Read our Resources</ManagementCardTitle> <ManagementCardTitle>Read our Resources</ManagementCardTitle>
<ManagementCardContent>Navigate helpful articles or get assistance.</ManagementCardContent> <ManagementCardContent>
Navigate helpful articles or get assistance.
</ManagementCardContent>
<ManagementCardFooter> <ManagementCardFooter>
<Button className="h-12 gap-x-2"> <Button className="h-12 gap-x-2">
<AddIcon /> <AddIcon />
@ -137,7 +146,9 @@ export default function MyAccount() {
</ManagementCard> </ManagementCard>
<ManagementCard> <ManagementCard>
<ManagementCardTitle>Delete Account</ManagementCardTitle> <ManagementCardTitle>Delete Account</ManagementCardTitle>
<ManagementCardContent>Once initiated, this action cannot be undone.</ManagementCardContent> <ManagementCardContent>
Once initiated, this action cannot be undone.
</ManagementCardContent>
<ManagementCardFooter> <ManagementCardFooter>
<Button className="h-12 gap-x-2" variant="destructive"> <Button className="h-12 gap-x-2" variant="destructive">
<AddIcon /> <AddIcon />
@ -170,21 +181,22 @@ export default function MyAccount() {
); );
} }
const ChangeEmailSchema = z.object({ const ChangeEmailSchema = z
email: z.string().email(), .object({
password: z.string(), email: z.string().email(),
retypePassword: z.string(), password: z.string(),
}) retypePassword: z.string(),
.superRefine((data, ctx) => { })
if (data.password !== data.retypePassword) { .superRefine((data, ctx) => {
return ctx.addIssue({ if (data.password !== data.retypePassword) {
code: z.ZodIssueCode.custom, return ctx.addIssue({
path: ["retypePassword"], code: z.ZodIssueCode.custom,
message: "Passwords do not match", path: ["retypePassword"],
}); message: "Passwords do not match",
} });
return true; }
}); return true;
});
const ChangeEmailForm = ({ const ChangeEmailForm = ({
open, open,
@ -195,7 +207,7 @@ const ChangeEmailForm = ({
setOpen: (value: boolean) => void; setOpen: (value: boolean) => void;
currentValue: string; currentValue: string;
}) => { }) => {
const{ data: identity } = useGetIdentity<{ id: BaseKey }>(); const { data: identity } = useGetIdentity<{ id: BaseKey }>();
const { mutate: updateEmail } = useUpdate(); const { mutate: updateEmail } = useUpdate();
const [form, fields] = useForm({ const [form, fields] = useForm({
id: "login", id: "login",
@ -210,13 +222,13 @@ const ChangeEmailForm = ({
const data = Object.fromEntries(new FormData(e.currentTarget).entries()); const data = Object.fromEntries(new FormData(e.currentTarget).entries());
console.log(identity); console.log(identity);
updateEmail({ updateEmail({
resource: 'users', resource: "users",
id: identity?.id || "", id: identity?.id || "",
values: { values: {
email: data.email.toString() email: data.email.toString(),
} },
}) });
} },
}); });
return ( return (
@ -255,21 +267,22 @@ const ChangeEmailForm = ({
); );
}; };
const ChangePasswordSchema = z.object({ const ChangePasswordSchema = z
currentPassword: z.string().email(), .object({
newPassword: z.string(), currentPassword: z.string().email(),
retypePassword: z.string(), newPassword: z.string(),
}) retypePassword: z.string(),
.superRefine((data, ctx) => { })
if (data.newPassword !== data.retypePassword) { .superRefine((data, ctx) => {
return ctx.addIssue({ if (data.newPassword !== data.retypePassword) {
code: z.ZodIssueCode.custom, return ctx.addIssue({
path: ["retypePassword"], code: z.ZodIssueCode.custom,
message: "Passwords do not match", path: ["retypePassword"],
}); message: "Passwords do not match",
} });
return true; }
}); return true;
});
const ChangePasswordForm = ({ const ChangePasswordForm = ({
open, open,
@ -292,9 +305,8 @@ const ChangePasswordForm = ({
const data = Object.fromEntries(new FormData(e.currentTarget).entries()); const data = Object.fromEntries(new FormData(e.currentTarget).entries());
updatePassword({ updatePassword({
password: data.newPassword.toString() password: data.newPassword.toString(),
}); });
}, },
}); });
@ -390,6 +402,7 @@ const SetupTwoFactorDialog = ({
const PasswordDots = ({ className }: { className?: string }) => { const PasswordDots = ({ className }: { className?: string }) => {
return ( return (
<svg <svg
aria-hidden="true"
width="219" width="219"
height="7" height="7"
viewBox="0 0 219 7" viewBox="0 0 219 7"

View File

@ -14,7 +14,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-20240314110748", "@lumeweb/portal-sdk": "0.0.0-20240318183202",
"@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",
@ -40,10 +40,10 @@
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-cookie": "^7.1.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"universal-cookie": "^7.1.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
@ -62,6 +62,7 @@
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.1.6", "typescript": "^5.1.6",
"vite": "^5.1.0", "vite": "^5.1.0",
"vite-plugin-node-polyfills": "^0.21.0",
"vite-tsconfig-paths": "^4.2.1" "vite-tsconfig-paths": "^4.2.1"
}, },
"engines": { "engines": {

View File

@ -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) {

View File

@ -1,27 +1,32 @@
import { vitePlugin as remix } from "@remix-run/dev" import {vitePlugin as remix} from "@remix-run/dev"
import { defineConfig } from "vite" import {defineConfig} from "vite"
import tsconfigPaths from "vite-tsconfig-paths" import tsconfigPaths from "vite-tsconfig-paths"
import {nodePolyfills} from 'vite-plugin-node-polyfills'
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
remix({ remix({
ssr: false, ssr: false,
ignoredRouteFiles: ["**/*.css"] ignoredRouteFiles: ["**/*.css"]
}), }),
tsconfigPaths() tsconfigPaths(),
], nodePolyfills({protocolImports: false}),
server: { ],
fs: { build: {
// Restrict files that could be served by Vite's dev server. Accessing minify: false
// files outside this directory list that aren't imported from an allowed },
// file will result in a 403. Both directories and files can be provided. server: {
// If you're comfortable with Vite's dev server making any file within the fs: {
// project root available, you can remove this option. See more: // Restrict files that could be served by Vite's dev server. Accessing
// https://vitejs.dev/config/server-options.html#server-fs-allow // files outside this directory list that aren't imported from an allowed
allow: [ // file will result in a 403. Both directories and files can be provided.
"app", // If you're comfortable with Vite's dev server making any file within the
"node_modules/@fontsource-variable/manrope", // project root available, you can remove this option. See more:
] // https://vitejs.dev/config/server-options.html#server-fs-allow
allow: [
"app",
"node_modules/@fontsource-variable/manrope",
]
}
} }
}
}) })