From 20533913bd85817dd80510c3d45bec44491e7812 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sat, 16 Mar 2024 15:41:31 -0400 Subject: [PATCH 01/31] refactor: switch to reading cookie only and let server handle it for security, add maybeSetupAuth helper. --- app/data/auth-provider.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/data/auth-provider.ts b/app/data/auth-provider.ts index e29e9fe..86cec49 100644 --- a/app/data/auth-provider.ts +++ b/app/data/auth-provider.ts @@ -56,7 +56,6 @@ export class PortalAuthProvider implements RequiredAuthProvider { } async login(params: AuthFormRequest): Promise { - const cookies = new Cookies(); const ret = await this.sdk.account().login({ email: params.email, password: params.password, @@ -65,7 +64,6 @@ 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"; @@ -88,17 +86,12 @@ export class PortalAuthProvider implements RequiredAuthProvider { } async check(params?: any): Promise { - const cookies = new Cookies(); - - const jwtCookie = cookies.get('jwt'); - - if (jwtCookie) { - this.sdk.setAuthToken(jwtCookie); - } + this.maybeSetupAuth(); const ret = await this.sdk.account().ping(); if (!ret) { + const cookies = new Cookies(); cookies.remove('jwt'); } @@ -106,6 +99,9 @@ export class PortalAuthProvider implements RequiredAuthProvider { } async onError(error: any): Promise { + const cookies = new Cookies(); + cookies.remove('jwt'); + this.sdk.setAuthToken(''); return {logout: true}; } @@ -132,6 +128,7 @@ export class PortalAuthProvider implements RequiredAuthProvider { } async getIdentity(params?: Identity): Promise { + this.maybeSetupAuth(); const ret = await this.sdk.account().info(); if (!ret) { @@ -148,6 +145,14 @@ export class PortalAuthProvider implements RequiredAuthProvider { }; } + maybeSetupAuth(): void { + const cookies = new Cookies(); + const jwtCookie = cookies.get('jwt'); + if (jwtCookie) { + this.sdk.setAuthToken(jwtCookie); + } + } + public static create(apiUrl: string): AuthProvider { return new PortalAuthProvider(apiUrl); } From 9fd55bf994feb25dcf282104015c579f798b04bd Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sun, 17 Mar 2024 08:18:33 -0400 Subject: [PATCH 02/31] refactor: update auth token name --- app/data/auth-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/auth-provider.ts b/app/data/auth-provider.ts index 86cec49..dfa10cc 100644 --- a/app/data/auth-provider.ts +++ b/app/data/auth-provider.ts @@ -147,7 +147,7 @@ export class PortalAuthProvider implements RequiredAuthProvider { maybeSetupAuth(): void { const cookies = new Cookies(); - const jwtCookie = cookies.get('jwt'); + const jwtCookie = cookies.get('auth_token'); if (jwtCookie) { this.sdk.setAuthToken(jwtCookie); } From 0e083c7a58b2322b410aa036cb86e7d3bc214559 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sun, 17 Mar 2024 09:37:53 -0400 Subject: [PATCH 03/31] dep: update portal-sdk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0cc9af6..5740c36 100644 --- a/package.json +++ b/package.json @@ -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-20240317133635", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", From fd7fec85807e86490dd3f45bf5e9936a7aee0c08 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sun, 17 Mar 2024 09:41:12 -0400 Subject: [PATCH 04/31] dep: just use universal-cookie --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5740c36..e28be43 100644 --- a/package.json +++ b/package.json @@ -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": { From 245467155d23b9a0980bb5fda6b939b3d4c8706b Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sun, 17 Mar 2024 09:41:52 -0400 Subject: [PATCH 05/31] refactor: don't try to delete the auth cookie --- app/data/auth-provider.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/data/auth-provider.ts b/app/data/auth-provider.ts index dfa10cc..af8db9a 100644 --- a/app/data/auth-provider.ts +++ b/app/data/auth-provider.ts @@ -78,10 +78,6 @@ export class PortalAuthProvider implements RequiredAuthProvider { async logout(params: any): Promise { let ret = await this.sdk.account().logout(); - if (ret) { - const cookies = new Cookies(); - cookies.remove('jwt'); - } return {success: ret, redirectTo: "/login"}; } @@ -90,18 +86,11 @@ export class PortalAuthProvider implements RequiredAuthProvider { const ret = await this.sdk.account().ping(); - if (!ret) { - const cookies = new Cookies(); - cookies.remove('jwt'); - } - return {authenticated: ret, redirectTo: ret ? undefined : "/login"}; } async onError(error: any): Promise { const cookies = new Cookies(); - cookies.remove('jwt'); - this.sdk.setAuthToken(''); return {logout: true}; } From 928deb89b6c4c24e8e9a7b90249a98ec550841df Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sun, 17 Mar 2024 09:48:21 -0400 Subject: [PATCH 06/31] refactor: update logout text name --- app/components/general-layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/general-layout.tsx b/app/components/general-layout.tsx index c0b079b..add18d6 100644 --- a/app/components/general-layout.tsx +++ b/app/components/general-layout.tsx @@ -108,8 +108,8 @@ export const GeneralLayout = ({ children }: React.PropsWithChildren<{}>) => { logout()}> - Log Out - + Logout + From 06db71bd8ad44c6192619823da937f60a74584ed Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Sun, 17 Mar 2024 11:26:23 -0400 Subject: [PATCH 07/31] dep: update portal-sdk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e28be43..ecfaa70 100644 --- a/package.json +++ b/package.json @@ -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-20240317133635", + "@lumeweb/portal-sdk": "0.0.0-20240317152232", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", From d3e847baf848d32cb8aa74d2df5f7f86c33de9d8 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:08:15 -0400 Subject: [PATCH 08/31] dep: update portal-sdk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ecfaa70..aa025f4 100644 --- a/package.json +++ b/package.json @@ -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-20240317152232", + "@lumeweb/portal-sdk": "0.0.0-20240318134436", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", From 7c332d0f43f3580ab7d06ad3714ce1827d7268e7 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:08:57 -0400 Subject: [PATCH 09/31] refactor: add sdk getter --- app/data/auth-provider.ts | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/app/data/auth-provider.ts b/app/data/auth-provider.ts index af8db9a..17c0dcb 100644 --- a/app/data/auth-provider.ts +++ b/app/data/auth-provider.ts @@ -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 = [ 'login', @@ -55,8 +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 { - const ret = await this.sdk.account().login({ + const ret = await this._sdk.account().login({ email: params.email, password: params.password, }) @@ -77,14 +85,14 @@ export class PortalAuthProvider implements RequiredAuthProvider { } async logout(params: any): Promise { - let ret = await this.sdk.account().logout(); + let ret = await this._sdk.account().logout(); return {success: ret, redirectTo: "/login"}; } async check(params?: any): Promise { this.maybeSetupAuth(); - const ret = await this.sdk.account().ping(); + const ret = await this._sdk.account().ping(); return {authenticated: ret, redirectTo: ret ? undefined : "/login"}; } @@ -95,7 +103,7 @@ export class PortalAuthProvider implements RequiredAuthProvider { } async register(params: RegisterFormRequest): Promise { - const ret = await this.sdk.account().register({ + const ret = await this._sdk.account().register({ email: params.email, password: params.password, first_name: params.firstName, @@ -118,7 +126,7 @@ export class PortalAuthProvider implements RequiredAuthProvider { async getIdentity(params?: Identity): Promise { this.maybeSetupAuth(); - const ret = await this.sdk.account().info(); + const ret = await this._sdk.account().info(); if (!ret) { return {identity: null}; @@ -138,13 +146,9 @@ export class PortalAuthProvider implements RequiredAuthProvider { const cookies = new Cookies(); const jwtCookie = cookies.get('auth_token'); if (jwtCookie) { - this.sdk.setAuthToken(jwtCookie); + this._sdk.setAuthToken(jwtCookie); } } - - public static create(apiUrl: string): AuthProvider { - return new PortalAuthProvider(apiUrl); - } } interface RequiredAuthProvider extends AuthProvider { From 8643363736cd690a62d54f0a0c9865a3e8439052 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:09:08 -0400 Subject: [PATCH 10/31] refactor: export RequiredAuthProvider --- app/data/auth-provider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/data/auth-provider.ts b/app/data/auth-provider.ts index 17c0dcb..7e1c170 100644 --- a/app/data/auth-provider.ts +++ b/app/data/auth-provider.ts @@ -151,7 +151,7 @@ export class PortalAuthProvider implements RequiredAuthProvider { } } -interface RequiredAuthProvider extends AuthProvider { +export interface RequiredAuthProvider extends AuthProvider { login: AuthProvider['login']; logout: AuthProvider['logout']; check: AuthProvider['check']; From 7f26bc1060331f9b4e293ab15c03b3d5e5249dec Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:09:37 -0400 Subject: [PATCH 11/31] feat: add sdk context and useSdk --- app/components/lib/sdk-context.tsx | 18 ++++++++++++++++++ app/root.tsx | 8 ++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 app/components/lib/sdk-context.tsx diff --git a/app/components/lib/sdk-context.tsx b/app/components/lib/sdk-context.tsx new file mode 100644 index 0000000..a2d455e --- /dev/null +++ b/app/components/lib/sdk-context.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import {Sdk} from "@lumeweb/portal-sdk"; + +export const SdkContext = React.createContext< + Partial +>({}); + +export const SdkContextProvider: React.FC< {sdk: Sdk, children: React.ReactNode}> = ({sdk, children}) => { + return ( + + {children} + + ); +}; + +export function useSdk(): Partial{ + return React.useContext(SdkContext); +} diff --git a/app/root.tsx b/app/root.tsx index d4286cf..96d1531 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -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 ( - + + + ); } From dca77ba71a208ed1b0bb89a4ceb90e2d747abf3e Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:10:33 -0400 Subject: [PATCH 12/31] fix: add patch for uppy tus to filter on files with a custom uploader property set to tus --- patches/@uppy+tus+3.5.3.patch | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 patches/@uppy+tus+3.5.3.patch diff --git a/patches/@uppy+tus+3.5.3.patch b/patches/@uppy+tus+3.5.3.patch new file mode 100644 index 0000000..5dcc0a8 --- /dev/null +++ b/patches/@uppy+tus+3.5.3.patch @@ -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) { From 988780b25f2876bfd2a09ececf360be7820e0cee Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:11:07 -0400 Subject: [PATCH 13/31] feat: add initial plugin for a post file upload via s5 sdk --- app/components/lib/uppy-file-upload.ts | 62 ++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 app/components/lib/uppy-file-upload.ts diff --git a/app/components/lib/uppy-file-upload.ts b/app/components/lib/uppy-file-upload.ts new file mode 100644 index 0000000..5153fb0 --- /dev/null +++ b/app/components/lib/uppy-file-upload.ts @@ -0,0 +1,62 @@ +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._sdk = opts?.sdk as Sdk; + } + + install() { + this.uppy.addUploader(this.handleUpload); + } + + 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(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); + } + } + } +} From 3a4b40ef27d83a7434176f6eb63cf125e0ea50d9 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:12:27 -0400 Subject: [PATCH 14/31] refactor: remove uploader from uppy hook and check by the upload size limit if we use tus plugin or just file post. additionally add a onBeforeUpload filter to set the custom uploader property. --- app/components/lib/uppy.ts | 68 +++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/app/components/lib/uppy.ts b/app/components/lib/uppy.ts index 6b725f8..522f658 100644 --- a/app/components/lib/uppy.ts +++ b/app/components/lib/uppy.ts @@ -1,17 +1,13 @@ -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 DropTarget, { type DropTargetOptions } from "./uppy-dropzone" +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 {Sdk} from "@lumeweb/portal-sdk"; const LISTENING_EVENTS = [ "upload", @@ -23,12 +19,26 @@ const LISTENING_EVENTS = [ ] as const export function useUppy({ - uploader, endpoint }: { - uploader: "tus" endpoint: string }) { + const sdk = useSdk() + + const [uploadLimit, setUploadLimit] = useState(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(null) const [targetRef, _setTargetRef] = useState(null) const uppyInstance = useRef() @@ -82,7 +92,17 @@ export function useUppy({ useEffect(() => { if (!targetRef) return - const uppy = new Uppy({ logger: debugLogger }).use(DropTarget, { + 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) @@ -109,12 +129,20 @@ export function useUppy({ } }) - switch (uploader) { - case "tus": - uppy.use(Tus, { endpoint: endpoint, limit: 6 }) - break - default: - } + + 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.on("complete", (result) => { if (result.failed.length === 0) { @@ -148,7 +176,7 @@ export function useUppy({ }) } setState("idle") - }, [targetRef, endpoint, uploader]) + }, [targetRef, endpoint, uploadLimit]) useEffect(() => { return () => { From 84f7585a666dd9294ec3b9480950d1acaf3fa376 Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 10:56:20 -0400 Subject: [PATCH 15/31] dep: update portal-sdk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa025f4..3ace925 100644 --- a/package.json +++ b/package.json @@ -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-20240318134436", + "@lumeweb/portal-sdk": "0.0.0-20240318145508", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", From 8a3181177bd106141b4de87dbd12be54f1d9241b Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 11:01:13 -0400 Subject: [PATCH 16/31] dep: update portal-sdk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ace925..3787282 100644 --- a/package.json +++ b/package.json @@ -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-20240318145508", + "@lumeweb/portal-sdk": "0.0.0-20240318150041", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", From ec9509ef6c0754cd83999f13151a36733315bdfc Mon Sep 17 00:00:00 2001 From: Derrick Hammer Date: Mon, 18 Mar 2024 11:06:45 -0400 Subject: [PATCH 17/31] dep: update portal-sdk --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3787282..053aa71 100644 --- a/package.json +++ b/package.json @@ -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-20240318150041", + "@lumeweb/portal-sdk": "0.0.0-20240318150445", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", From c9d956e1b63fbd944fac63e8bc51c0646137e56c Mon Sep 17 00:00:00 2001 From: Juan Di Toro Date: Mon, 18 Mar 2024 16:07:11 +0100 Subject: [PATCH 18/31] fix: remove the backup key card from my account --- app/data/pinning.ts | 55 ++++++++++++++++ app/routes/account.tsx | 145 ++++++++++++++++++++++------------------- 2 files changed, 134 insertions(+), 66 deletions(-) create mode 100644 app/data/pinning.ts diff --git a/app/data/pinning.ts b/app/data/pinning.ts new file mode 100644 index 0000000..80d9540 --- /dev/null +++ b/app/data/pinning.ts @@ -0,0 +1,55 @@ +interface PinningStatus { + id: string; + progress: number; + status: 'inprogress' | 'completed' | 'stale'; +} + +// biome-ignore lint/complexity/noStaticOnlyClass: +class PinningProcess { + private static instances: Map = 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 { + 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(); +// } +// } +// })(); diff --git a/app/routes/account.tsx b/app/routes/account.tsx index b8d32f7..41a4717 100644 --- a/app/routes/account.tsx +++ b/app/routes/account.tsx @@ -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, @@ -51,10 +62,12 @@ export default function MyAccount() { Email Address - {identity?.email} + {identity?.email} - @@ -63,8 +76,8 @@ export default function MyAccount() { Account Type - Lite Premium Account - + Lite Premium Account + @@ -91,35 +106,27 @@ export default function MyAccount() { Two-Factor Authentication - Improve security by enabling 2FA. + Improve security by enabling 2FA. - - - Backup Key - - Never share this code with anyone. - - - - -

More

Invite a Friend - Get 1 GB per friend invited for free (max 5 GB). + + Get 1 GB per friend invited for free (max 5 GB). + - @@ -127,7 +134,9 @@ export default function MyAccount() { Read our Resources - Navigate helpful articles or get assistance. + + Navigate helpful articles or get assistance. +