From bf7445fc71708a0b5ff9a242f2c8eafa3f9b45eb Mon Sep 17 00:00:00 2001 From: Yaroslav Grishajev Date: Wed, 22 May 2024 12:53:40 +0200 Subject: [PATCH] feat(setting): request user confirmation on local data import As a part of this implement handier popup tooling refs #56 --- api/package-lock.json | 4 +- deploy-web/package-lock.json | 57 +++++++++++------ deploy-web/package.json | 11 ++-- .../LocalDataManager.component.tsx | 13 +++- deploy-web/src/components/shared/Popup.tsx | 10 +-- .../context/PopupProvider/PopupProvider.tsx | 62 +++++++++++++++++++ deploy-web/src/pages/_app.tsx | 6 +- 7 files changed, 127 insertions(+), 36 deletions(-) create mode 100644 deploy-web/src/context/PopupProvider/PopupProvider.tsx diff --git a/api/package-lock.json b/api/package-lock.json index aa8b7d9e4..f85596d23 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1,12 +1,12 @@ { "name": "cloudmos-api", - "version": "2.18.3", + "version": "2.18.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cloudmos-api", - "version": "2.18.3", + "version": "2.18.4", "license": "Apache-2.0", "dependencies": { "@akashnetwork/akash-api": "^1.3.0", diff --git a/deploy-web/package-lock.json b/deploy-web/package-lock.json index f3ad88484..72b2206c2 100644 --- a/deploy-web/package-lock.json +++ b/deploy-web/package-lock.json @@ -106,6 +106,7 @@ "react-use-websocket": "^3.0.0", "rehype-highlight": "^6.0.0", "remark-gfm": "^3.0.1", + "rxjs": "^7.8.1", "sharp": "^0.30.3", "stripe": "^10.14.0", "tailwind-merge": "^2.0.0", @@ -169,14 +170,6 @@ "@grpc/grpc-js": "^1.10.6" } }, - "node_modules/@akashnetwork/akash-api/node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/@akashnetwork/akashjs": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@akashnetwork/akashjs/-/akashjs-0.10.0.tgz", @@ -2062,6 +2055,22 @@ "node": ">=6" } }, + "node_modules/@coinbase/wallet-sdk/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@coinbase/wallet-sdk/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@confio/ics23": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz", @@ -6749,6 +6758,22 @@ "semver": "^7.3.5" } }, + "node_modules/@ledgerhq/devices/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@ledgerhq/devices/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@ledgerhq/errors": { "version": "5.50.0", "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-5.50.0.tgz", @@ -23459,21 +23484,13 @@ } }, "node_modules/rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" + "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", diff --git a/deploy-web/package.json b/deploy-web/package.json index 955d61d84..8bef7cdbf 100644 --- a/deploy-web/package.json +++ b/deploy-web/package.json @@ -1,17 +1,17 @@ { "name": "akash-console", "version": "2.7.2", - "description": "Web UI to deploy on the Akash Network and view statistic about network usage.", - "author": "Akash Network", "private": true, + "description": "Web UI to deploy on the Akash Network and view statistic about network usage.", "license": "Apache-2.0", + "author": "Akash Network", "scripts": { - "dev": "next -p 3000", "build": "next build", "build-analyze": "set ANALYZE=true&& next build", + "dev": "next -p 3000", + "pretty": "prettier --write \"./**/*.{js,jsx,ts,tsx,json}\"", "start": "next start", - "type-check": "tsc", - "pretty": "prettier --write \"./**/*.{js,jsx,ts,tsx,json}\"" + "type-check": "tsc" }, "dependencies": { "@akashnetwork/akash-api": "^1.3.0", @@ -111,6 +111,7 @@ "react-use-websocket": "^3.0.0", "rehype-highlight": "^6.0.0", "remark-gfm": "^3.0.1", + "rxjs": "^7.8.1", "sharp": "^0.30.3", "stripe": "^10.14.0", "tailwind-merge": "^2.0.0", diff --git a/deploy-web/src/components/settings/LocalDataManager/LocalDataManager.component.tsx b/deploy-web/src/components/settings/LocalDataManager/LocalDataManager.component.tsx index ce5868ed0..565e9c09f 100644 --- a/deploy-web/src/components/settings/LocalDataManager/LocalDataManager.component.tsx +++ b/deploy-web/src/components/settings/LocalDataManager/LocalDataManager.component.tsx @@ -1,6 +1,8 @@ import React, { FC, useCallback, useRef } from "react"; import { Download, Upload } from "iconoir-react"; + import { Button } from "@src/components/ui/button"; +import { usePopup } from "@src/context/PopupProvider/PopupProvider"; export type LocalData = Record; @@ -12,9 +14,16 @@ interface LocalDataManagerProps { export const LocalDataManagerComponent: FC = ({ read, write, onDone }) => { const ref = useRef(null); + const { confirm } = usePopup(); - const triggerFileUpload = useCallback(() => { - ref.current?.click(); + const triggerFileUpload = useCallback(async () => { + const isConfirmed = await confirm({ + title: "Import Local Data", + message: "Existing local data will be overwritten. Are you sure you want to proceed?" + }); + if (isConfirmed) { + ref.current?.click(); + } }, [ref.current]); const importLocalData = useCallback((event: React.ChangeEvent) => { diff --git a/deploy-web/src/components/shared/Popup.tsx b/deploy-web/src/components/shared/Popup.tsx index 1c24fb0ba..8d2596b80 100644 --- a/deploy-web/src/components/shared/Popup.tsx +++ b/deploy-web/src/components/shared/Popup.tsx @@ -14,7 +14,7 @@ type MessageProps = { onValidate: () => void; }; -type ConfirmProps = { +export type ConfirmProps = { variant: "confirm"; onValidate: () => void; onCancel: () => void; @@ -35,7 +35,7 @@ export type TOnCloseHandler = { (event: any, reason: "backdropClick" | "escapeKeyDown" | "action"): void; }; -type CommonProps = { +export type CommonProps = { title?: string | React.ReactNode; message?: string; open?: boolean; @@ -131,8 +131,8 @@ export function Popup(props: React.PropsWithChildren) { )); component.push( - +
{leftButtons}
{rightButtons}
diff --git a/deploy-web/src/context/PopupProvider/PopupProvider.tsx b/deploy-web/src/context/PopupProvider/PopupProvider.tsx new file mode 100644 index 000000000..0e0c235aa --- /dev/null +++ b/deploy-web/src/context/PopupProvider/PopupProvider.tsx @@ -0,0 +1,62 @@ +import { Popup, PopupProps, CommonProps, ConfirmProps } from "@src/components/shared/Popup"; +import React, { FC, useCallback, useMemo, useState } from "react"; +import { firstValueFrom, Subject } from "rxjs"; + +type ConfirmPopupProps = string | (Omit & Omit); + +type PopupProviderContext = { + confirm: (messageOrProps: ConfirmPopupProps) => Promise; +}; + +const PopupContext = React.createContext(undefined); + +export const PopupProvider: FC = ({ children }) => { + const [popupProps, setPopupProps] = useState(); + + const confirm = useCallback( + (messageOrProps: ConfirmPopupProps) => { + let subject: Subject | undefined = new Subject(); + + const closeWithResult = (result: boolean) => () => { + if (subject) { + subject.next(result); + subject.complete(); + setPopupProps(undefined); + subject = undefined; + } + }; + const reject = closeWithResult(false); + const props = typeof messageOrProps === "string" ? { message: messageOrProps } : messageOrProps; + + setPopupProps({ + title: "Confirm", + ...props, + open: true, + variant: "confirm", + onValidate: closeWithResult(true), + onCancel: reject, + onClose: reject + }); + + return firstValueFrom(subject); + }, + [setPopupProps] + ); + + const context = useMemo(() => ({ confirm }), [confirm]); + + return ( + + {children} + {popupProps && } + + ); +}; + +export const usePopup = () => { + const context = React.useContext(PopupContext); + if (!context) { + throw new Error("usePopup must be used within a PopupProvider"); + } + return context; +}; diff --git a/deploy-web/src/pages/_app.tsx b/deploy-web/src/pages/_app.tsx index 41f4963e3..8dd339820 100644 --- a/deploy-web/src/pages/_app.tsx +++ b/deploy-web/src/pages/_app.tsx @@ -32,6 +32,7 @@ import { TooltipProvider } from "@src/components/ui/tooltip"; import { cn } from "@src/utils/styleUtils"; import { GeistSans } from "geist/font/sans"; import GoogleAnalytics from "@src/components/layout/CustomGoogleAnalytics"; +import { PopupProvider } from "@src/context/PopupProvider/PopupProvider"; interface Props extends AppProps {} @@ -70,8 +71,9 @@ const App: React.FunctionComponent = props => { - - + + +