From 722d162cfa27bf72fe27381bcd279d3de7b31fb4 Mon Sep 17 00:00:00 2001 From: Ian-potter Date: Thu, 6 Jun 2024 16:52:04 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20show=20=20approve=20?= =?UTF-8?q?detial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierrc | 21 +++ public/error.svg | 5 + public/portkeyLogoBlack.svg | 17 +++ src/app/social-start/page.tsx | 31 +++-- src/components/CommonImage/index.tsx | 11 ++ src/components/CommonImage/styles.module.css | 3 + src/constants/entry.ts | 14 ++ src/pages-components/entry/index.tsx | 98 +++++++++++++ .../entry/mobile-styles.module.css | 127 +++++++++++++++++ .../entry/pc-styles.module.css | 129 ++++++++++++++++++ src/types/auth.ts | 5 +- src/utils/mobile.ts | 22 +++ src/utils/model.ts | 64 +++++++++ 13 files changed, 534 insertions(+), 13 deletions(-) create mode 100644 .prettierrc create mode 100644 public/error.svg create mode 100644 public/portkeyLogoBlack.svg create mode 100644 src/components/CommonImage/index.tsx create mode 100644 src/components/CommonImage/styles.module.css create mode 100644 src/constants/entry.ts create mode 100644 src/pages-components/entry/index.tsx create mode 100644 src/pages-components/entry/mobile-styles.module.css create mode 100644 src/pages-components/entry/pc-styles.module.css create mode 100644 src/utils/mobile.ts create mode 100644 src/utils/model.ts diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3cc7493 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,21 @@ +{ + "jsxBracketSameLine": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 120, + "endOfLine": "lf", + "overrides": [ + { + "files": "*.css", + "options": { + "parser": "css" + } + }, + { + "files": "*.less", + "options": { + "parser": "less" + } + } + ] +} diff --git a/public/error.svg b/public/error.svg new file mode 100644 index 0000000..12cc054 --- /dev/null +++ b/public/error.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/portkeyLogoBlack.svg b/public/portkeyLogoBlack.svg new file mode 100644 index 0000000..ead57b1 --- /dev/null +++ b/public/portkeyLogoBlack.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/app/social-start/page.tsx b/src/app/social-start/page.tsx index d46987d..93d648f 100644 --- a/src/app/social-start/page.tsx +++ b/src/app/social-start/page.tsx @@ -1,30 +1,37 @@ -"use client"; -import React, { useMemo } from "react"; -import { useSearchParams } from "next/navigation"; -import { base64toJSON } from "src/utils"; -import { OPENLOGIN_ACTIONS } from "src/constants/social"; -import SocialAuth from "src/pages-components/social-auth"; -import { OpenLoginParamConfig } from "src/types/auth"; +'use client'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; +import { base64toJSON } from 'src/utils'; +import { OPENLOGIN_ACTIONS } from 'src/constants/social'; +import SocialAuth from 'src/pages-components/social-auth'; +import { OpenLoginParamConfig } from 'src/types/auth'; +import JumpEntry from 'src/pages-components/entry'; export default function SocialStart() { const searchParams = useSearchParams(); - const b64Params = searchParams.get("b64Params") || ""; + const b64Params = searchParams.get('b64Params') || ''; const params = useMemo(() => { try { const data = base64toJSON(b64Params); - console.log(data, "b64Params==="); + console.log(data, 'b64Params==='); return data as any; } catch (error) { return {}; } }, [b64Params]); + const [showAgree, setShowAggree] = useState(true); + const onApprove = useCallback(() => { + setShowAggree(false); + }, []); + + if (params?.actionType !== OPENLOGIN_ACTIONS.LOGIN) return <>`actionType` No Support; return (
- {params?.actionType === OPENLOGIN_ACTIONS.LOGIN ? ( - + {params.approveDetail && showAgree ? ( + ) : ( - <>`actionType` No Support + )}
); diff --git a/src/components/CommonImage/index.tsx b/src/components/CommonImage/index.tsx new file mode 100644 index 0000000..9df42c1 --- /dev/null +++ b/src/components/CommonImage/index.tsx @@ -0,0 +1,11 @@ +import clsx from 'clsx'; +import Image, { ImageProps } from 'next/image'; +import styles from './styles.module.css'; + +export default function CommonImage({ className, style, layout = 'fill', alt = 'img', ...props }: ImageProps) { + return ( +
+ {alt} +
+ ); +} diff --git a/src/components/CommonImage/styles.module.css b/src/components/CommonImage/styles.module.css new file mode 100644 index 0000000..dd65c73 --- /dev/null +++ b/src/components/CommonImage/styles.module.css @@ -0,0 +1,3 @@ +.common-img { + position: relative; +} diff --git a/src/constants/entry.ts b/src/constants/entry.ts new file mode 100644 index 0000000..3fd4816 --- /dev/null +++ b/src/constants/entry.ts @@ -0,0 +1,14 @@ +export const entryPageData = { + logo: { + src: '/portkeyLogoBlack.svg', + width: 230, + height: 78, + alt: 'Portkey Logo', + }, + errorIcon: { + src: '/error.svg', + width: 16, + height: 16, + alt: 'Error Icon', + }, +}; diff --git a/src/pages-components/entry/index.tsx b/src/pages-components/entry/index.tsx new file mode 100644 index 0000000..1093e41 --- /dev/null +++ b/src/pages-components/entry/index.tsx @@ -0,0 +1,98 @@ +import pcStyle from './pc-styles.module.css'; +import mobileStyle from './mobile-styles.module.css'; +import { Button, ConfigProvider } from 'antd'; +import { useCallback, useEffect, useState } from 'react'; +import { getCountry, translateOperationEnumToStr } from 'src/utils/model'; + +import CommonImage from 'src/components/CommonImage'; +import { entryPageData } from 'src/constants/entry'; +import { useStyleProvider } from 'src/utils/mobile'; +import { OpenLoginParamConfig } from 'src/types/auth'; + +export default function JumpEntry({ onApprove, authInfo }: { onApprove?: () => void; authInfo: OpenLoginParamConfig }) { + const { logo, errorIcon } = entryPageData; + const styles = useStyleProvider>({ + pcStyle, + mobileStyle, + }); + const [consumedData, setConsumedData] = useState<{ [x: string]: string | number | boolean } | undefined>({}); + const [ip, setIp] = useState('--'); + + const checkUserIp = useCallback(async (serviceUrl: string) => { + try { + const country = await getCountry(serviceUrl); + setIp(country); + // eslint-disable-next-line no-empty + } catch (ignored) {} + }, []); + + useEffect(() => { + try { + if (!authInfo) throw new Error('invalid data'); + console.log(authInfo, 'entryPrams==='); + const { approveDetail, serviceURI } = authInfo; + if (!approveDetail) return onApprove?.(); + const { symbol, amount, originChainId, targetChainId, operationType, guardian } = approveDetail; + const { guardianType, identifier, thirdPartyEmail } = guardian; + if (!serviceURI || !guardianType) { + throw new Error('invalid data'); + } + const raw: any = {}; + raw['Operation Type'] = translateOperationEnumToStr(operationType); + symbol && (raw['Token'] = symbol); + (amount || typeof amount === 'number') && (raw['Amount'] = amount); + const realChainId = targetChainId || originChainId; + raw['Chain'] = realChainId === `AELF` ? `MainChain ${realChainId}` : `SideChain ${realChainId}`; + raw['Guardian Type'] = guardianType; + const guardianAccount = thirdPartyEmail || identifier; + raw['Guardian Account'] = guardianAccount; + raw['Time'] = new Date().toLocaleString(); + raw['IP'] = ip; + setConsumedData(raw); + checkUserIp(serviceURI); + } catch (e) { + console.error(e); + setConsumedData(undefined); + } + }, [authInfo, checkUserIp, ip, onApprove]); + + return ( + +
+
+ +
+ {'Verification details are as follows. Proceed only if all data matches:'} +
+
+ {consumedData ? ( + Object.entries(consumedData).map(([key, value]) => { + return ( +
+
{key}
+
{value}
+
+ ); + }) + ) : ( +
+ +
Invalid parameter.
+
+ )} +
+ {!!consumedData && ( + + )} +
+
+
+ ); +} diff --git a/src/pages-components/entry/mobile-styles.module.css b/src/pages-components/entry/mobile-styles.module.css new file mode 100644 index 0000000..9eb7a22 --- /dev/null +++ b/src/pages-components/entry/mobile-styles.module.css @@ -0,0 +1,127 @@ +/* stylelint-disable selector-class-pattern */ +/* stylelint-disable custom-property-pattern */ +/* stylelint-disable font-family-no-missing-generic-family-keyword */ + +.container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 200%; +} + +.wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: 44px; + padding-left: 24px; + padding-right: 24px; +} + +.introductionText { + max-width: 320px; + color: var(--Light-Mode-Neutral-01-Primary-Text, #101114); + font-family: Roboto-Regular; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-top: 40px; +} + +.dashboard { + display: flex; + padding: 20px; + flex-direction: column; + align-items: flex-start; + gap: 10px; + align-self: stretch; + width: 320px; + margin-top: 16px; + border-radius: 6px; + background: var(--Light-Mode-Neutral-07-Container-BG, #f5f6f7); +} + +.infoLine { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.infoTitle { + color: var(--Light-Mode-Neutral-03-Tertiary-Text, #979aa1); + font-family: Roboto-Regular; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; /* 133.333% */ + width: 102px; + word-break: break-all; +} + +.infoContent { + width: 146px; + color: var(--Light-Mode-Neutral-01-Primary-Text, #101114); + font-family: Roboto-Regular; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; /* 133.333% */ + margin-left: 32px; + word-break: break-all; +} + +.jumpBtn { + margin-top: 48px; + width: 320px; + height: 48px; + background-color: #5b8ef4; + border-radius: 24px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.jumpBtnText { + color: #fff; + text-align: center; + font-family: Roboto-Regular; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 137.5% */ +} + +.errorText { + color: var(--Light-Mode-Neutral-01-Primary-Text, #101114); + font-family: Roboto-Regular; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; /* 133.333% */ + margin-left: 8px; +} + +.footer { + width: 320px; + position: absolute; + bottom: 0; +} + +.footerMedia { + display: flex; + flex-direction: column !important; + justify-content: center !important; + align-items: center !important; + border-top: 0 !important; +} + +.footerMediaPortkeyText { + padding-top: 16px; + padding-bottom: 26px; +} diff --git a/src/pages-components/entry/pc-styles.module.css b/src/pages-components/entry/pc-styles.module.css new file mode 100644 index 0000000..78c4a9a --- /dev/null +++ b/src/pages-components/entry/pc-styles.module.css @@ -0,0 +1,129 @@ +/* stylelint-disable selector-class-pattern */ +/* stylelint-disable custom-property-pattern */ +/* stylelint-disable font-family-no-missing-generic-family-keyword */ + +.container { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; +} + +.wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: 99px; +} + +.headerIcon { + width: 480px; + height: 208px; +} + +.introductionText { + max-width: 480px; + color: var(--Light-Mode-Neutral-01-Primary-Text, #101114); + font-family: Roboto-Regular; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-top: 40px; +} + +.dashboard { + display: flex; + padding: 20px; + flex-direction: column; + align-items: flex-start; + gap: 10px; + align-self: stretch; + width: 480px; + margin-top: 16px; + border-radius: 6px; + background: var(--Light-Mode-Neutral-07-Container-BG, #f5f6f7); +} + +.infoLine { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.infoTitle { + color: var(--Light-Mode-Neutral-03-Tertiary-Text, #979aa1); + font-family: Roboto-Regular; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; /* 133.333% */ + width: 102px; + word-break: break-all; +} + +.infoContent { + width: 306px; + color: var(--Light-Mode-Neutral-01-Primary-Text, #101114); + font-family: Roboto-Regular; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; /* 133.333% */ + margin-left: 32px; + word-break: break-all; +} + +.jumpBtn { + margin-top: 48px; + width: 480px; + height: 48px; + background-color: #5b8ef4; + border-radius: 24px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.jumpBtnText { + color: #fff; + text-align: center; + font-family: Roboto-Regular; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 137.5% */ +} + +.errorText { + color: var(--Light-Mode-Neutral-01-Primary-Text, #101114); + font-family: Roboto-Regular; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 16px; /* 133.333% */ + margin-left: 8px; +} + +.footer { + width: 480px; + position: absolute; + bottom: 0; +} + +.footerMedia { + display: flex; + flex-direction: column !important; + justify-content: center !important; + align-items: center !important; + padding-bottom: 4px !important; + border-top: 0 !important; +} + +.footerMediaPortkeyText { + padding-top: 16px; + padding-bottom: 26px; +} diff --git a/src/types/auth.ts b/src/types/auth.ts index 28d7685..eade590 100644 --- a/src/types/auth.ts +++ b/src/types/auth.ts @@ -1,3 +1,5 @@ +import { IVerifyEntryParams } from 'src/utils/model'; + export interface GoogleAuthProps { scope?: string; prompt?: string; @@ -12,7 +14,7 @@ export interface GoogleAuthProps { discoveryDocs?: string; children?: React.ReactNode; isOnlyGetToken?: boolean; - typeResponse?: "idToken" | "accessToken"; + typeResponse?: 'idToken' | 'accessToken'; fetchBasicProfile?: boolean; } @@ -24,6 +26,7 @@ export interface BaseOpenLoginParam { network: string; publicKey: string; serviceURI: string; + approveDetail?: IVerifyEntryParams; } export type OpenLoginParamConfig = BaseOpenLoginParam & Record; diff --git a/src/utils/mobile.ts b/src/utils/mobile.ts new file mode 100644 index 0000000..d9deae2 --- /dev/null +++ b/src/utils/mobile.ts @@ -0,0 +1,22 @@ +import { useEffect, useState } from 'react'; + +const MOBILE_DEFAULT_WIDTH = 500; + +export const useStyleProvider = (props: { pcStyle: T; mobileStyle: T; checker?: () => boolean }): T => { + const { pcStyle, mobileStyle, checker = defaultMobileChecker } = props; + const [isMobile, setIsMobile] = useState(false); + useEffect(() => { + if (!pcStyle || !mobileStyle) throw new Error('invalid style object.'); + setIsMobile(checker()); + }, [checker, mobileStyle, pcStyle]); + return isMobile ? mobileStyle : pcStyle; +}; + +export const defaultMobileChecker = (): boolean => { + const width = getCurrentWidth(); + return width <= MOBILE_DEFAULT_WIDTH; +}; + +export const getCurrentWidth = () => { + return window.screen.width ?? window.outerWidth ?? window.innerWidth ?? MOBILE_DEFAULT_WIDTH; +}; diff --git a/src/utils/model.ts b/src/utils/model.ts new file mode 100644 index 0000000..3a9f59e --- /dev/null +++ b/src/utils/model.ts @@ -0,0 +1,64 @@ +import { ChainId } from '@portkey/types'; +import { OperationTypeEnum } from '@portkey/services'; + +export interface IVerifyEntryParams { + guardian: { + guardianType: string; + identifier?: string; + thirdPartyEmail?: string; + }; + originChainId?: ChainId; + targetChainId?: ChainId; + operationType: OperationTypeEnum; + symbol?: string; + amount?: string; +} + +export const translateOperationEnumToStr = (enumPtr: OperationTypeEnum) => { + switch (enumPtr) { + case OperationTypeEnum.register: { + return 'Create AA Address'; + } + case OperationTypeEnum.communityRecovery: { + return 'Social Recovery'; + } + + case OperationTypeEnum.addGuardian: { + return 'Add Guardian'; + } + case OperationTypeEnum.deleteGuardian: { + return 'Remove Guardian'; + } + case OperationTypeEnum.editGuardian: { + return 'Update Guardian'; + } + case OperationTypeEnum.removeOtherManager: { + return 'Remove Device'; + } + case OperationTypeEnum.setLoginAccount: { + return 'Set Login Account'; + } + case OperationTypeEnum.managerApprove: { + return 'Approve'; + } + case OperationTypeEnum.modifyTransferLimit: { + return 'Set Transfer Limit'; + } + case OperationTypeEnum.transferApprove: { + return 'Guardian Approve Transfer'; + } + case OperationTypeEnum.unsetLoginAccount: { + return 'Unset Login Account'; + } + + default: + case OperationTypeEnum.unknown: { + return 'Unknown'; + } + } +}; + +export const getCountry = async (serviceUrl: string) => { + const data = await fetch(`${serviceUrl}/api/app/ipInfo/ipInfo`); + return (await data.json()).country; +}; From 66104287b3f29bebea1307a49c0cee43758901cb Mon Sep 17 00:00:00 2001 From: Ian-potter Date: Fri, 7 Jun 2024 15:46:56 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20default=20reca?= =?UTF-8?q?ptcha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/recaptcha/page.tsx | 44 +++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/app/recaptcha/page.tsx b/src/app/recaptcha/page.tsx index 937e3f2..647126a 100644 --- a/src/app/recaptcha/page.tsx +++ b/src/app/recaptcha/page.tsx @@ -1,54 +1,50 @@ -"use client"; -import React, { useEffect, useMemo } from "react"; -import GoogleReCaptcha from "src/components/GoogleReCaptcha"; -import { BaseReCaptcha } from "src/components/GoogleReCaptcha/types"; -import { useSendMessageByIframe } from "src/hooks/iframe"; -import { TSearch } from "src/types"; -import "./index.css"; +'use client'; +import React, { useEffect, useMemo } from 'react'; +import GoogleReCaptcha from 'src/components/GoogleReCaptcha'; +import { BaseReCaptcha } from 'src/components/GoogleReCaptcha/types'; +import { useSendMessageByIframe } from 'src/hooks/iframe'; +import { TSearch } from 'src/types'; +import './index.css'; -export default function Recaptcha({ - searchParams = {}, -}: { - searchParams: TSearch; -}) { +export default function Recaptcha({ searchParams = {} }: { searchParams: TSearch }) { const { onSuccess } = useSendMessageByIframe({ - target: "@portkey/ui-did-react:ReCaptcha", + target: '@portkey/ui-did-react:ReCaptcha', }); const { - theme = "light", - siteKey, - size = "normal", + theme = 'light', + siteKey = '6LfR_bElAAAAAJSOBuxle4dCFaciuu9zfxRQfQC0', + size = 'normal', } = useMemo(() => { - return searchParams as Omit; + return searchParams as Omit; }, [searchParams]); useEffect(() => { - document.body.classList.add("recaptcha-body"); + document.body.classList.add('recaptcha-body'); }, []); return (
{siteKey ? ( { onSuccess({ - type: "PortkeyReCaptchaOnSuccess", + type: 'PortkeyReCaptchaOnSuccess', data: res, }); }} onError={(res) => { - console.log(res, "onError"); + console.log(res, 'onError'); }} onExpire={(err) => { - console.log(err, "onExpire"); + console.log(err, 'onExpire'); }} /> ) : ( - "Invalid siteKey" + 'Invalid siteKey' )}
);