diff --git a/packages/core/package.json b/packages/core/package.json index aa57b111..d7429319 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@fileverse-dev/fortune-core", - "version": "1.0.88", + "version": "1.0.88-patch-10", "main": "lib/index.js", "module": "es/index.js", "typings": "lib/index.d.ts", @@ -15,7 +15,7 @@ "dev": "father-build --watch" }, "dependencies": { - "@fileverse-dev/formula-parser": "0.2.50", + "@fileverse-dev/formula-parser": "0.2.50-patch-7", "dayjs": "^1.11.0", "immer": "^9.0.12", "lodash": "^4.17.21", diff --git a/packages/formula-parser/package.json b/packages/formula-parser/package.json index ce87ad3d..32aacead 100644 --- a/packages/formula-parser/package.json +++ b/packages/formula-parser/package.json @@ -1,6 +1,6 @@ { "name": "@fileverse-dev/formula-parser", - "version": "0.2.50", + "version": "0.2.50-patch-7", "description": "Formula parser", "main": "lib/index.js", "module": "es/index.js", @@ -48,7 +48,7 @@ "webpack-cli": "^4.2.0" }, "dependencies": { - "@fileverse-dev/formulajs": "4.4.11-mod-89", + "@fileverse-dev/formulajs": "4.4.11-mod-93", "tiny-emitter": "^2.1.0" }, "jest": { diff --git a/packages/react/package.json b/packages/react/package.json index 1ce476ea..4cdc56ff 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@fileverse-dev/fortune-react", - "version": "1.0.88", + "version": "1.0.88-patch-10", "main": "lib/index.js", "types": "lib/index.d.ts", "module": "es/index.js", @@ -16,7 +16,7 @@ "tsc": "tsc" }, "dependencies": { - "@fileverse-dev/fortune-core": "1.0.88", + "@fileverse-dev/fortune-core": "1.0.88-patch-9", "@fileverse/ui": "^4.1.7-patch-21", "@tippyjs/react": "^4.2.6", "@types/regenerator-runtime": "^0.13.6", diff --git a/packages/react/src/components/SheetOverlay/FormulaHint/constants.ts b/packages/react/src/components/SheetOverlay/FormulaHint/constants.ts new file mode 100644 index 00000000..cdbfef3b --- /dev/null +++ b/packages/react/src/components/SheetOverlay/FormulaHint/constants.ts @@ -0,0 +1 @@ +export const GNOSIS_PAY_ACCESS = "GNOSIS_PAY_ACCESS"; diff --git a/packages/react/src/components/SheetOverlay/FormulaHint/index.tsx b/packages/react/src/components/SheetOverlay/FormulaHint/index.tsx index 2e9c3aa7..f6ed9bf7 100644 --- a/packages/react/src/components/SheetOverlay/FormulaHint/index.tsx +++ b/packages/react/src/components/SheetOverlay/FormulaHint/index.tsx @@ -3,6 +3,8 @@ import { Button, TextField, LucideIcon } from "@fileverse/ui"; import React, { useContext, useEffect, useRef, useState } from "react"; import WorkbookContext from "../../../context"; import "./index.css"; +import { timeFromNowMessage } from "./utils/utils"; +import useGnosisPay from "./use-gnosis-pay"; const FormulaHint: React.FC> = (props) => { const { context } = useContext(WorkbookContext); @@ -18,11 +20,23 @@ const FormulaHint: React.FC> = (props) => { ); const [showFunctionBody, setShouldShowFunctionBody] = useState(true); + const { + grantAccess, + handleGnosisPayToken, + hasGnosisPayToken, + isWrongGnosisPayConnector, + isLoading, + accessTokenCreatedAt, + timeLeft, + } = useGnosisPay(fn); + useEffect(() => { if (fn) { setApiKeyAdded(!!localStorage.getItem(fn?.API_KEY)); setAPI_KEY(localStorage.getItem(fn?.API_KEY) || ""); setShowAPInput(!localStorage.getItem(fn?.API_KEY)); + + handleGnosisPayToken(); } }, [fn]); const apiKeyPlaceholder: Record = { @@ -89,6 +103,7 @@ const FormulaHint: React.FC> = (props) => { if (el && handleWheel) el.removeEventListener("wheel", handleWheel); }; }, []); + if (!fn) return null; return ( @@ -213,7 +228,7 @@ const FormulaHint: React.FC> = (props) => { style={{ backgroundColor: `${fn.BRAND_COLOR ? fn.BRAND_COLOR : "#F8F9FA"}`, maxHeight: "318px", - overflowY: "scroll", + overflowY: "auto", }} > {fn.API_KEY && ( @@ -291,6 +306,74 @@ const FormulaHint: React.FC> = (props) => { )} + {fn.n === "GNOSISPAY" && ( +
+
{}} + > +

+ {hasGnosisPayToken + ? "Access granted" + : "Connect your Gnosis Pay account"} +

+
+
+

+ {!hasGnosisPayToken + ? "Grant access to your Gnosis Pay account and ensure you're using the same wallet for both Gnosis Pay and dSheet." + : ` You can now interact with your Gnosis Pay account for the next ${timeFromNowMessage( + timeLeft + )}. When the timer’s up, just re-grant access and you're good to go!`} +

+ +
+
+ )} +
{ + const gnosisTokenTokenIntervalRef = useRef(null); + const [isLoading, setIsLoading] = useState(false); + const [hasGnosisPayToken, setHasGnosisPayToken] = useState(false); + const [timeLeft, setTimeLeft] = useState("00:00"); + const [accessTokenCreatedAt, setAccessTokenCreatedAt] = useState(0); + const isWrongGnosisPayConnector = + localStorage.getItem("LOGIN_METHOD") !== "walletAddress"; + + const handleGnosisPayToken = (onDone?: () => void) => { + if (localStorage.getItem(GNOSIS_PAY_ACCESS)) { + const access = localStorage.getItem(GNOSIS_PAY_ACCESS) || ""; + if (!access || isGnosisPayAccessExpired(access)) { + if (hasGnosisPayToken) { + setHasGnosisPayToken(false); + } + if (accessTokenCreatedAt) { + setAccessTokenCreatedAt(0); + } + localStorage.removeItem(GNOSIS_PAY_ACCESS); + return; + } + setHasGnosisPayToken(!!access); + setAccessTokenCreatedAt(getJwtExpiry(access)); + onDone?.(); + } else { + const urlParams = new URLSearchParams(window.location.search); + const isRejected = urlParams.has("reject"); + if (isRejected) { + const url = new URL(window.location.href); + url.searchParams.delete("reject"); + window.history.replaceState({}, "", url.toString()); + onDone?.(); + } + } + }; + + useEffect(() => { + return () => { + if (gnosisTokenTokenIntervalRef.current) + clearInterval(gnosisTokenTokenIntervalRef.current); + }; + }, [gnosisTokenTokenIntervalRef]); + + useEffect(() => { + if (accessTokenCreatedAt <= 0) return () => {}; + + const interval = setInterval(() => { + const access = localStorage.getItem(GNOSIS_PAY_ACCESS) || ""; + const expiryTimestamp = getJwtExpiry(access) * 1000; + const newTimeLeft = expiryTimestamp - Date.now(); + setTimeLeft(formatTimeLeft(newTimeLeft)); + if ( + isGnosisPayAccessExpired(localStorage.getItem(GNOSIS_PAY_ACCESS)) || + !document.getElementById("gnosis-pay-area") + ) { + localStorage.removeItem(GNOSIS_PAY_ACCESS); + setHasGnosisPayToken(false); + setAccessTokenCreatedAt(0); + clearInterval(interval); + } + }, 1000); + + return () => { + clearInterval(interval); + }; + }, [accessTokenCreatedAt, fn]); + + const grantAccess = () => { + const button = document.getElementById("grant-gnosispay-access"); + if (!button) return; + button.click(); + setIsLoading(true); + const interval = setInterval(() => { + handleGnosisPayToken(() => { + clearInterval(interval); + setIsLoading(false); + }); + }, 5000); + + gnosisTokenTokenIntervalRef.current = interval; + }; + + return { + grantAccess, + handleGnosisPayToken, + hasGnosisPayToken, + isWrongGnosisPayConnector, + isLoading, + accessTokenCreatedAt, + timeLeft, + }; +}; + +export default useGnosisPay; diff --git a/packages/react/src/components/SheetOverlay/FormulaHint/utils/utils.ts b/packages/react/src/components/SheetOverlay/FormulaHint/utils/utils.ts new file mode 100644 index 00000000..aa10a2ab --- /dev/null +++ b/packages/react/src/components/SheetOverlay/FormulaHint/utils/utils.ts @@ -0,0 +1,27 @@ +export function formatTimeLeft(msLeft: number): string { + const totalSeconds = Math.max(0, Math.floor(msLeft / 1000)); + const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, "0"); + const seconds = String(totalSeconds % 60).padStart(2, "0"); + return `${minutes}:${seconds}`; +} +export function timeFromNowMessage(expiryStr: string): string { + if (!expiryStr) { + return "0 minute"; + } + const [mm] = expiryStr.split(":").map(Number); + + return `${mm} minute${mm !== 1 ? "s" : ""}`; +} +export function getJwtExpiry(token: string): number { + try { + const payloadBase64 = token?.split(".")?.[1]; + if (!payloadBase64) return 0; + + const payloadJson = atob(payloadBase64); + const payload = JSON.parse(payloadJson); + + return typeof payload.exp === "number" ? payload.exp : 0; + } catch (error) { + return 0; + } +} diff --git a/packages/react/src/components/SheetOverlay/FormulaSearch/index.tsx b/packages/react/src/components/SheetOverlay/FormulaSearch/index.tsx index fea55aac..3683e041 100644 --- a/packages/react/src/components/SheetOverlay/FormulaSearch/index.tsx +++ b/packages/react/src/components/SheetOverlay/FormulaSearch/index.tsx @@ -1,6 +1,12 @@ import _ from "lodash"; -import React, { useContext, useEffect, useRef, useState } from "react"; -import { LucideIcon, Tooltip } from "@fileverse/ui"; +import React, { + Fragment, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { cn, LucideIcon, Tooltip } from "@fileverse/ui"; import { UNFilter } from "./constant"; import WorkbookContext from "../../../context"; import "./index.css"; @@ -12,6 +18,8 @@ const FormulaSearch: React.FC> = ( context, settings: { isAuthorized }, } = useContext(WorkbookContext); + const isWrongGnosisPayConnector = + localStorage.getItem("LOGIN_METHOD") !== "walletAddress"; const authedFunction = [ "COINGECKO", "ETHERSCAN", @@ -226,14 +234,41 @@ const FormulaSearch: React.FC> = (
-
- {v.n} +
+ {v.LOGO && + isWrongGnosisPayConnector && + v.n === "GNOSISPAY" && ( +
+ Service Logo +
+ )} +
+ {v.n} +
+
> = ( gap: "6px", }} > - {v.LOGO && ( - Service Logo - )} - {v.SECONDARY_LOGO && ( - Service Logo - )} - {v.API_KEY && ( + {isWrongGnosisPayConnector && v.n === "GNOSISPAY" ? ( +

+ Your dSheet account was created via + email/social. Unfortunately you are not able to + use Gnosis Pay onchain function. +

+

+ To use Gnosis Pay onchain function you need to + create a new dSheets account via the same wallet + as your Gnosis Pay account. +

+
} > -
- -
+ + ) : ( + <> + {v.LOGO && ( + Service Logo + )} + {v.SECONDARY_LOGO && ( + Service Logo + )} + {v.API_KEY && ( + +
+ +
+
+ )} + )}
diff --git a/packages/react/src/components/SheetOverlay/InputBox.tsx b/packages/react/src/components/SheetOverlay/InputBox.tsx index bc9283c3..04f033dc 100644 --- a/packages/react/src/components/SheetOverlay/InputBox.tsx +++ b/packages/react/src/components/SheetOverlay/InputBox.tsx @@ -243,6 +243,10 @@ const InputBox: React.FC = () => { const formulaName = getActiveFormula()?.querySelector( ".luckysheet-formula-search-func" )?.textContent; + const isWrongGnosisPayConnector = + localStorage.getItem("LOGIN_METHOD") !== "walletAddress"; + + if (isWrongGnosisPayConnector && formulaName === "GNOSISPAY") return; if (formulaName) { insertSelectedFormula(formulaName); // User selects datablock @@ -257,10 +261,15 @@ const InputBox: React.FC = () => { (e: React.MouseEvent) => { // @ts-expect-error later if (e.target.className.includes("sign-fortune")) return; - preText.current = inputRef.current!.innerText; const formulaName = getActiveFormula()?.querySelector( ".luckysheet-formula-search-func" )?.textContent; + const isWrongGnosisPayConnector = + localStorage.getItem("LOGIN_METHOD") !== "walletAddress"; + + if (isWrongGnosisPayConnector && formulaName === "GNOSISPAY") return; + + preText.current = inputRef.current!.innerText; if (formulaName) { insertSelectedFormula(formulaName); e.preventDefault(); diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 6ed1be74..63146c95 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -3,6 +3,7 @@ import Workbook from "./Workbook"; export { ERROR_MESSAGES_FLAG, SERVICES_API_KEY, + isGnosisPayAccessExpired, // @ts-ignore } from "@fileverse-dev/formulajs/crypto-constants"; export { Workbook }; diff --git a/yarn.lock b/yarn.lock index f2397dfd..7b538174 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2402,15 +2402,16 @@ resolved "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz" integrity sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ== -"@fileverse-dev/formulajs@4.4.11-mod-89": - version "4.4.11-mod-89" - resolved "https://registry.yarnpkg.com/@fileverse-dev/formulajs/-/formulajs-4.4.11-mod-89.tgz#9f231e3d44399f95eda0bf6432c70fbd11a40322" - integrity sha512-ZoXtKwAccVbtunjiqNwFPUd6sGhgyYP/BY4z6YBjZ3R1JpganjK47COOK9CoKe7naZVlACPj0+Py50GJtxfO+Q== +"@fileverse-dev/formulajs@4.4.11-mod-93": + version "4.4.11-mod-93" + resolved "https://registry.yarnpkg.com/@fileverse-dev/formulajs/-/formulajs-4.4.11-mod-93.tgz#78a200bed9166fb358154fe477dbb7b73023b662" + integrity sha512-FW//jlcU0Uxm2RNvx1Ra2OrufJV5NsLrnKQF8mZ4P9j/mXelNtbZBNNrfTa8yhpDW6RP85Ckhtu7QZXD5p7pCg== dependencies: bessel "^1.0.2" esbuild "^0.25.4" js-sha3 "^0.9.3" jstat "^1.9.6" + jwt-check-expiration "^1.0.5" zod "^3.25.71" "@fileverse/ui@^4.1.7-patch-21": @@ -14798,6 +14799,18 @@ jstat@^1.9.6: object.assign "^4.1.4" object.values "^1.1.6" +jwt-check-expiration@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/jwt-check-expiration/-/jwt-check-expiration-1.0.5.tgz#4161c3090831b5279f8f0d7cb44ecb08ad852991" + integrity sha512-Ov2A7f/zwiZ8wvi6KG+Jeb6am1pvwgcms+HytB4GMPzrCf5UfytnlBVRh9a39Hi2v91H3aETtXHZreB8BTdbUQ== + dependencies: + jwt-decode "^2.2.0" + +jwt-decode@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" + integrity sha512-86GgN2vzfUu7m9Wcj63iUkuDzFNYFVmjeDm2GzWpUk+opB0pEpMsw6ePCMrhYkumz2C1ihqtZzOMAg7FiXcNoQ== + keyv@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz"