From 80b497786b2849dbf9c80f7decaa5672d79000bf Mon Sep 17 00:00:00 2001 From: quinn-gao Date: Fri, 21 Nov 2025 10:44:08 +0800 Subject: [PATCH 01/41] fix: add judgement --- .../components/people/judgement/discord.jsx | 50 ++++++++++++ .../components/people/judgement/element.jsx | 65 +++++++++++++++ .../components/people/judgement/email.jsx | 81 +++++++++++++++++++ .../components/people/judgement/index.jsx | 70 ++++++++++++++++ .../components/people/judgement/twitter.jsx | 51 ++++++++++++ .../next-common/utils/consts/menu/people.jsx | 6 ++ .../pages/people/judgement/auth/discord.jsx | 30 +++++++ .../pages/people/judgement/auth/twitter.jsx | 27 +++++++ .../next/pages/people/judgement/index.jsx | 65 +++++++++++++++ 9 files changed, 445 insertions(+) create mode 100644 packages/next-common/components/people/judgement/discord.jsx create mode 100644 packages/next-common/components/people/judgement/element.jsx create mode 100644 packages/next-common/components/people/judgement/email.jsx create mode 100644 packages/next-common/components/people/judgement/index.jsx create mode 100644 packages/next-common/components/people/judgement/twitter.jsx create mode 100644 packages/next/pages/people/judgement/auth/discord.jsx create mode 100644 packages/next/pages/people/judgement/auth/twitter.jsx create mode 100644 packages/next/pages/people/judgement/index.jsx diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx new file mode 100644 index 0000000000..d6e1330b3a --- /dev/null +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -0,0 +1,50 @@ +import { LinkDiscord } from "@osn/icons/subsquare"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import PrimaryButton from "next-common/lib/button/primary"; +import { useMemo } from "react"; + +function generateDiscordAuthLink(state = {}) { + const client_id = process.env.NEXT_PUBLIC_DISCORD_CLIENT_ID; + const redirect_uri = location.origin + "/people/judgement/auth/discord"; + + const params = new URLSearchParams({ + client_id, + redirect_uri, + response_type: "code", + scope: "identify", + state: encodeURIComponent(JSON.stringify(state)), + }); + + return `https://discord.com/oauth2/authorize?${params.toString()}`; +} + +export default function Discord() { + const link = useMemo(() => generateDiscordAuthLink(), []); + return ( +
+
+
+
+ + · +

Discord

+
+
+ Pending +
+
+
+
+
+ Username: + + pending@example.com + +
+ + Connect Discord + +
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/element.jsx b/packages/next-common/components/people/judgement/element.jsx new file mode 100644 index 0000000000..c8e8ca948d --- /dev/null +++ b/packages/next-common/components/people/judgement/element.jsx @@ -0,0 +1,65 @@ +import { LinkElement } from "@osn/icons/subsquare"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import PrimaryButton from "next-common/lib/button/primary"; +import SecondaryButton from "next-common/lib/button/secondary"; +import Copyable from "next-common/components/copyable"; + +export default function Email() { + return ( +
+
+
+
+ + · +

Element

+
+
+ Pending +
+
+ Verify +
+
+
+ Matrix ID: + @quinn:matrix.org +
+
+ +
+ +
+ + #Subsquare-Identity-Verify:matrix.org + + copy +
+
+
+ +
+ + XXX-XXX-1234 + +
+
+
+ +
+ After sending the code in the room, click the "verify" + button below to check if verification is complete. +
+
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/email.jsx b/packages/next-common/components/people/judgement/email.jsx new file mode 100644 index 0000000000..960fa914a4 --- /dev/null +++ b/packages/next-common/components/people/judgement/email.jsx @@ -0,0 +1,81 @@ +import { LinkEmail } from "@osn/icons/subsquare"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import PrimaryButton from "next-common/lib/button/primary"; +import SecondaryButton from "next-common/lib/button/secondary"; +import Input from "next-common/lib/input"; +import { useState, useEffect } from "react"; + +export default function Email() { + return ( +
+
+
+
+ + · +

Email

+
+
+ Pending +
+
+ +
+
+
+ Email Address: + + pending@example.com + +
+
+
+

+ Tips: We‘ve send a 6-digit + code to your email. Code expires in 10 minutes. +

+
+
+ +
+ + + Verify +
+
+
+ ); +} + +function SendCode() { + const [countdown, setCountdown] = useState(0); + const [loading, setLoading] = useState(false); + + useEffect(() => { + let timer; + if (countdown > 0) { + timer = setTimeout(() => setCountdown(countdown - 1), 1000); + } + return () => clearTimeout(timer); + }, [countdown]); + + const sendCode = async () => { + setLoading(true); + setTimeout(() => { + setLoading(false); + setCountdown(60); + }, 1500); + }; + return ( + + Send Code {countdown ? ` ${countdown} s` : ""} + + ); +} diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx new file mode 100644 index 0000000000..8e848258f6 --- /dev/null +++ b/packages/next-common/components/people/judgement/index.jsx @@ -0,0 +1,70 @@ +import ListLayout from "next-common/components/layout/ListLayout"; +import ChainSocialLinks from "next-common/components/chain/socialLinks"; +import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; +import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; +import Account from "next-common/components/account"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { SummaryGreyText } from "next-common/components/summary/styled"; +import SummaryLayout from "next-common/components/summary/layout/layout"; +import SummaryItem from "next-common/components/summary/layout/item"; +import Email from "./email"; +import Discord from "./discord"; +import Twitter from "./twitter"; +import Element from "./element"; +export const tabs = [ + { + value: "Judgement", + label: "Judgement", + url: "/people/judgement", + exactMatch: false, + }, +]; + +export default function JudgementPage() { + const address = useRealAddress(); + return ( + + } + > +
+
+ +
+ + + 0 + + + {0} + + + + + {0} + / {0} + + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx new file mode 100644 index 0000000000..7a5fc916e6 --- /dev/null +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -0,0 +1,51 @@ +import { LinkTwitter } from "@osn/icons/subsquare"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import PrimaryButton from "next-common/lib/button/primary"; +import { useMemo } from "react"; + +const scope = "tweet.read users.read offline.access"; +const client_id = process.env.NEXT_PUBLIC_X_CLIENT_ID; +const redirect_uri = location.origin + "/people/judgement/auth/twitter"; + +function generateAuthLink(data = {}) { + const params = new URLSearchParams({ + state: encodeURIComponent(JSON.stringify(data)), + client_id, + redirect_uri, + scope, + response_type: "code", + code_challenge: "challenge", + code_challenge_method: "plain", + }); + + return `https://x.com/i/oauth2/authorize?${params}`; +} + +export default function Twitter() { + const link = useMemo(() => generateAuthLink(), []); + return ( +
+
+
+
+ + · +

Twitter

+
+
+ Pending +
+
+
+
+
+ Username: + @QuinnGao +
+ + Connect Twitter + +
+
+ ); +} diff --git a/packages/next-common/utils/consts/menu/people.jsx b/packages/next-common/utils/consts/menu/people.jsx index 21d97d1150..f1a788f327 100644 --- a/packages/next-common/utils/consts/menu/people.jsx +++ b/packages/next-common/utils/consts/menu/people.jsx @@ -22,6 +22,12 @@ export const peopleMenu = { pathname: "/people", icon: , }, + { + name: "Judgement", + value: "judgement", + pathname: "/people/judgement", + icon: , + }, { name: "Identities", value: "identities", diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/judgement/auth/discord.jsx new file mode 100644 index 0000000000..2c28058432 --- /dev/null +++ b/packages/next/pages/people/judgement/auth/discord.jsx @@ -0,0 +1,30 @@ +import ListLayout from "next-common/components/layout/ListLayout"; +import ChainSocialLinks from "next-common/components/chain/socialLinks"; +import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; +import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; +import Account from "next-common/components/account"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { PeopleGlobalProvider } from ".."; + +export default function Page() { + const address = useRealAddress(); + return ( + + + } + > +
+
+ +
+
+
+
+
+
+ ); +} diff --git a/packages/next/pages/people/judgement/auth/twitter.jsx b/packages/next/pages/people/judgement/auth/twitter.jsx new file mode 100644 index 0000000000..2e5acf5490 --- /dev/null +++ b/packages/next/pages/people/judgement/auth/twitter.jsx @@ -0,0 +1,27 @@ +import ListLayout from "next-common/components/layout/ListLayout"; +import ChainSocialLinks from "next-common/components/chain/socialLinks"; +import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; +import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; +import Account from "next-common/components/account"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; + +export default function Page() { + const address = useRealAddress(); + return ( + + } + > +
+
+ +
+
+
+
+
+ ); +} diff --git a/packages/next/pages/people/judgement/index.jsx b/packages/next/pages/people/judgement/index.jsx new file mode 100644 index 0000000000..db3397f469 --- /dev/null +++ b/packages/next/pages/people/judgement/index.jsx @@ -0,0 +1,65 @@ +import { CHAIN } from "next-common/utils/constants"; +import { createStore } from "next-common/store"; +import { commonReducers } from "next-common/store/reducers"; +import { withCommonProps } from "next-common/lib"; +import RelayInfoProvider from "next-common/context/relayInfo"; +import ApiProvider from "next-common/context/api"; +import ChainProvider from "next-common/context/chain"; +import { Provider } from "react-redux"; +import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; +import getChainSettings from "next-common/utils/consts/settings"; + +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; + +const PeopleOverviewPageImpl = dynamicClientOnly(() => + import("next-common/components/people/judgement"), +); + +let chain; +let store; + +if (isPeopleSupported) { + chain = `${CHAIN}-people`; + store = createStore({ + chain, + reducer: commonReducers, + }); +} + +export const PeopleGlobalProvider = ({ children }) => { + if (!isPeopleSupported) { + return null; + } + + return ( + + + + {children} + + + + ); +}; + +export default function PeoplePage() { + return ( + + + + ); +} + +export const getServerSideProps = async (ctx) => { + if (!isPeopleSupported) { + return { + notFound: true, + }; + } + + return withCommonProps(async () => { + return { + props: {}, + }; + })(ctx); +}; From 1742ab5568a4e9f6f862c197ec7e52c85ad1f71a Mon Sep 17 00:00:00 2001 From: leochen Date: Fri, 21 Nov 2025 15:40:36 +0800 Subject: [PATCH 02/41] add check judgement entry, #6881 --- .../overview/identity/checkJudgement.jsx | 96 +++++++++++++++++++ .../identity/directIdentityActions.jsx | 2 + 2 files changed, 98 insertions(+) create mode 100644 packages/next-common/components/people/overview/identity/checkJudgement.jsx diff --git a/packages/next-common/components/people/overview/identity/checkJudgement.jsx b/packages/next-common/components/people/overview/identity/checkJudgement.jsx new file mode 100644 index 0000000000..dc58ce9976 --- /dev/null +++ b/packages/next-common/components/people/overview/identity/checkJudgement.jsx @@ -0,0 +1,96 @@ +import { useState, useCallback, useMemo } from "react"; +import { useIdentityInfoContext } from "next-common/context/people/identityInfoContext"; +import dynamicPopup from "next-common/lib/dynamic/popup"; +import { isIdentityEmpty } from "next-common/components/people/common"; +import PrimaryButton from "next-common/lib/button/primary"; +import { isEmpty } from "lodash-es"; +import { useRouter } from "next/router"; + +const RequestJudgementPopup = dynamicPopup( + () => import("next-common/components/requestJudgementPopup"), + { + ssr: false, + }, +); + +// TODO: default subsquare registrar index. +const SUBSQUARE_REGISTRAR_INDEX = "2"; + +const VERIFIED_STATUSES = ["Reasonable", "KnownGood"]; + +function isPendingRequestBySubsquare(judgements) { + return judgements?.some( + (judgement) => judgement.index === SUBSQUARE_REGISTRAR_INDEX, + ); +} + +function isVerifiedStatus(judgements) { + return judgements?.some((judgement) => + VERIFIED_STATUSES.includes(judgement.status), + ); +} + +function RequestJudgement() { + const [showPopup, setShowPopup] = useState(false); + const { info, isLoading } = useIdentityInfoContext(); + + const isInfoEmpty = useMemo(() => isIdentityEmpty(info), [info]); + + const handleOpenPopup = useCallback(() => { + setShowPopup(true); + }, []); + + const handleClosePopup = useCallback(() => { + setShowPopup(false); + }, []); + + if (isLoading || isInfoEmpty) { + return null; + } + + return ( +
+ + Request Subsquare Judgement + + {showPopup && } +
+ ); +} + +function CheckSubsquareJudgement() { + const router = useRouter(); + + const handleCheckJudgement = useCallback(() => { + router.push("/people/judgement"); + }, [router]); + + return ( +
+ + Check Your Judgement + +
+ ); +} + +export default function CheckJudgement() { + const { judgements, isLoading } = useIdentityInfoContext(); + if (isLoading) { + return null; + } + + if (isEmpty(judgements)) { + return ; + } + + if (isVerifiedStatus(judgements)) { + return null; + } + + if (isPendingRequestBySubsquare(judgements)) { + return ; + } + + return ; +} diff --git a/packages/next-common/components/people/overview/identity/directIdentityActions.jsx b/packages/next-common/components/people/overview/identity/directIdentityActions.jsx index e500e790ad..1cd1a4d618 100644 --- a/packages/next-common/components/people/overview/identity/directIdentityActions.jsx +++ b/packages/next-common/components/people/overview/identity/directIdentityActions.jsx @@ -13,6 +13,7 @@ import { useExtensionAccounts } from "next-common/components/popupWithSigner/con import getChainSettings from "next-common/utils/consts/settings"; import { useCallback, useState } from "react"; import dynamicPopup from "next-common/lib/dynamic/popup"; +import CheckJudgement from "./checkJudgement"; const SetIdentityPopup = dynamicPopup( () => import("next-common/components/setIdentityPopup"), @@ -65,6 +66,7 @@ export function DirectIdentityActions() { return ( <>
+
Date: Fri, 21 Nov 2025 15:43:28 +0800 Subject: [PATCH 03/41] fix: add identity skeleton, #6881 --- .../components/people/judgement/discord.jsx | 7 +- .../components/people/judgement/element.jsx | 38 ++++++--- .../components/people/judgement/email.jsx | 31 ++++++- .../components/people/judgement/index.jsx | 44 ++++++---- .../components/people/judgement/twitter.jsx | 11 +-- .../next-common/utils/consts/menu/people.jsx | 11 ++- .../pages/people/judgement/auth/discord.jsx | 81 +++++++++++++++---- .../pages/people/judgement/auth/twitter.jsx | 70 +++++++++++++--- .../next/pages/people/judgement/index.jsx | 34 +------- 9 files changed, 225 insertions(+), 102 deletions(-) diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index d6e1330b3a..e3663c03f9 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -41,9 +41,10 @@ export default function Discord() { pending@example.com
- - Connect Discord - + + window.open(link)} size="small"> + Connect Discord +
); diff --git a/packages/next-common/components/people/judgement/element.jsx b/packages/next-common/components/people/judgement/element.jsx index c8e8ca948d..bbd6a67092 100644 --- a/packages/next-common/components/people/judgement/element.jsx +++ b/packages/next-common/components/people/judgement/element.jsx @@ -1,8 +1,10 @@ import { LinkElement } from "@osn/icons/subsquare"; import { ClosedTag } from "next-common/components/tags/state/styled"; import PrimaryButton from "next-common/lib/button/primary"; -import SecondaryButton from "next-common/lib/button/secondary"; import Copyable from "next-common/components/copyable"; +import { newSuccessToast } from "next-common/store/reducers/toastSlice"; +import { useDispatch } from "react-redux"; +import { useState } from "react"; export default function Email() { return ( @@ -18,7 +20,7 @@ export default function Email() { Pending - Verify +
@@ -32,13 +34,14 @@ export default function Email() { Step 1: Join Verify Room
@@ -63,3 +66,20 @@ export default function Email() {
); } + +function VerifyButton() { + const dispatch = useDispatch(); + const [loading, setLoading] = useState(false); + const verifyCode = async () => { + setLoading(true); + setTimeout(() => { + setLoading(false); + dispatch(newSuccessToast("Verify element successfully")); + }, 1500); + }; + return ( + + Verify + + ); +} diff --git a/packages/next-common/components/people/judgement/email.jsx b/packages/next-common/components/people/judgement/email.jsx index 960fa914a4..44b485f1a3 100644 --- a/packages/next-common/components/people/judgement/email.jsx +++ b/packages/next-common/components/people/judgement/email.jsx @@ -4,8 +4,11 @@ import PrimaryButton from "next-common/lib/button/primary"; import SecondaryButton from "next-common/lib/button/secondary"; import Input from "next-common/lib/input"; import { useState, useEffect } from "react"; +import { newSuccessToast } from "next-common/store/reducers/toastSlice"; +import { useDispatch } from "react-redux"; export default function Email() { + const [code, setCode] = useState(""); return (
@@ -40,16 +43,37 @@ export default function Email() { Enter Verification Code
- - - Verify + setCode(e.target.value)} + /> +
); } +function VerifyButton({ code }) { + const dispatch = useDispatch(); + const [loading, setLoading] = useState(false); + const verifyCode = async () => { + setLoading(true); + setTimeout(() => { + setLoading(false); + dispatch(newSuccessToast("Verify code successfully")); + }, 1500); + }; + return ( + + Verify + + ); +} + function SendCode() { + const dispatch = useDispatch(); const [countdown, setCountdown] = useState(0); const [loading, setLoading] = useState(false); @@ -65,6 +89,7 @@ function SendCode() { setLoading(true); setTimeout(() => { setLoading(false); + dispatch(newSuccessToast("Send email successfully")); setCountdown(60); }, 1500); }; diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index 8e848258f6..dfd4f95012 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -11,6 +11,8 @@ import Email from "./email"; import Discord from "./discord"; import Twitter from "./twitter"; import Element from "./element"; +import { useState, useEffect } from "react"; +import Loading from "next-common/components/loading"; export const tabs = [ { value: "Judgement", @@ -22,6 +24,13 @@ export const tabs = [ export default function JudgementPage() { const address = useRealAddress(); + const [loading, setLoading] = useState(true); + useEffect(() => { + setTimeout(() => { + setLoading(false); + }, 1500); + }, []); + return ( } >
-
+
- + 0 {0} - {0} @@ -50,20 +58,26 @@ export default function JudgementPage() {
-
-
- -
-
- + {loading ? ( +
+
-
- + ) : ( +
+
+ +
+
+ +
+
+ +
+
+ +
-
- -
-
+ )} ); diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index 7a5fc916e6..58f829f1f5 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -7,9 +7,9 @@ const scope = "tweet.read users.read offline.access"; const client_id = process.env.NEXT_PUBLIC_X_CLIENT_ID; const redirect_uri = location.origin + "/people/judgement/auth/twitter"; -function generateAuthLink(data = {}) { +function generateAuthLink(state = {}) { const params = new URLSearchParams({ - state: encodeURIComponent(JSON.stringify(data)), + state: encodeURIComponent(JSON.stringify(state)), client_id, redirect_uri, scope, @@ -42,9 +42,10 @@ export default function Twitter() { Username: @QuinnGao
- - Connect Twitter - + window.open(link)}> + Connect Twitter + + {/* */}
); diff --git a/packages/next-common/utils/consts/menu/people.jsx b/packages/next-common/utils/consts/menu/people.jsx index f1a788f327..cf6e21c62b 100644 --- a/packages/next-common/utils/consts/menu/people.jsx +++ b/packages/next-common/utils/consts/menu/people.jsx @@ -21,12 +21,11 @@ export const peopleMenu = { value: "overview", pathname: "/people", icon: , - }, - { - name: "Judgement", - value: "judgement", - pathname: "/people/judgement", - icon: , + extraMatchNavMenuActivePathnames: [ + "/people/judgement", + "/people/judgement/auth/discord", + "/people/judgement/auth/twitter", + ], }, { name: "Identities", diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/judgement/auth/discord.jsx index 2c28058432..978ffae929 100644 --- a/packages/next/pages/people/judgement/auth/discord.jsx +++ b/packages/next/pages/people/judgement/auth/discord.jsx @@ -1,30 +1,77 @@ import ListLayout from "next-common/components/layout/ListLayout"; -import ChainSocialLinks from "next-common/components/chain/socialLinks"; -import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; import Account from "next-common/components/account"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; -import { PeopleGlobalProvider } from ".."; +import { PeopleGlobalProvider } from "../../index"; +import { CHAIN } from "next-common/utils/constants"; +import { withCommonProps } from "next-common/lib"; +import getChainSettings from "next-common/utils/consts/settings"; +import PrimaryButton from "next-common/lib/button/primary"; +import KvList from "next-common/components/listInfo/kvList"; + +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; export default function Page() { - const address = useRealAddress(); return ( - - } - > -
-
+ + + ); +} + +function PageImpl() { + const address = useRealAddress(); + return ( + <> + +
+ + } + > +
+

+ Verify Your Discord Account Successfully +

+ + -
- - - +
+
+
+ ); } + +export const getServerSideProps = async (ctx) => { + if (!isPeopleSupported) { + return { + notFound: true, + }; + } + + return withCommonProps(async () => { + return { + props: {}, + }; + })(ctx); +}; diff --git a/packages/next/pages/people/judgement/auth/twitter.jsx b/packages/next/pages/people/judgement/auth/twitter.jsx index 2e5acf5490..71d2ead2d4 100644 --- a/packages/next/pages/people/judgement/auth/twitter.jsx +++ b/packages/next/pages/people/judgement/auth/twitter.jsx @@ -1,27 +1,75 @@ import ListLayout from "next-common/components/layout/ListLayout"; -import ChainSocialLinks from "next-common/components/chain/socialLinks"; -import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; import Account from "next-common/components/account"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { PeopleGlobalProvider } from "../../index"; +import { CHAIN } from "next-common/utils/constants"; +import { withCommonProps } from "next-common/lib"; +import getChainSettings from "next-common/utils/consts/settings"; +import PrimaryButton from "next-common/lib/button/primary"; +import KvList from "next-common/components/listInfo/kvList"; + +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; export default function Page() { + return ( + + + + ); +} + +function PageImpl() { const address = useRealAddress(); return ( - + <> } + title="Subsquare Judgement Verify" + seoInfo={{ + rawTitle: generateLayoutRawTitle("Subsquare verify your X account"), + }} + description={"Subsquare verify your X account"} + headContent={ + <> +
+ +
+ + } > -
-
- +
+

+ Verify Your X Account Successfully +

+ +
- + ); } + +export const getServerSideProps = async (ctx) => { + if (!isPeopleSupported) { + return { + notFound: true, + }; + } + + return withCommonProps(async () => { + return { + props: {}, + }; + })(ctx); +}; diff --git a/packages/next/pages/people/judgement/index.jsx b/packages/next/pages/people/judgement/index.jsx index db3397f469..478ea43d62 100644 --- a/packages/next/pages/people/judgement/index.jsx +++ b/packages/next/pages/people/judgement/index.jsx @@ -1,13 +1,8 @@ import { CHAIN } from "next-common/utils/constants"; -import { createStore } from "next-common/store"; -import { commonReducers } from "next-common/store/reducers"; import { withCommonProps } from "next-common/lib"; -import RelayInfoProvider from "next-common/context/relayInfo"; -import ApiProvider from "next-common/context/api"; -import ChainProvider from "next-common/context/chain"; -import { Provider } from "react-redux"; import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; import getChainSettings from "next-common/utils/consts/settings"; +import { PeopleGlobalProvider } from ".."; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; @@ -15,33 +10,6 @@ const PeopleOverviewPageImpl = dynamicClientOnly(() => import("next-common/components/people/judgement"), ); -let chain; -let store; - -if (isPeopleSupported) { - chain = `${CHAIN}-people`; - store = createStore({ - chain, - reducer: commonReducers, - }); -} - -export const PeopleGlobalProvider = ({ children }) => { - if (!isPeopleSupported) { - return null; - } - - return ( - - - - {children} - - - - ); -}; - export default function PeoplePage() { return ( From ddd8f7eace0409931269b7e5086dca548f261c33 Mon Sep 17 00:00:00 2001 From: leochen Date: Fri, 21 Nov 2025 17:31:18 +0800 Subject: [PATCH 04/41] default registrar, #6881 --- .../overview/hooks/useDefaultRegistrar.js | 40 +++++++++++++++++++ .../overview/identity/checkJudgement.jsx | 19 +++++++-- .../requestJudgementPopup/content.jsx | 6 ++- .../requestJudgementPopup/index.jsx | 7 +++- 4 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js diff --git a/packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js b/packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js new file mode 100644 index 0000000000..6c93841fdd --- /dev/null +++ b/packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js @@ -0,0 +1,40 @@ +import { useState, useMemo, useEffect } from "react"; +import { isEmpty, isNil } from "lodash-es"; +import { useRegistrarContext } from "next-common/context/people/registrarContext"; +import useJudgementsData from "next-common/components/people/overview/hooks/useJudgementsData"; + +export default function useDefaultRegistrar(defaultRegistrarIndex) { + const { registrars } = useRegistrarContext(); + const { data = [] } = useJudgementsData(); + const [dafaultValue, setDefaultValue] = useState(null); + + const selectableRegistrars = useMemo(() => { + if (isEmpty(registrars) || isEmpty(data)) { + return []; + } + + return registrars?.filter( + (s) => !data.some((r) => r.account === s.account), + ); + }, [data, registrars]); + + const defaultRegistrarObj = useMemo(() => { + if (isEmpty(selectableRegistrars)) { + return null; + } + + return selectableRegistrars?.find( + (registrar) => String(registrar?.index) === defaultRegistrarIndex, + ); + }, [selectableRegistrars, defaultRegistrarIndex]); + + useEffect(() => { + if (isNil(defaultRegistrarObj)) { + return; + } + + setDefaultValue(defaultRegistrarObj?.account); + }, [defaultRegistrarObj]); + + return dafaultValue; +} diff --git a/packages/next-common/components/people/overview/identity/checkJudgement.jsx b/packages/next-common/components/people/overview/identity/checkJudgement.jsx index dc58ce9976..8f961effe9 100644 --- a/packages/next-common/components/people/overview/identity/checkJudgement.jsx +++ b/packages/next-common/components/people/overview/identity/checkJudgement.jsx @@ -5,6 +5,7 @@ import { isIdentityEmpty } from "next-common/components/people/common"; import PrimaryButton from "next-common/lib/button/primary"; import { isEmpty } from "lodash-es"; import { useRouter } from "next/router"; +import useDefaultRegistrar from "next-common/components/people/overview/hooks/useDefaultRegistrar"; const RequestJudgementPopup = dynamicPopup( () => import("next-common/components/requestJudgementPopup"), @@ -14,8 +15,7 @@ const RequestJudgementPopup = dynamicPopup( ); // TODO: default subsquare registrar index. -const SUBSQUARE_REGISTRAR_INDEX = "2"; - +const SUBSQUARE_REGISTRAR_INDEX = "0"; const VERIFIED_STATUSES = ["Reasonable", "KnownGood"]; function isPendingRequestBySubsquare(judgements) { @@ -30,6 +30,17 @@ function isVerifiedStatus(judgements) { ); } +function RequestJudgementPopupImpl({ onClose }) { + const defaultRegistrar = useDefaultRegistrar(SUBSQUARE_REGISTRAR_INDEX); + + return ( + + ); +} + function RequestJudgement() { const [showPopup, setShowPopup] = useState(false); const { info, isLoading } = useIdentityInfoContext(); @@ -51,9 +62,9 @@ function RequestJudgement() { return (
- Request Subsquare Judgement + Request Judgement - {showPopup && } + {showPopup && }
); } diff --git a/packages/next-common/components/requestJudgementPopup/content.jsx b/packages/next-common/components/requestJudgementPopup/content.jsx index cc676416fa..8dff9b8743 100644 --- a/packages/next-common/components/requestJudgementPopup/content.jsx +++ b/packages/next-common/components/requestJudgementPopup/content.jsx @@ -26,8 +26,10 @@ const StyledSignerWithBalance = styled.div` } `; -export default function RequestJudgementPopupContent() { - const [value, setValue] = useState(); +export default function RequestJudgementPopupContent({ + defaultRegistrar = null, +}) { + const [value, setValue] = useState(defaultRegistrar); const { symbol, decimals } = useChainSettings(); const { registrars, isLoading } = useRegistrarContext(); const api = useContextApi(); diff --git a/packages/next-common/components/requestJudgementPopup/index.jsx b/packages/next-common/components/requestJudgementPopup/index.jsx index 4eb78519cb..2df3666c39 100644 --- a/packages/next-common/components/requestJudgementPopup/index.jsx +++ b/packages/next-common/components/requestJudgementPopup/index.jsx @@ -2,11 +2,14 @@ import Popup from "../popup/wrapper/Popup"; import SignerPopupWrapper from "../popupWithSigner/signerPopupWrapper"; import RequestJudgementPopupContent from "./content"; -export default function RequestJudgementPopup({ onClose }) { +export default function RequestJudgementPopup({ + onClose, + defaultRegistrar = null, +}) { return ( - + ); From 8df2a57ec5efd63e750fbd5443133dda0b12c63d Mon Sep 17 00:00:00 2001 From: leochen Date: Fri, 21 Nov 2025 17:49:43 +0800 Subject: [PATCH 05/41] Fix default registrar, #6881 --- .../components/people/overview/hooks/useDefaultRegistrar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js b/packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js index 6c93841fdd..d4d01cb6d3 100644 --- a/packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js +++ b/packages/next-common/components/people/overview/hooks/useDefaultRegistrar.js @@ -9,12 +9,12 @@ export default function useDefaultRegistrar(defaultRegistrarIndex) { const [dafaultValue, setDefaultValue] = useState(null); const selectableRegistrars = useMemo(() => { - if (isEmpty(registrars) || isEmpty(data)) { + if (isEmpty(registrars)) { return []; } return registrars?.filter( - (s) => !data.some((r) => r.account === s.account), + (s) => !data?.some((r) => r.account === s.account), ); }, [data, registrars]); From 9af17e02c5ccb461d53c4115f4175d880aa8ed49 Mon Sep 17 00:00:00 2001 From: leochen Date: Fri, 21 Nov 2025 18:44:34 +0800 Subject: [PATCH 06/41] Modify check button null guard, #6881 --- .../people/overview/identity/checkJudgement.jsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/next-common/components/people/overview/identity/checkJudgement.jsx b/packages/next-common/components/people/overview/identity/checkJudgement.jsx index 8f961effe9..867eeebce8 100644 --- a/packages/next-common/components/people/overview/identity/checkJudgement.jsx +++ b/packages/next-common/components/people/overview/identity/checkJudgement.jsx @@ -15,8 +15,8 @@ const RequestJudgementPopup = dynamicPopup( ); // TODO: default subsquare registrar index. -const SUBSQUARE_REGISTRAR_INDEX = "0"; -const VERIFIED_STATUSES = ["Reasonable", "KnownGood"]; +const SUBSQUARE_REGISTRAR_INDEX = "1"; +const NO_ACTION_REQUIRED_STATUSES = ["Reasonable", "KnownGood", "Erroneous"]; function isPendingRequestBySubsquare(judgements) { return judgements?.some( @@ -24,9 +24,9 @@ function isPendingRequestBySubsquare(judgements) { ); } -function isVerifiedStatus(judgements) { +function isNoActionRequired(judgements) { return judgements?.some((judgement) => - VERIFIED_STATUSES.includes(judgement.status), + NO_ACTION_REQUIRED_STATUSES.includes(judgement.status), ); } @@ -87,7 +87,8 @@ function CheckSubsquareJudgement() { export default function CheckJudgement() { const { judgements, isLoading } = useIdentityInfoContext(); - if (isLoading) { + + if (isLoading || isNoActionRequired(judgements)) { return null; } @@ -95,10 +96,6 @@ export default function CheckJudgement() { return ; } - if (isVerifiedStatus(judgements)) { - return null; - } - if (isPendingRequestBySubsquare(judgements)) { return ; } From c6434577ed66e5832c326cdfeaeef2aabbb51886 Mon Sep 17 00:00:00 2001 From: quinn-gao Date: Fri, 21 Nov 2025 16:33:20 +0800 Subject: [PATCH 07/41] fix: replace anchor tags with Link component for navigation, #6881 --- packages/next/pages/people/judgement/auth/discord.jsx | 5 +++-- packages/next/pages/people/judgement/auth/twitter.jsx | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/judgement/auth/discord.jsx index 978ffae929..e47f768bd0 100644 --- a/packages/next/pages/people/judgement/auth/discord.jsx +++ b/packages/next/pages/people/judgement/auth/discord.jsx @@ -8,6 +8,7 @@ import { withCommonProps } from "next-common/lib"; import getChainSettings from "next-common/utils/consts/settings"; import PrimaryButton from "next-common/lib/button/primary"; import KvList from "next-common/components/listInfo/kvList"; +import Link from "next/link"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; @@ -51,9 +52,9 @@ function PageImpl() { ]} />
diff --git a/packages/next/pages/people/judgement/auth/twitter.jsx b/packages/next/pages/people/judgement/auth/twitter.jsx index 71d2ead2d4..7b0fbfeaab 100644 --- a/packages/next/pages/people/judgement/auth/twitter.jsx +++ b/packages/next/pages/people/judgement/auth/twitter.jsx @@ -8,6 +8,7 @@ import { withCommonProps } from "next-common/lib"; import getChainSettings from "next-common/utils/consts/settings"; import PrimaryButton from "next-common/lib/button/primary"; import KvList from "next-common/components/listInfo/kvList"; +import Link from "next/link"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; @@ -49,9 +50,9 @@ function PageImpl() { ]} />
From 9fb997318d1a9399ea954c08e99c8b7afb07505e Mon Sep 17 00:00:00 2001 From: quinn-gao Date: Mon, 24 Nov 2025 14:07:05 +0800 Subject: [PATCH 08/41] feat: add verification logic and timer for Discord and Twitter account verification, #6881 --- .../components/people/judgement/twitter.jsx | 1 - .../pages/people/judgement/auth/discord.jsx | 72 ++++++++++++------- .../pages/people/judgement/auth/twitter.jsx | 68 ++++++++++++------ 3 files changed, 92 insertions(+), 49 deletions(-) diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index 58f829f1f5..a8a936f227 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -45,7 +45,6 @@ export default function Twitter() { window.open(link)}> Connect Twitter - {/* */}
); diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/judgement/auth/discord.jsx index e47f768bd0..40e03a7b4e 100644 --- a/packages/next/pages/people/judgement/auth/discord.jsx +++ b/packages/next/pages/people/judgement/auth/discord.jsx @@ -7,31 +7,41 @@ import { CHAIN } from "next-common/utils/constants"; import { withCommonProps } from "next-common/lib"; import getChainSettings from "next-common/utils/consts/settings"; import PrimaryButton from "next-common/lib/button/primary"; -import KvList from "next-common/components/listInfo/kvList"; import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; -export default function Page() { - return ( - - - - ); -} - -function PageImpl() { +export default function Page({ result }) { const address = useRealAddress(); + const [time, setTime] = useState(3); + const router = useRouter(); + + useEffect(() => { + if (result?.success) { + const timer = setInterval(() => { + setTime((prev) => { + if (prev === 1) { + router.replace("/people/judgement"); + clearInterval(timer); + } + return prev - 1; + }); + }, 1000); + return () => clearInterval(timer); + } + }, [result?.success, router]); return ( - <> +
@@ -41,16 +51,15 @@ function PageImpl() { } >
-

- Verify Your Discord Account Successfully -

- + {result?.success ? ( +

+ Verification successful! You will be redirected in {time} seconds… +

+ ) : ( +

+ Verify Your Discord Account Failed +

+ )}
Go to Judgement Detail @@ -59,7 +68,7 @@ function PageImpl() {
- + ); } @@ -69,10 +78,23 @@ export const getServerSideProps = async (ctx) => { notFound: true, }; } + const result = await getVerifyDetail(ctx.query.code); return withCommonProps(async () => { return { - props: {}, + props: { + result, + }, }; })(ctx); }; + +const getVerifyDetail = (code) => { + code; + // mocked data + if (Math.round(Math.random() * 10) % 2 == 0) { + return { success: true, message: "Verify success" }; + } else { + return { success: false, message: "Verify error" }; + } +}; diff --git a/packages/next/pages/people/judgement/auth/twitter.jsx b/packages/next/pages/people/judgement/auth/twitter.jsx index 7b0fbfeaab..79052a29a7 100644 --- a/packages/next/pages/people/judgement/auth/twitter.jsx +++ b/packages/next/pages/people/judgement/auth/twitter.jsx @@ -7,23 +7,33 @@ import { CHAIN } from "next-common/utils/constants"; import { withCommonProps } from "next-common/lib"; import getChainSettings from "next-common/utils/consts/settings"; import PrimaryButton from "next-common/lib/button/primary"; -import KvList from "next-common/components/listInfo/kvList"; import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; -export default function Page() { - return ( - - - - ); -} - -function PageImpl() { +export default function Page({ result }) { const address = useRealAddress(); + const [time, setTime] = useState(3); + const router = useRouter(); + + useEffect(() => { + if (result?.success) { + const timer = setInterval(() => { + setTime((prev) => { + if (prev === 1) { + router.replace("/people/judgement"); + clearInterval(timer); + } + return prev - 1; + }); + }, 1000); + return () => clearInterval(timer); + } + }, [result?.success, router]); return ( - <> +
-

- Verify Your X Account Successfully -

- + {result?.success ? ( +

+ Verification successful! You will be redirected in {time} seconds… +

+ ) : ( +

+ Verify Your X Account Failed +

+ )}
Go to Judgement Detail @@ -57,7 +66,7 @@ function PageImpl() {
- + ); } @@ -67,10 +76,23 @@ export const getServerSideProps = async (ctx) => { notFound: true, }; } + const result = await getVerifyDetail(ctx.query.code); return withCommonProps(async () => { return { - props: {}, + props: { + result, + }, }; })(ctx); }; + +const getVerifyDetail = (code) => { + code; + // mocked data + if (Math.round(Math.random() * 10) % 2 == 0) { + return { success: true, message: "Verify success" }; + } else { + return { success: false, message: "Verify error" }; + } +}; From 840db8a563bebe63aaa12b7cf01dab8f70c10615 Mon Sep 17 00:00:00 2001 From: leochen Date: Mon, 24 Nov 2025 16:20:19 +0800 Subject: [PATCH 09/41] remove useless code --- .../people/overview/identity/checkJudgement.jsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/next-common/components/people/overview/identity/checkJudgement.jsx b/packages/next-common/components/people/overview/identity/checkJudgement.jsx index 867eeebce8..ee972ec58d 100644 --- a/packages/next-common/components/people/overview/identity/checkJudgement.jsx +++ b/packages/next-common/components/people/overview/identity/checkJudgement.jsx @@ -60,12 +60,12 @@ function RequestJudgement() { } return ( -
+ <> Request Judgement {showPopup && } -
+ ); } @@ -77,17 +77,18 @@ function CheckSubsquareJudgement() { }, [router]); return ( -
- - Check Your Judgement - -
+ + Check Your Judgement + ); } export default function CheckJudgement() { const { judgements, isLoading } = useIdentityInfoContext(); - if (isLoading || isNoActionRequired(judgements)) { return null; } From 1082fda12324c8b3601cbc28aa1d69ef34cf5c25 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Tue, 6 Jan 2026 10:15:13 +0800 Subject: [PATCH 10/41] Implement discord verification, #6881 (#7072) --- .../people/hooks/useMyJudgementRequest.js | 22 ++ .../components/people/judgement/consts.js | 8 + .../components/people/judgement/discord.jsx | 101 +++++-- .../components/people/judgement/index.jsx | 61 +++-- packages/next-common/utils/url.js | 4 + .../pages/people/judgement/auth/discord.jsx | 247 ++++++++++++++---- 6 files changed, 354 insertions(+), 89 deletions(-) create mode 100644 packages/next-common/components/people/hooks/useMyJudgementRequest.js create mode 100644 packages/next-common/components/people/judgement/consts.js diff --git a/packages/next-common/components/people/hooks/useMyJudgementRequest.js b/packages/next-common/components/people/hooks/useMyJudgementRequest.js new file mode 100644 index 0000000000..ad51fa2ee5 --- /dev/null +++ b/packages/next-common/components/people/hooks/useMyJudgementRequest.js @@ -0,0 +1,22 @@ +import { backendApi } from "next-common/services/nextApi"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { useAsync } from "react-use"; + +export default function useMyJudgementRequest() { + const realAddress = useRealAddress(); + + return useAsync(async () => { + if (!realAddress) { + return null; + } + + const { result } = await backendApi.fetch( + `people/judgement/requests/pending?who=${realAddress}`, + ); + if (result) { + return result; + } + + return null; + }, [realAddress]); +} diff --git a/packages/next-common/components/people/judgement/consts.js b/packages/next-common/components/people/judgement/consts.js new file mode 100644 index 0000000000..0e4a0de243 --- /dev/null +++ b/packages/next-common/components/people/judgement/consts.js @@ -0,0 +1,8 @@ +export const PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE = "people:judgement-auth"; + +export const PeopleSocialType = Object.freeze({ + discord: "discord", + email: "email", + twitter: "twitter", + element: "element", +}); diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index e3663c03f9..a7038769ca 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -1,25 +1,84 @@ import { LinkDiscord } from "@osn/icons/subsquare"; import { ClosedTag } from "next-common/components/tags/state/styled"; import PrimaryButton from "next-common/lib/button/primary"; -import { useMemo } from "react"; +import { backendApi } from "next-common/services/nextApi"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { trimEndSlash } from "next-common/utils/url"; +import { useCallback, useEffect, useState } from "react"; +import { PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE, PeopleSocialType } from "./consts"; -function generateDiscordAuthLink(state = {}) { - const client_id = process.env.NEXT_PUBLIC_DISCORD_CLIENT_ID; - const redirect_uri = location.origin + "/people/judgement/auth/discord"; +function isDiscordOpenerMessage(data) { + return ( + data?.type === PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE && + data?.provider === PeopleSocialType.discord + ); +} - const params = new URLSearchParams({ - client_id, - redirect_uri, - response_type: "code", - scope: "identify", - state: encodeURIComponent(JSON.stringify(state)), - }); +function useGetDiscordAuthLink() { + const [loading, setLoading] = useState(false); + const realAddress = useRealAddress(); + const getDiscordAuthLink = useCallback(async () => { + if (!realAddress) { + return ""; + } + setLoading(true); + try { + const { result } = await backendApi.fetch( + `people/judgement/auth/discord/auth-url?who=${realAddress}&redirectUri=${trimEndSlash( + process.env.NEXT_PUBLIC_SITE_URL, + )}/people/judgement/auth/discord`, + ); + return result.url; + } finally { + setLoading(false); + } + }, [realAddress]); - return `https://discord.com/oauth2/authorize?${params.toString()}`; + return { + loading, + getDiscordAuthLink, + }; } -export default function Discord() { - const link = useMemo(() => generateDiscordAuthLink(), []); +export default function Discord({ request }) { + const { loading, getDiscordAuthLink } = useGetDiscordAuthLink(); + const realAddress = useRealAddress(); + + const isVerified = request?.verification?.discord === true; + + const [connected, setConnected] = useState(isVerified); + + useEffect(() => { + setConnected(isVerified); + }, [isVerified]); + + useEffect(() => { + if (typeof window === "undefined") { + return; + } + + const handleMessage = (event) => { + if (event.origin !== window.location.origin) { + return; + } + + const data = event.data; + if (!isDiscordOpenerMessage(data)) { + return; + } + + if (data?.ok && data?.who && data.who === realAddress) { + setConnected(true); + } + }; + + window.addEventListener("message", handleMessage); + + return () => { + window.removeEventListener("message", handleMessage); + }; + }, [realAddress]); + return (
@@ -30,7 +89,7 @@ export default function Discord() {

Discord

- Pending + {connected ? "Connected" : "Pending"}
@@ -38,11 +97,19 @@ export default function Discord() {
Username: - pending@example.com + {request?.info?.discord}
- window.open(link)} size="small"> + { + const link = await getDiscordAuthLink(); + window.open(link); + }} + size="small" + > Connect Discord
diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index dfd4f95012..f49e348b79 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -11,8 +11,9 @@ import Email from "./email"; import Discord from "./discord"; import Twitter from "./twitter"; import Element from "./element"; -import { useState, useEffect } from "react"; import Loading from "next-common/components/loading"; +import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; + export const tabs = [ { value: "Judgement", @@ -24,12 +25,10 @@ export const tabs = [ export default function JudgementPage() { const address = useRealAddress(); - const [loading, setLoading] = useState(true); - useEffect(() => { - setTimeout(() => { - setLoading(false); - }, 1500); - }, []); + const { value, loading: isLoadingMyJudgementRequest } = + useMyJudgementRequest(); + + const myJudgementRequest = value?.items[0] || null; return ( @@ -58,26 +57,36 @@ export default function JudgementPage() {
- {loading ? ( -
- -
- ) : ( -
-
- -
-
- +
+ {isLoadingMyJudgementRequest ? ( +
+
-
- -
-
- -
-
- )} + ) : ( + <> + {myJudgementRequest?.info?.email && ( +
+ +
+ )} + {myJudgementRequest?.info?.element && ( +
+ +
+ )} + {myJudgementRequest?.info?.discord && ( +
+ +
+ )} + {myJudgementRequest?.info?.twitter && ( +
+ +
+ )} + + )} +
); diff --git a/packages/next-common/utils/url.js b/packages/next-common/utils/url.js index c22b1807a4..65e45a0981 100644 --- a/packages/next-common/utils/url.js +++ b/packages/next-common/utils/url.js @@ -23,3 +23,7 @@ export function objectToQueryString(obj) { }); return searchParams.toString(); } + +export function trimEndSlash(url) { + return url.replace(/\/+$/, ""); +} diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/judgement/auth/discord.jsx index 40e03a7b4e..3d13e0afd7 100644 --- a/packages/next/pages/people/judgement/auth/discord.jsx +++ b/packages/next/pages/people/judgement/auth/discord.jsx @@ -8,30 +8,140 @@ import { withCommonProps } from "next-common/lib"; import getChainSettings from "next-common/utils/consts/settings"; import PrimaryButton from "next-common/lib/button/primary"; import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useRouter } from "next/router"; +import { useEffect, useRef, useState } from "react"; +import { backendApi } from "next-common/services/nextApi"; +import { + PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE, + PeopleSocialType, +} from "next-common/components/people/judgement/consts"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; -export default function Page({ result }) { - const address = useRealAddress(); - const [time, setTime] = useState(3); - const router = useRouter(); +function notifyOpener(payload) { + if (typeof window === "undefined") { + return; + } + + try { + window.opener?.postMessage(payload, window.location.origin); + } catch (e) { + // ignore + } +} + +function useCloseCountdown(seconds, enabled) { + const [time, setTime] = useState(seconds); useEffect(() => { - if (result?.success) { - const timer = setInterval(() => { - setTime((prev) => { - if (prev === 1) { - router.replace("/people/judgement"); - clearInterval(timer); + if (!enabled) { + return; + } + + const timer = setInterval(() => { + setTime((prev) => { + if (prev <= 1) { + clearInterval(timer); + try { + window.close(); + } catch (e) { + // ignore } - return prev - 1; - }); - }, 1000); - return () => clearInterval(timer); + return 0; + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(timer); + }, [enabled]); + + return time; +} + +function useNotifyOpenerOnResult(result) { + useEffect(() => { + if (!result) return; + + notifyOpener({ + type: PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE, + provider: PeopleSocialType.discord, + ok: !!result.success, + who: result.who, + message: result.message, + ts: Date.now(), + }); + }, [result]); +} + +function useDiscordAuthVerifyResult({ code, state }) { + const [result, setResult] = useState(null); + const hasRequestedRef = useRef(false); + + useEffect(() => { + if (hasRequestedRef.current) { + return; + } + + hasRequestedRef.current = true; + + if (!code || !state) { + setResult({ success: false, message: "Missing code or state" }); + return; } - }, [result?.success, router]); + + let cancelled = false; + (async () => { + const { result, error } = await backendApi.fetch( + "people/judgement/auth/discord/callback", + { code, state }, + ); + + if (cancelled) { + return; + } + + if (error) { + setResult({ + success: false, + message: error.message || "Verification failed", + }); + return; + } + + setResult({ + success: true, + who: result?.who, + message: "Verify success", + }); + })(); + + return () => { + cancelled = true; + }; + }, [code, state]); + + return result; +} + +function DiscordAuthSuccess({ time }) { + return ( +

+ Verification successful! This window will close in {time} seconds… +

+ ); +} + +function DiscordAuthFailed({ message, time }) { + return ( +

+ {message ? message : "Verify Your Discord Account Failed"} + {time > 0 ? ` (closing in ${time}s…)` : ""} +

+ ); +} + +function DiscordAuthLayout({ children }) { + const address = useRealAddress(); return ( } > -
- {result?.success ? ( -

- Verification successful! You will be redirected in {time} seconds… -

- ) : ( -

- Verify Your Discord Account Failed -

- )} -
- - Go to Judgement Detail - -
-
-
+ {children}
); } +function DiscordAuthResultCard({ result, time }) { + return ( +
+ {result === null ? ( +

+ Verifying your Discord account… +

+ ) : result?.success ? ( + + ) : ( + + )} +
+ + Go to Judgement Detail + +
+
+ ); +} + +function DiscordAuthErrorFlow({ error, errorDescription }) { + const message = + error === "access_denied" + ? "Authorization cancelled" + : `OAuth error: ${error}`; + const result = { + success: false, + who: "", + message: errorDescription ? `${message}: ${errorDescription}` : message, + }; + + useNotifyOpenerOnResult(result); + const time = useCloseCountdown(5, true); + + return ( + + + + ); +} + +function DiscordAuthVerifyFlow({ code, state }) { + const result = useDiscordAuthVerifyResult({ code, state }); + useNotifyOpenerOnResult(result); + const time = useCloseCountdown(5, result !== null); + + return ( + + + + ); +} + +export default function Page({ code, state, error, errorDescription }) { + if (error) { + return ( + + ); + } + + return ; +} + export const getServerSideProps = async (ctx) => { if (!isPeopleSupported) { return { notFound: true, }; } - const result = await getVerifyDetail(ctx.query.code); + + const code = ctx.query.code || ""; + const state = ctx.query.state || ""; + const error = ctx.query.error || ""; + const errorDescription = ctx.query.error_description || ""; return withCommonProps(async () => { return { props: { - result, + code, + state, + error, + errorDescription, }, }; })(ctx); }; - -const getVerifyDetail = (code) => { - code; - // mocked data - if (Math.round(Math.random() * 10) % 2 == 0) { - return { success: true, message: "Verify success" }; - } else { - return { success: false, message: "Verify error" }; - } -}; From cbd06ad1c82e7a48dd15a2216769b1fa3121d035 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Tue, 6 Jan 2026 13:49:14 +0800 Subject: [PATCH 11/41] Implement twitter authentication, #6881 (#7074) --- .../pages/people/judgement/authCallback.jsx | 261 ++++++++++++++++++ .../components/people/judgement/discord.jsx | 127 ++------- .../hooks/usePeopleJudgementSocialAuth.js | 89 ++++++ .../components/people/judgement/index.jsx | 2 +- .../people/judgement/socialConnect.jsx | 51 ++++ .../components/people/judgement/twitter.jsx | 61 ++-- .../pages/people/judgement/auth/discord.jsx | 231 +--------------- .../pages/people/judgement/auth/twitter.jsx | 92 ++---- 8 files changed, 470 insertions(+), 444 deletions(-) create mode 100644 packages/next-common/components/pages/people/judgement/authCallback.jsx create mode 100644 packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js create mode 100644 packages/next-common/components/people/judgement/socialConnect.jsx diff --git a/packages/next-common/components/pages/people/judgement/authCallback.jsx b/packages/next-common/components/pages/people/judgement/authCallback.jsx new file mode 100644 index 0000000000..e557d22e60 --- /dev/null +++ b/packages/next-common/components/pages/people/judgement/authCallback.jsx @@ -0,0 +1,261 @@ +import ListLayout from "next-common/components/layout/ListLayout"; +import Account from "next-common/components/account"; +import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; +import PrimaryButton from "next-common/lib/button/primary"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { backendApi } from "next-common/services/nextApi"; +import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; +import { PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE } from "next-common/components/people/judgement/consts"; + +function notifyOpener(payload) { + if (typeof window === "undefined") { + return; + } + + try { + window.opener?.postMessage(payload, window.location.origin); + } catch (e) { + // ignore + } +} + +function useCloseCountdown(seconds, enabled) { + const [time, setTime] = useState(seconds); + + useEffect(() => { + if (!enabled) { + return; + } + + const timer = setInterval(() => { + setTime((prev) => { + if (prev <= 1) { + clearInterval(timer); + try { + window.close(); + } catch (e) { + // ignore + } + return 0; + } + return prev - 1; + }); + }, 1000); + + return () => clearInterval(timer); + }, [enabled]); + + return time; +} + +function useNotifyOpenerOnResult({ provider, result }) { + useEffect(() => { + if (!result) return; + + notifyOpener({ + type: PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE, + provider, + ok: !!result.success, + who: result.who, + message: result.message, + ts: Date.now(), + }); + }, [provider, result]); +} + +function useAuthVerifyResult({ code, state, backendCallbackPath }) { + const [result, setResult] = useState(null); + const hasRequestedRef = useRef(false); + + useEffect(() => { + if (hasRequestedRef.current) { + return; + } + + hasRequestedRef.current = true; + + if (!code || !state) { + setResult({ success: false, message: "Missing code or state" }); + return; + } + + let cancelled = false; + (async () => { + const { result, error } = await backendApi.fetch(backendCallbackPath, { + code, + state, + }); + + if (cancelled) { + return; + } + + if (error) { + setResult({ + success: false, + message: error.message || "Verification failed", + }); + return; + } + + setResult({ + success: true, + who: result?.who, + message: "Verify success", + }); + })(); + + return () => { + cancelled = true; + }; + }, [backendCallbackPath, code, state]); + + return result; +} + +function AuthSuccess({ time }) { + return ( +

+ Verification successful! This window will close in {time} seconds… +

+ ); +} + +function AuthFailed({ message, time, providerLabel }) { + return ( +

+ {message ? message : `Verify Your ${providerLabel} Account Failed`} + {time > 0 ? ` (closing in ${time}s…)` : ""} +

+ ); +} + +function AuthLayout({ providerLabel, children }) { + const address = useRealAddress(); + + return ( + +
+ +
+ + } + > + {children} +
+ ); +} + +function AuthResultCard({ providerLabel, result, time }) { + return ( +
+ {result === null ? ( +

+ Verifying your {providerLabel} account… +

+ ) : result?.success ? ( + + ) : ( + + )} +
+ + Go to Judgement Detail + +
+
+ ); +} + +function AuthErrorFlow({ provider, providerLabel, error, errorDescription }) { + const message = + error === "access_denied" + ? "Authorization cancelled" + : `OAuth error: ${error}`; + + const result = { + success: false, + who: "", + message: errorDescription ? `${message}: ${errorDescription}` : message, + }; + + useNotifyOpenerOnResult({ provider, result }); + const time = useCloseCountdown(5, true); + + return ( + + + + ); +} + +function AuthVerifyFlow({ + provider, + providerLabel, + backendCallbackPath, + code, + state, +}) { + const result = useAuthVerifyResult({ code, state, backendCallbackPath }); + useNotifyOpenerOnResult({ provider, result }); + const time = useCloseCountdown(5, result !== null); + + return ( + + + + ); +} + +export default function PeopleJudgementAuthCallbackPage({ + provider, + providerLabel, + backendCallbackPath, + code, + state, + error, + errorDescription, +}) { + if (error) { + return ( + + ); + } + + return ( + + ); +} diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index a7038769ca..36681a321e 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -1,118 +1,25 @@ import { LinkDiscord } from "@osn/icons/subsquare"; -import { ClosedTag } from "next-common/components/tags/state/styled"; -import PrimaryButton from "next-common/lib/button/primary"; -import { backendApi } from "next-common/services/nextApi"; -import useRealAddress from "next-common/utils/hooks/useRealAddress"; -import { trimEndSlash } from "next-common/utils/url"; -import { useCallback, useEffect, useState } from "react"; -import { PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE, PeopleSocialType } from "./consts"; - -function isDiscordOpenerMessage(data) { - return ( - data?.type === PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE && - data?.provider === PeopleSocialType.discord - ); -} - -function useGetDiscordAuthLink() { - const [loading, setLoading] = useState(false); - const realAddress = useRealAddress(); - const getDiscordAuthLink = useCallback(async () => { - if (!realAddress) { - return ""; - } - setLoading(true); - try { - const { result } = await backendApi.fetch( - `people/judgement/auth/discord/auth-url?who=${realAddress}&redirectUri=${trimEndSlash( - process.env.NEXT_PUBLIC_SITE_URL, - )}/people/judgement/auth/discord`, - ); - return result.url; - } finally { - setLoading(false); - } - }, [realAddress]); - - return { - loading, - getDiscordAuthLink, - }; -} +import usePeopleJudgementSocialAuth from "./hooks/usePeopleJudgementSocialAuth"; +import PeopleJudgementSocialConnect from "./socialConnect"; +import { PeopleSocialType } from "./consts"; export default function Discord({ request }) { - const { loading, getDiscordAuthLink } = useGetDiscordAuthLink(); - const realAddress = useRealAddress(); - const isVerified = request?.verification?.discord === true; - - const [connected, setConnected] = useState(isVerified); - - useEffect(() => { - setConnected(isVerified); - }, [isVerified]); - - useEffect(() => { - if (typeof window === "undefined") { - return; - } - - const handleMessage = (event) => { - if (event.origin !== window.location.origin) { - return; - } - - const data = event.data; - if (!isDiscordOpenerMessage(data)) { - return; - } - - if (data?.ok && data?.who && data.who === realAddress) { - setConnected(true); - } - }; - - window.addEventListener("message", handleMessage); - - return () => { - window.removeEventListener("message", handleMessage); - }; - }, [realAddress]); + const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ + provider: PeopleSocialType.discord, + authUrlPath: "people/judgement/auth/discord/auth-url", + redirectPath: "/people/judgement/auth/discord", + isVerified, + }); return ( -
-
-
-
- - · -

Discord

-
-
- {connected ? "Connected" : "Pending"} -
-
-
-
-
- Username: - - {request?.info?.discord} - -
- - { - const link = await getDiscordAuthLink(); - window.open(link); - }} - size="small" - > - Connect Discord - -
-
+ } + title="Discord" + username={request?.info?.discord} + connected={connected} + loading={loading} + onConnect={openAuthWindow} + /> ); } diff --git a/packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js b/packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js new file mode 100644 index 0000000000..575f1eef11 --- /dev/null +++ b/packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js @@ -0,0 +1,89 @@ +import { backendApi } from "next-common/services/nextApi"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { trimEndSlash } from "next-common/utils/url"; +import { useCallback, useEffect, useState } from "react"; +import { PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE } from "next-common/components/people/judgement/consts"; + +function isJudgementAuthOpenerMessage(data, provider) { + return ( + data?.type === PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE && + data?.provider === provider + ); +} + +export default function usePeopleJudgementSocialAuth({ + provider, + authUrlPath, + redirectPath, + isVerified, +}) { + const realAddress = useRealAddress(); + + const [loading, setLoading] = useState(false); + const [connected, setConnected] = useState(isVerified); + + useEffect(() => { + setConnected(isVerified); + }, [isVerified]); + + const getAuthLink = useCallback(async () => { + if (!realAddress) { + return ""; + } + + setLoading(true); + try { + const redirect = `${trimEndSlash( + process.env.NEXT_PUBLIC_SITE_URL, + )}${redirectPath}`; + const { result } = await backendApi.fetch( + `${authUrlPath}?who=${realAddress}&redirectUri=${redirect}`, + ); + return result?.url || ""; + } finally { + setLoading(false); + } + }, [authUrlPath, realAddress, redirectPath]); + + const openAuthWindow = useCallback(async () => { + const link = await getAuthLink(); + if (!link) { + return; + } + + window.open(link); + }, [getAuthLink]); + + useEffect(() => { + if (typeof window === "undefined") { + return; + } + + const handleMessage = (event) => { + if (event.origin !== window.location.origin) { + return; + } + + const data = event.data; + if (!isJudgementAuthOpenerMessage(data, provider)) { + return; + } + + if (data?.ok && data?.who && data.who === realAddress) { + setConnected(true); + } + }; + + window.addEventListener("message", handleMessage); + + return () => { + window.removeEventListener("message", handleMessage); + }; + }, [provider, realAddress]); + + return { + loading, + connected, + openAuthWindow, + }; +} diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index f49e348b79..7209dc7fdf 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -81,7 +81,7 @@ export default function JudgementPage() { )} {myJudgementRequest?.info?.twitter && (
- +
)} diff --git a/packages/next-common/components/people/judgement/socialConnect.jsx b/packages/next-common/components/people/judgement/socialConnect.jsx new file mode 100644 index 0000000000..89fae9bb1c --- /dev/null +++ b/packages/next-common/components/people/judgement/socialConnect.jsx @@ -0,0 +1,51 @@ +import { + ClosedTag, + PositiveTag, +} from "next-common/components/tags/state/styled"; +import PrimaryButton from "next-common/lib/button/primary"; + +export default function PeopleJudgementSocialConnect({ + icon, + title, + username, + connected, + loading, + onConnect, +}) { + return ( +
+
+
+
+ {icon} + · +

{title}

+
+
+ {connected ? ( + Connected + ) : ( + Pending + )} +
+
+
+ +
+
+ Username: + {username} +
+ + + Connect {title} + +
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index a8a936f227..bfe7f2360d 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -1,51 +1,26 @@ import { LinkTwitter } from "@osn/icons/subsquare"; -import { ClosedTag } from "next-common/components/tags/state/styled"; -import PrimaryButton from "next-common/lib/button/primary"; -import { useMemo } from "react"; +import usePeopleJudgementSocialAuth from "./hooks/usePeopleJudgementSocialAuth"; +import PeopleJudgementSocialConnect from "./socialConnect"; +import { PeopleSocialType } from "./consts"; -const scope = "tweet.read users.read offline.access"; -const client_id = process.env.NEXT_PUBLIC_X_CLIENT_ID; -const redirect_uri = location.origin + "/people/judgement/auth/twitter"; +export default function Twitter({ request }) { + const isVerified = request?.verification?.twitter === true; -function generateAuthLink(state = {}) { - const params = new URLSearchParams({ - state: encodeURIComponent(JSON.stringify(state)), - client_id, - redirect_uri, - scope, - response_type: "code", - code_challenge: "challenge", - code_challenge_method: "plain", + const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ + provider: PeopleSocialType.twitter, + authUrlPath: "people/judgement/auth/twitter/auth-url", + redirectPath: "/people/judgement/auth/twitter", + isVerified, }); - return `https://x.com/i/oauth2/authorize?${params}`; -} - -export default function Twitter() { - const link = useMemo(() => generateAuthLink(), []); return ( -
-
-
-
- - · -

Twitter

-
-
- Pending -
-
-
-
-
- Username: - @QuinnGao -
- window.open(link)}> - Connect Twitter - -
-
+ } + title="Twitter" + username={request?.info?.twitter} + connected={connected} + loading={loading} + onConnect={openAuthWindow} + /> ); } diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/judgement/auth/discord.jsx index 3d13e0afd7..c22123441a 100644 --- a/packages/next/pages/people/judgement/auth/discord.jsx +++ b/packages/next/pages/people/judgement/auth/discord.jsx @@ -1,235 +1,28 @@ -import ListLayout from "next-common/components/layout/ListLayout"; -import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; -import Account from "next-common/components/account"; -import useRealAddress from "next-common/utils/hooks/useRealAddress"; import { PeopleGlobalProvider } from "../../index"; import { CHAIN } from "next-common/utils/constants"; import { withCommonProps } from "next-common/lib"; import getChainSettings from "next-common/utils/consts/settings"; -import PrimaryButton from "next-common/lib/button/primary"; -import Link from "next/link"; -import { useEffect, useRef, useState } from "react"; -import { backendApi } from "next-common/services/nextApi"; -import { - PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE, - PeopleSocialType, -} from "next-common/components/people/judgement/consts"; +import PeopleJudgementAuthCallbackPage from "next-common/components/pages/people/judgement/authCallback"; +import { PeopleSocialType } from "next-common/components/people/judgement/consts"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; -function notifyOpener(payload) { - if (typeof window === "undefined") { - return; - } - - try { - window.opener?.postMessage(payload, window.location.origin); - } catch (e) { - // ignore - } -} - -function useCloseCountdown(seconds, enabled) { - const [time, setTime] = useState(seconds); - - useEffect(() => { - if (!enabled) { - return; - } - - const timer = setInterval(() => { - setTime((prev) => { - if (prev <= 1) { - clearInterval(timer); - try { - window.close(); - } catch (e) { - // ignore - } - return 0; - } - return prev - 1; - }); - }, 1000); - - return () => clearInterval(timer); - }, [enabled]); - - return time; -} - -function useNotifyOpenerOnResult(result) { - useEffect(() => { - if (!result) return; - - notifyOpener({ - type: PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE, - provider: PeopleSocialType.discord, - ok: !!result.success, - who: result.who, - message: result.message, - ts: Date.now(), - }); - }, [result]); -} - -function useDiscordAuthVerifyResult({ code, state }) { - const [result, setResult] = useState(null); - const hasRequestedRef = useRef(false); - - useEffect(() => { - if (hasRequestedRef.current) { - return; - } - - hasRequestedRef.current = true; - - if (!code || !state) { - setResult({ success: false, message: "Missing code or state" }); - return; - } - - let cancelled = false; - (async () => { - const { result, error } = await backendApi.fetch( - "people/judgement/auth/discord/callback", - { code, state }, - ); - - if (cancelled) { - return; - } - - if (error) { - setResult({ - success: false, - message: error.message || "Verification failed", - }); - return; - } - - setResult({ - success: true, - who: result?.who, - message: "Verify success", - }); - })(); - - return () => { - cancelled = true; - }; - }, [code, state]); - - return result; -} - -function DiscordAuthSuccess({ time }) { - return ( -

- Verification successful! This window will close in {time} seconds… -

- ); -} - -function DiscordAuthFailed({ message, time }) { - return ( -

- {message ? message : "Verify Your Discord Account Failed"} - {time > 0 ? ` (closing in ${time}s…)` : ""} -

- ); -} - -function DiscordAuthLayout({ children }) { - const address = useRealAddress(); +export default function Page({ code, state, error, errorDescription }) { return ( - -
- -
- - } - > - {children} -
+
); } -function DiscordAuthResultCard({ result, time }) { - return ( -
- {result === null ? ( -

- Verifying your Discord account… -

- ) : result?.success ? ( - - ) : ( - - )} -
- - Go to Judgement Detail - -
-
- ); -} - -function DiscordAuthErrorFlow({ error, errorDescription }) { - const message = - error === "access_denied" - ? "Authorization cancelled" - : `OAuth error: ${error}`; - const result = { - success: false, - who: "", - message: errorDescription ? `${message}: ${errorDescription}` : message, - }; - - useNotifyOpenerOnResult(result); - const time = useCloseCountdown(5, true); - - return ( - - - - ); -} - -function DiscordAuthVerifyFlow({ code, state }) { - const result = useDiscordAuthVerifyResult({ code, state }); - useNotifyOpenerOnResult(result); - const time = useCloseCountdown(5, result !== null); - - return ( - - - - ); -} - -export default function Page({ code, state, error, errorDescription }) { - if (error) { - return ( - - ); - } - - return ; -} - export const getServerSideProps = async (ctx) => { if (!isPeopleSupported) { return { diff --git a/packages/next/pages/people/judgement/auth/twitter.jsx b/packages/next/pages/people/judgement/auth/twitter.jsx index 79052a29a7..38bf1e029d 100644 --- a/packages/next/pages/people/judgement/auth/twitter.jsx +++ b/packages/next/pages/people/judgement/auth/twitter.jsx @@ -1,71 +1,24 @@ -import ListLayout from "next-common/components/layout/ListLayout"; -import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; -import Account from "next-common/components/account"; -import useRealAddress from "next-common/utils/hooks/useRealAddress"; import { PeopleGlobalProvider } from "../../index"; import { CHAIN } from "next-common/utils/constants"; import { withCommonProps } from "next-common/lib"; import getChainSettings from "next-common/utils/consts/settings"; -import PrimaryButton from "next-common/lib/button/primary"; -import Link from "next/link"; -import { useEffect, useState } from "react"; -import { useRouter } from "next/router"; +import PeopleJudgementAuthCallbackPage from "next-common/components/pages/people/judgement/authCallback"; +import { PeopleSocialType } from "next-common/components/people/judgement/consts"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; -export default function Page({ result }) { - const address = useRealAddress(); - const [time, setTime] = useState(3); - const router = useRouter(); - - useEffect(() => { - if (result?.success) { - const timer = setInterval(() => { - setTime((prev) => { - if (prev === 1) { - router.replace("/people/judgement"); - clearInterval(timer); - } - return prev - 1; - }); - }, 1000); - return () => clearInterval(timer); - } - }, [result?.success, router]); +export default function Page({ code, state, error, errorDescription }) { return ( - -
- -
- - } - > -
- {result?.success ? ( -

- Verification successful! You will be redirected in {time} seconds… -

- ) : ( -

- Verify Your X Account Failed -

- )} -
- - Go to Judgement Detail - -
-
-
-
+
); } @@ -76,23 +29,20 @@ export const getServerSideProps = async (ctx) => { notFound: true, }; } - const result = await getVerifyDetail(ctx.query.code); + + const code = ctx.query.code || ""; + const state = ctx.query.state || ""; + const error = ctx.query.error || ""; + const errorDescription = ctx.query.error_description || ""; return withCommonProps(async () => { return { props: { - result, + code, + state, + error, + errorDescription, }, }; })(ctx); }; - -const getVerifyDetail = (code) => { - code; - // mocked data - if (Math.round(Math.random() * 10) % 2 == 0) { - return { success: true, message: "Verify success" }; - } else { - return { success: false, message: "Verify error" }; - } -}; From 6cf8598ffd6c1bded5ec181f279071ea545acbf3 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Tue, 6 Jan 2026 14:56:09 +0800 Subject: [PATCH 12/41] Implement github authentication, #6881 (#7075) --- .../components/people/judgement/consts.js | 1 + .../components/people/judgement/github.jsx | 26 ++++++++++ .../components/people/judgement/index.jsx | 6 +++ .../pages/people/judgement/auth/github.jsx | 48 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 packages/next-common/components/people/judgement/github.jsx create mode 100644 packages/next/pages/people/judgement/auth/github.jsx diff --git a/packages/next-common/components/people/judgement/consts.js b/packages/next-common/components/people/judgement/consts.js index 0e4a0de243..6da2e77a90 100644 --- a/packages/next-common/components/people/judgement/consts.js +++ b/packages/next-common/components/people/judgement/consts.js @@ -4,5 +4,6 @@ export const PeopleSocialType = Object.freeze({ discord: "discord", email: "email", twitter: "twitter", + github: "github", element: "element", }); diff --git a/packages/next-common/components/people/judgement/github.jsx b/packages/next-common/components/people/judgement/github.jsx new file mode 100644 index 0000000000..f4e7765e1c --- /dev/null +++ b/packages/next-common/components/people/judgement/github.jsx @@ -0,0 +1,26 @@ +import { LinkGithub } from "@osn/icons/subsquare"; +import usePeopleJudgementSocialAuth from "./hooks/usePeopleJudgementSocialAuth"; +import PeopleJudgementSocialConnect from "./socialConnect"; +import { PeopleSocialType } from "./consts"; + +export default function Github({ request }) { + const isVerified = request?.verification?.github === true; + + const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ + provider: PeopleSocialType.github, + authUrlPath: "people/judgement/auth/github/auth-url", + redirectPath: "/people/judgement/auth/github", + isVerified, + }); + + return ( + } + title="GitHub" + username={request?.info?.github} + connected={connected} + loading={loading} + onConnect={openAuthWindow} + /> + ); +} diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index 7209dc7fdf..9511e7002f 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -10,6 +10,7 @@ import SummaryItem from "next-common/components/summary/layout/item"; import Email from "./email"; import Discord from "./discord"; import Twitter from "./twitter"; +import Github from "./github"; import Element from "./element"; import Loading from "next-common/components/loading"; import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; @@ -84,6 +85,11 @@ export default function JudgementPage() {
)} + {myJudgementRequest?.info?.github && ( +
+ +
+ )} )}
diff --git a/packages/next/pages/people/judgement/auth/github.jsx b/packages/next/pages/people/judgement/auth/github.jsx new file mode 100644 index 0000000000..f2cb451418 --- /dev/null +++ b/packages/next/pages/people/judgement/auth/github.jsx @@ -0,0 +1,48 @@ +import { PeopleGlobalProvider } from "../../index"; +import { CHAIN } from "next-common/utils/constants"; +import { withCommonProps } from "next-common/lib"; +import getChainSettings from "next-common/utils/consts/settings"; +import PeopleJudgementAuthCallbackPage from "next-common/components/pages/people/judgement/authCallback"; +import { PeopleSocialType } from "next-common/components/people/judgement/consts"; + +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; + +export default function Page({ code, state, error, errorDescription }) { + return ( + + + + ); +} + +export const getServerSideProps = async (ctx) => { + if (!isPeopleSupported) { + return { + notFound: true, + }; + } + + const code = ctx.query.code || ""; + const state = ctx.query.state || ""; + const error = ctx.query.error || ""; + const errorDescription = ctx.query.error_description || ""; + + return withCommonProps(async () => { + return { + props: { + code, + state, + error, + errorDescription, + }, + }; + })(ctx); +}; From 4e55e0842ce5237d0841f1ca524df1207f153e9d Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Wed, 7 Jan 2026 12:16:05 +0800 Subject: [PATCH 13/41] Identity judgement email, #6881 (#7077) * Implement email verification, #6881 * refactor, #6881 * refactor, #6881 * fix useSubScanHeightStream, #6881 --- .../components/people/judgement/email.jsx | 106 ------------------ .../judgement/email/emailAddressRow.jsx | 10 ++ .../judgement/email/emailCardHeader.jsx | 17 +++ .../judgement/email/emailVerificationTips.jsx | 10 ++ .../email/hooks/useSendJudgementEmailCode.js | 50 +++++++++ .../hooks/useVerifyJudgementEmailCode.js | 55 +++++++++ .../people/judgement/email/index.jsx | 25 +++++ .../judgement/email/pendingEmailCard.jsx | 40 +++++++ .../email/pendingEmailNotSentCard.jsx | 34 ++++++ .../judgement/email/pendingEmailSentCard.jsx | 88 +++++++++++++++ .../judgement/email/verifiedEmailCard.jsx | 13 +++ .../components/people/judgement/index.jsx | 22 ++-- .../scanHeight/useSubScanHeightStream.js | 75 ++++++++++--- packages/next-common/hooks/useCountdown.js | 27 +++++ 14 files changed, 438 insertions(+), 134 deletions(-) delete mode 100644 packages/next-common/components/people/judgement/email.jsx create mode 100644 packages/next-common/components/people/judgement/email/emailAddressRow.jsx create mode 100644 packages/next-common/components/people/judgement/email/emailCardHeader.jsx create mode 100644 packages/next-common/components/people/judgement/email/emailVerificationTips.jsx create mode 100644 packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js create mode 100644 packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js create mode 100644 packages/next-common/components/people/judgement/email/index.jsx create mode 100644 packages/next-common/components/people/judgement/email/pendingEmailCard.jsx create mode 100644 packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx create mode 100644 packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx create mode 100644 packages/next-common/components/people/judgement/email/verifiedEmailCard.jsx create mode 100644 packages/next-common/hooks/useCountdown.js diff --git a/packages/next-common/components/people/judgement/email.jsx b/packages/next-common/components/people/judgement/email.jsx deleted file mode 100644 index 44b485f1a3..0000000000 --- a/packages/next-common/components/people/judgement/email.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import { LinkEmail } from "@osn/icons/subsquare"; -import { ClosedTag } from "next-common/components/tags/state/styled"; -import PrimaryButton from "next-common/lib/button/primary"; -import SecondaryButton from "next-common/lib/button/secondary"; -import Input from "next-common/lib/input"; -import { useState, useEffect } from "react"; -import { newSuccessToast } from "next-common/store/reducers/toastSlice"; -import { useDispatch } from "react-redux"; - -export default function Email() { - const [code, setCode] = useState(""); - return ( -
-
-
-
- - · -

Email

-
-
- Pending -
-
- -
-
-
- Email Address: - - pending@example.com - -
-
-
-

- Tips: We‘ve send a 6-digit - code to your email. Code expires in 10 minutes. -

-
-
- -
- setCode(e.target.value)} - /> - -
-
-
- ); -} - -function VerifyButton({ code }) { - const dispatch = useDispatch(); - const [loading, setLoading] = useState(false); - const verifyCode = async () => { - setLoading(true); - setTimeout(() => { - setLoading(false); - dispatch(newSuccessToast("Verify code successfully")); - }, 1500); - }; - return ( - - Verify - - ); -} - -function SendCode() { - const dispatch = useDispatch(); - const [countdown, setCountdown] = useState(0); - const [loading, setLoading] = useState(false); - - useEffect(() => { - let timer; - if (countdown > 0) { - timer = setTimeout(() => setCountdown(countdown - 1), 1000); - } - return () => clearTimeout(timer); - }, [countdown]); - - const sendCode = async () => { - setLoading(true); - setTimeout(() => { - setLoading(false); - dispatch(newSuccessToast("Send email successfully")); - setCountdown(60); - }, 1500); - }; - return ( - - Send Code {countdown ? ` ${countdown} s` : ""} - - ); -} diff --git a/packages/next-common/components/people/judgement/email/emailAddressRow.jsx b/packages/next-common/components/people/judgement/email/emailAddressRow.jsx new file mode 100644 index 0000000000..812b15a3c0 --- /dev/null +++ b/packages/next-common/components/people/judgement/email/emailAddressRow.jsx @@ -0,0 +1,10 @@ +export default function EmailAddressRow({ email }) { + return ( +
+
+ Email Address: + {email} +
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/email/emailCardHeader.jsx b/packages/next-common/components/people/judgement/email/emailCardHeader.jsx new file mode 100644 index 0000000000..ac988a5a2f --- /dev/null +++ b/packages/next-common/components/people/judgement/email/emailCardHeader.jsx @@ -0,0 +1,17 @@ +import { LinkEmail } from "@osn/icons/subsquare"; + +export default function EmailCardHeader({ tag, actions }) { + return ( +
+
+
+ + · +

Email

+
+
{tag}
+
+ {actions} +
+ ); +} diff --git a/packages/next-common/components/people/judgement/email/emailVerificationTips.jsx b/packages/next-common/components/people/judgement/email/emailVerificationTips.jsx new file mode 100644 index 0000000000..e9b8f1ac0c --- /dev/null +++ b/packages/next-common/components/people/judgement/email/emailVerificationTips.jsx @@ -0,0 +1,10 @@ +export default function EmailVerificationTips() { + return ( +
+

+ Tips: We‘ve sent a 6-digit + code to your email. Code expires in 10 minutes. +

+
+ ); +} diff --git a/packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js b/packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js new file mode 100644 index 0000000000..c7807c4a29 --- /dev/null +++ b/packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js @@ -0,0 +1,50 @@ +import { useCallback, useState } from "react"; +import { useDispatch } from "react-redux"; +import { useEnsureLogin } from "next-common/hooks/useEnsureLogin"; +import { nextApi } from "next-common/services/nextApi"; +import { + newErrorToast, + newSuccessToast, +} from "next-common/store/reducers/toastSlice"; + +export default function useSendJudgementEmailCode({ who, setError, onSent }) { + const dispatch = useDispatch(); + const { ensureLogin } = useEnsureLogin(); + const [sending, setSending] = useState(false); + + const sendCode = useCallback(async () => { + setError?.(""); + if (!who) { + setError?.("Unable to determine who"); + return; + } + + setSending(true); + try { + if (!(await ensureLogin())) { + return; + } + + const { error: sendError } = await nextApi.post( + "people/judgement/auth/email/send-code", + { who }, + ); + if (sendError) { + const message = sendError.message || "Failed to send code"; + setError?.(message); + dispatch(newErrorToast(message)); + return; + } + + onSent?.(); + dispatch(newSuccessToast("Verification code sent")); + } finally { + setSending(false); + } + }, [dispatch, ensureLogin, onSent, setError, who]); + + return { + sending, + sendCode, + }; +} diff --git a/packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js b/packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js new file mode 100644 index 0000000000..9f0fc57b0f --- /dev/null +++ b/packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js @@ -0,0 +1,55 @@ +import { useCallback, useState } from "react"; +import { useDispatch } from "react-redux"; +import { useEnsureLogin } from "next-common/hooks/useEnsureLogin"; +import { nextApi } from "next-common/services/nextApi"; +import { newSuccessToast } from "next-common/store/reducers/toastSlice"; + +export default function useVerifyJudgementEmailCode({ + who, + code, + setError, + onVerified, +}) { + const dispatch = useDispatch(); + const { ensureLogin } = useEnsureLogin(); + const [verifying, setVerifying] = useState(false); + + const verifyCode = useCallback(async () => { + setError?.(""); + if (!who) { + setError?.("Unable to determine who"); + return; + } + + if (!code) { + setError?.("Code is required"); + return; + } + + setVerifying(true); + try { + if (!(await ensureLogin())) { + return; + } + + const { error: verifyError } = await nextApi.post( + "people/judgement/auth/email/verify-code", + { who, code }, + ); + if (verifyError) { + setError?.(verifyError.message || "Verification failed"); + return; + } + + dispatch(newSuccessToast("Email verified")); + onVerified?.(); + } finally { + setVerifying(false); + } + }, [code, dispatch, ensureLogin, onVerified, setError, who]); + + return { + verifying, + verifyCode, + }; +} diff --git a/packages/next-common/components/people/judgement/email/index.jsx b/packages/next-common/components/people/judgement/email/index.jsx new file mode 100644 index 0000000000..38d7aae84b --- /dev/null +++ b/packages/next-common/components/people/judgement/email/index.jsx @@ -0,0 +1,25 @@ +import { useEffect, useState } from "react"; +import PendingEmailCard from "./pendingEmailCard"; +import VerifiedEmailCard from "./verifiedEmailCard"; + +export default function Email({ request }) { + const email = request?.info?.email || ""; + const initialVerified = request?.verification?.email === true; + const [verified, setVerified] = useState(initialVerified); + + useEffect(() => { + setVerified(initialVerified); + }, [initialVerified]); + + if (verified) { + return ; + } + + return ( + setVerified(true)} + /> + ); +} diff --git a/packages/next-common/components/people/judgement/email/pendingEmailCard.jsx b/packages/next-common/components/people/judgement/email/pendingEmailCard.jsx new file mode 100644 index 0000000000..c3d360a114 --- /dev/null +++ b/packages/next-common/components/people/judgement/email/pendingEmailCard.jsx @@ -0,0 +1,40 @@ +import { useState } from "react"; +import useCountdown from "next-common/hooks/useCountdown"; +import PendingEmailNotSentCard from "./pendingEmailNotSentCard"; +import PendingEmailSentCard from "./pendingEmailSentCard"; + +export default function PendingEmailCard({ request, email, onVerified }) { + const [hasSent, setHasSent] = useState(false); + + const { + countdown, + start: startCountdown, + stop: stopCountdown, + } = useCountdown(); + + if (!hasSent) { + return ( + { + setHasSent(true); + startCountdown(60); + }} + /> + ); + } + + return ( + { + stopCountdown(); + onVerified?.(); + }} + /> + ); +} diff --git a/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx b/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx new file mode 100644 index 0000000000..18aa0bb37f --- /dev/null +++ b/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx @@ -0,0 +1,34 @@ +import { useState } from "react"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import SecondaryButton from "next-common/lib/button/secondary"; +import EmailCardHeader from "./emailCardHeader"; +import EmailAddressRow from "./emailAddressRow"; +import useSendJudgementEmailCode from "./hooks/useSendJudgementEmailCode"; + +export default function PendingEmailNotSentCard({ request, email, onSent }) { + const [error, setError] = useState(""); + const who = request?.who || ""; + + const { sending, sendCode } = useSendJudgementEmailCode({ + who, + setError, + onSent, + }); + + return ( +
+ Pending} + actions={ +
+ + Send code + +
+ } + /> + + {error &&
{error}
} +
+ ); +} diff --git a/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx b/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx new file mode 100644 index 0000000000..3644478bbf --- /dev/null +++ b/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx @@ -0,0 +1,88 @@ +import { useState } from "react"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import PrimaryButton from "next-common/lib/button/primary"; +import SecondaryButton from "next-common/lib/button/secondary"; +import Input from "next-common/lib/input"; +import EmailCardHeader from "./emailCardHeader"; +import EmailAddressRow from "./emailAddressRow"; +import EmailVerificationTips from "./emailVerificationTips"; +import useSendJudgementEmailCode from "./hooks/useSendJudgementEmailCode"; +import useVerifyJudgementEmailCode from "./hooks/useVerifyJudgementEmailCode"; + +export default function PendingEmailSentCard({ + request, + email, + countdown, + startCountdown, + onVerified, +}) { + const [error, setError] = useState(""); + const who = request?.who || ""; + const [code, setCode] = useState(""); + const trimmedCode = String(code || "").trim(); + const canVerify = Boolean(trimmedCode); + + const { sending, sendCode } = useSendJudgementEmailCode({ + who, + setError, + onSent: () => { + startCountdown(60); + }, + }); + + const { verifying, verifyCode } = useVerifyJudgementEmailCode({ + who, + code: trimmedCode, + setError, + onVerified: () => { + onVerified?.(); + }, + }); + + return ( +
+ Pending} + actions={ +
+ 0} + size="small" + > + Resend{countdown > 0 ? ` ${countdown}s` : ""} + + + Start verify + +
+ } + /> + + + + + +
+ +
+ setCode(e.target.value)} + /> +
+ {error &&
{error}
} +
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/email/verifiedEmailCard.jsx b/packages/next-common/components/people/judgement/email/verifiedEmailCard.jsx new file mode 100644 index 0000000000..1281cf549f --- /dev/null +++ b/packages/next-common/components/people/judgement/email/verifiedEmailCard.jsx @@ -0,0 +1,13 @@ +import { PositiveTag } from "next-common/components/tags/state/styled"; +import EmailCardHeader from "./emailCardHeader"; +import EmailAddressRow from "./emailAddressRow"; + +export default function VerifiedEmailCard({ email }) { + return ( +
+ Verified} /> + + +
+ ); +} diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index 9511e7002f..d7067aaa1e 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -29,7 +29,7 @@ export default function JudgementPage() { const { value, loading: isLoadingMyJudgementRequest } = useMyJudgementRequest(); - const myJudgementRequest = value?.items[0] || null; + const request = value?.items[0] || null; return ( @@ -59,35 +59,35 @@ export default function JudgementPage() {
- {isLoadingMyJudgementRequest ? ( + {isLoadingMyJudgementRequest && !request ? (
) : ( <> - {myJudgementRequest?.info?.email && ( + {request?.info?.email && (
- +
)} - {myJudgementRequest?.info?.element && ( + {request?.info?.element && (
)} - {myJudgementRequest?.info?.discord && ( + {request?.info?.discord && (
- +
)} - {myJudgementRequest?.info?.twitter && ( + {request?.info?.twitter && (
- +
)} - {myJudgementRequest?.info?.github && ( + {request?.info?.github && (
- +
)} diff --git a/packages/next-common/hooks/scanHeight/useSubScanHeightStream.js b/packages/next-common/hooks/scanHeight/useSubScanHeightStream.js index e84256c2ee..b72f523f14 100644 --- a/packages/next-common/hooks/scanHeight/useSubScanHeightStream.js +++ b/packages/next-common/hooks/scanHeight/useSubScanHeightStream.js @@ -1,6 +1,6 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { sleep } from "next-common/utils"; -import { noop } from "lodash-es"; +import { isNil, noop } from "lodash-es"; export function useSubScanHeightStream({ url, @@ -9,30 +9,44 @@ export function useSubScanHeightStream({ enabled = true, }) { const [reconnect, setReconnect] = useState(0); + const callbackRef = useRef(callback); + + useEffect(() => { + callbackRef.current = callback; + }, [callback]); useEffect(() => { if (!enabled) { return; } - let aborted = false; + const controller = new AbortController(); + let reader = null; + + (async () => { + try { + const response = await fetch( + new URL(url, process.env.NEXT_PUBLIC_BACKEND_API_END_POINT), + { signal: controller.signal }, + ); - fetch(new URL(url, process.env.NEXT_PUBLIC_BACKEND_API_END_POINT)) - .then(async (response) => { if (!response.ok) { throw new Error(response.statusText); } + const decoder = new TextDecoder(); - const reader = response.body.getReader(); + let buffer = ""; + reader = response.body?.getReader?.() ?? null; if (!reader) { throw new Error("Reader is null"); } + // eslint-disable-next-line no-constant-condition while (true) { - if (aborted) { - reader.cancel(); + if (controller.signal.aborted) { break; } + const { value, done } = await Promise.race([ reader.read(), new Promise((_, reject) => @@ -42,28 +56,55 @@ export function useSubScanHeightStream({ ), ), ]); + if (done) { throw new Error("Scan height stream closed"); } + try { - const data = JSON.parse(decoder.decode(value)); - const possibleValue = data?.value; - if (possibleValue) { - callback(possibleValue); + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) { + continue; + } + const data = JSON.parse(trimmed); + const possibleValue = data?.value; + if (!isNil(possibleValue)) { + callbackRef.current(possibleValue); + } } } catch (e) { console.error("Error parsing scan height data:", e); } } - }) - .catch(async (e) => { + } catch (e) { + if (controller.signal.aborted || e?.name === "AbortError") { + return; + } + console.error("Error fetching scan height:", e); await sleep(5000); + if (controller.signal.aborted) { + return; + } setReconnect((prev) => prev + 1); - }); + } finally { + try { + await reader?.cancel?.(); + } catch { + // ignore + } + } + })(); return () => { - aborted = true; + controller.abort(); + // reader.cancel() is async and may reject with AbortError; swallow it. + reader?.cancel?.().catch(() => null); }; - }, [reconnect, url, timeout, callback, enabled]); + }, [reconnect, url, timeout, enabled]); } diff --git a/packages/next-common/hooks/useCountdown.js b/packages/next-common/hooks/useCountdown.js new file mode 100644 index 0000000000..2deb9d448f --- /dev/null +++ b/packages/next-common/hooks/useCountdown.js @@ -0,0 +1,27 @@ +import { useCallback, useEffect, useState } from "react"; + +export default function useCountdown() { + const [countdown, setCountdown] = useState(0); + + useEffect(() => { + let timer; + if (countdown > 0) { + timer = setTimeout(() => setCountdown((v) => v - 1), 1000); + } + return () => clearTimeout(timer); + }, [countdown]); + + const start = useCallback((seconds) => { + setCountdown(seconds); + }, []); + + const stop = useCallback(() => { + setCountdown(0); + }, []); + + return { + countdown, + start, + stop, + }; +} From 8e10913bb7c0db34bcd52dc4e79ae0e99dee8609 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Wed, 7 Jan 2026 12:37:19 +0800 Subject: [PATCH 14/41] Implement judgement summary data, #6881 (#7078) --- .../components/people/judgement/index.jsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index d7067aaa1e..84487451ba 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -14,6 +14,7 @@ import Github from "./github"; import Element from "./element"; import Loading from "next-common/components/loading"; import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; +import { PeopleSocialType } from "./consts"; export const tabs = [ { @@ -31,6 +32,17 @@ export default function JudgementPage() { const request = value?.items[0] || null; + const allSocialTypes = Object.values(PeopleSocialType); + const info = request?.info || {}; + const verification = request?.verification || {}; + const totalSocials = allSocialTypes.filter((key) => + Boolean(info[key]), + ).length; + const verifiedSocials = allSocialTypes.filter( + (key) => Boolean(info[key]) && verification?.[key] === true, + ).length; + const pendingSocials = totalSocials - verifiedSocials; + return ( - 0 + {verifiedSocials} - {0} + {pendingSocials} - + - {0} - / {0} + {totalSocials} + / {allSocialTypes.length} From 283abbc15deda5a9b055ab2c8c9d84c1edeb418d Mon Sep 17 00:00:00 2001 From: chaojun Date: Wed, 7 Jan 2026 13:07:55 +0800 Subject: [PATCH 15/41] refactor judgement summary, #6881 --- .../components/people/judgement/index.jsx | 39 +---------------- .../components/people/judgement/summary.js | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 37 deletions(-) create mode 100644 packages/next-common/components/people/judgement/summary.js diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index 84487451ba..1153499782 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -2,11 +2,6 @@ import ListLayout from "next-common/components/layout/ListLayout"; import ChainSocialLinks from "next-common/components/chain/socialLinks"; import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; -import Account from "next-common/components/account"; -import useRealAddress from "next-common/utils/hooks/useRealAddress"; -import { SummaryGreyText } from "next-common/components/summary/styled"; -import SummaryLayout from "next-common/components/summary/layout/layout"; -import SummaryItem from "next-common/components/summary/layout/item"; import Email from "./email"; import Discord from "./discord"; import Twitter from "./twitter"; @@ -14,7 +9,7 @@ import Github from "./github"; import Element from "./element"; import Loading from "next-common/components/loading"; import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; -import { PeopleSocialType } from "./consts"; +import JudgementSummary from "./summary"; export const tabs = [ { @@ -26,23 +21,11 @@ export const tabs = [ ]; export default function JudgementPage() { - const address = useRealAddress(); const { value, loading: isLoadingMyJudgementRequest } = useMyJudgementRequest(); const request = value?.items[0] || null; - const allSocialTypes = Object.values(PeopleSocialType); - const info = request?.info || {}; - const verification = request?.verification || {}; - const totalSocials = allSocialTypes.filter((key) => - Boolean(info[key]), - ).length; - const verifiedSocials = allSocialTypes.filter( - (key) => Boolean(info[key]) && verification?.[key] === true, - ).length; - const pendingSocials = totalSocials - verifiedSocials; - return ( } > -
-
- -
- - - {verifiedSocials} - - - {pendingSocials} - - - - {totalSocials} - / {allSocialTypes.length} - - - -
+
{isLoadingMyJudgementRequest && !request ? (
diff --git a/packages/next-common/components/people/judgement/summary.js b/packages/next-common/components/people/judgement/summary.js new file mode 100644 index 0000000000..bb6d7f1c85 --- /dev/null +++ b/packages/next-common/components/people/judgement/summary.js @@ -0,0 +1,43 @@ +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { PeopleSocialType } from "./consts"; +import { SummaryGreyText } from "next-common/components/summary/styled"; +import SummaryLayout from "next-common/components/summary/layout/layout"; +import SummaryItem from "next-common/components/summary/layout/item"; +import Account from "next-common/components/account"; + +export default function JudgementSummary({ request }) { + const address = useRealAddress(); + + const allSocialTypes = Object.values(PeopleSocialType); + const info = request?.info || {}; + const verification = request?.verification || {}; + const totalSocials = allSocialTypes.filter((key) => + Boolean(info[key]), + ).length; + const verifiedSocials = allSocialTypes.filter( + (key) => Boolean(info[key]) && verification?.[key] === true, + ).length; + const pendingSocials = totalSocials - verifiedSocials; + + return ( +
+
+ +
+ + + {verifiedSocials} + + + {pendingSocials} + + + + {totalSocials} + / {allSocialTypes.length} + + + +
+ ); +} From 4bf78a01a37b6f6f9d53d61ba5f2ef35f2c0cd0e Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Wed, 7 Jan 2026 13:32:03 +0800 Subject: [PATCH 16/41] Implement non-login page, #6881 (#7079) --- .../components/people/judgement/content.jsx | 56 ++++++++++++++++ .../components/people/judgement/index.jsx | 65 ++++--------------- 2 files changed, 70 insertions(+), 51 deletions(-) create mode 100644 packages/next-common/components/people/judgement/content.jsx diff --git a/packages/next-common/components/people/judgement/content.jsx b/packages/next-common/components/people/judgement/content.jsx new file mode 100644 index 0000000000..861922ffa8 --- /dev/null +++ b/packages/next-common/components/people/judgement/content.jsx @@ -0,0 +1,56 @@ +import Loading from "next-common/components/loading"; +import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; +import Discord from "./discord"; +import Element from "./element"; +import Email from "./email"; +import Github from "./github"; +import JudgementSummary from "./summary"; +import Twitter from "./twitter"; + +export default function JudgementPageContent() { + const { value, loading: isLoadingMyJudgementRequest } = + useMyJudgementRequest(); + + const request = value?.items[0] || null; + + return ( + <> + +
+ {isLoadingMyJudgementRequest && !request ? ( +
+ +
+ ) : ( + <> + {request?.info?.email && ( +
+ +
+ )} + {request?.info?.element && ( +
+ +
+ )} + {request?.info?.discord && ( +
+ +
+ )} + {request?.info?.twitter && ( +
+ +
+ )} + {request?.info?.github && ( +
+ +
+ )} + + )} +
+ + ); +} diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index 1153499782..4fddc037bb 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -2,14 +2,9 @@ import ListLayout from "next-common/components/layout/ListLayout"; import ChainSocialLinks from "next-common/components/chain/socialLinks"; import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; -import Email from "./email"; -import Discord from "./discord"; -import Twitter from "./twitter"; -import Github from "./github"; -import Element from "./element"; -import Loading from "next-common/components/loading"; -import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; -import JudgementSummary from "./summary"; +import NoWalletConnected from "next-common/components/noWalletConnected"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import JudgementPageContent from "./content"; export const tabs = [ { @@ -21,55 +16,23 @@ export const tabs = [ ]; export default function JudgementPage() { - const { value, loading: isLoadingMyJudgementRequest } = - useMyJudgementRequest(); - - const request = value?.items[0] || null; + const realAddress = useRealAddress(); return ( } > - -
- {isLoadingMyJudgementRequest && !request ? ( -
- -
- ) : ( - <> - {request?.info?.email && ( -
- -
- )} - {request?.info?.element && ( -
- -
- )} - {request?.info?.discord && ( -
- -
- )} - {request?.info?.twitter && ( -
- -
- )} - {request?.info?.github && ( -
- -
- )} - - )} -
+ {!realAddress ? ( +
+ +
+ ) : ( + + )}
); From bc2143299cdad304c45e3fe5eb256a44f28e3f43 Mon Sep 17 00:00:00 2001 From: chaojun Date: Thu, 8 Jan 2026 09:53:31 +0800 Subject: [PATCH 17/41] fix: judgement summary loading, #6881 --- .../components/people/judgement/content.jsx | 7 +++++-- .../components/people/judgement/summary.js | 21 ++++++++++++------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/next-common/components/people/judgement/content.jsx b/packages/next-common/components/people/judgement/content.jsx index 861922ffa8..ca423f1f11 100644 --- a/packages/next-common/components/people/judgement/content.jsx +++ b/packages/next-common/components/people/judgement/content.jsx @@ -15,7 +15,10 @@ export default function JudgementPageContent() { return ( <> - +
{isLoadingMyJudgementRequest && !request ? (
@@ -28,7 +31,7 @@ export default function JudgementPageContent() {
)} - {request?.info?.element && ( + {request?.info?.matrix && (
diff --git a/packages/next-common/components/people/judgement/summary.js b/packages/next-common/components/people/judgement/summary.js index bb6d7f1c85..ac98aa2c51 100644 --- a/packages/next-common/components/people/judgement/summary.js +++ b/packages/next-common/components/people/judgement/summary.js @@ -4,8 +4,9 @@ import { SummaryGreyText } from "next-common/components/summary/styled"; import SummaryLayout from "next-common/components/summary/layout/layout"; import SummaryItem from "next-common/components/summary/layout/item"; import Account from "next-common/components/account"; +import LoadableContent from "next-common/components/common/loadableContent"; -export default function JudgementSummary({ request }) { +export default function JudgementSummary({ request, loading }) { const address = useRealAddress(); const allSocialTypes = Object.values(PeopleSocialType); @@ -26,16 +27,22 @@ export default function JudgementSummary({ request }) {
- {verifiedSocials} + + {verifiedSocials} + - {pendingSocials} + + {pendingSocials} + - - {totalSocials} - / {allSocialTypes.length} - + + + {totalSocials} + / {allSocialTypes.length} + +
From 1067495b24315c5c0a7b6a5ce694d14fbc9ba659 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Fri, 9 Jan 2026 18:17:07 +0800 Subject: [PATCH 18/41] Add identity element verification, #6881 (#7092) --- .../components/people/judgement/content.jsx | 2 +- .../components/people/judgement/element.jsx | 85 ------------------- .../judgement/element/elementAddressRow.jsx | 10 +++ .../judgement/element/elementCardHeader.jsx | 17 ++++ .../element/elementVerificationTips.jsx | 11 +++ .../hooks/useFinishElementVerification.js | 54 ++++++++++++ .../hooks/useStartElementVerification.js | 54 ++++++++++++ .../people/judgement/element/index.jsx | 25 ++++++ .../judgement/element/pendingElementCard.jsx | 38 +++++++++ .../element/pendingElementNotStartedCard.jsx | 42 +++++++++ .../element/pendingElementStartedCard.jsx | 54 ++++++++++++ .../judgement/element/verifiedElementCard.jsx | 13 +++ 12 files changed, 319 insertions(+), 86 deletions(-) delete mode 100644 packages/next-common/components/people/judgement/element.jsx create mode 100644 packages/next-common/components/people/judgement/element/elementAddressRow.jsx create mode 100644 packages/next-common/components/people/judgement/element/elementCardHeader.jsx create mode 100644 packages/next-common/components/people/judgement/element/elementVerificationTips.jsx create mode 100644 packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js create mode 100644 packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js create mode 100644 packages/next-common/components/people/judgement/element/index.jsx create mode 100644 packages/next-common/components/people/judgement/element/pendingElementCard.jsx create mode 100644 packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx create mode 100644 packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx create mode 100644 packages/next-common/components/people/judgement/element/verifiedElementCard.jsx diff --git a/packages/next-common/components/people/judgement/content.jsx b/packages/next-common/components/people/judgement/content.jsx index ca423f1f11..a076d43112 100644 --- a/packages/next-common/components/people/judgement/content.jsx +++ b/packages/next-common/components/people/judgement/content.jsx @@ -33,7 +33,7 @@ export default function JudgementPageContent() { )} {request?.info?.matrix && (
- +
)} {request?.info?.discord && ( diff --git a/packages/next-common/components/people/judgement/element.jsx b/packages/next-common/components/people/judgement/element.jsx deleted file mode 100644 index bbd6a67092..0000000000 --- a/packages/next-common/components/people/judgement/element.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import { LinkElement } from "@osn/icons/subsquare"; -import { ClosedTag } from "next-common/components/tags/state/styled"; -import PrimaryButton from "next-common/lib/button/primary"; -import Copyable from "next-common/components/copyable"; -import { newSuccessToast } from "next-common/store/reducers/toastSlice"; -import { useDispatch } from "react-redux"; -import { useState } from "react"; - -export default function Email() { - return ( -
-
-
-
- - · -

Element

-
-
- Pending -
-
- -
-
-
- Matrix ID: - @quinn:matrix.org -
-
- -
- - -
-
- -
- - XXX-XXX-1234 - -
-
-
- -
- After sending the code in the room, click the "verify" - button below to check if verification is complete. -
-
-
- ); -} - -function VerifyButton() { - const dispatch = useDispatch(); - const [loading, setLoading] = useState(false); - const verifyCode = async () => { - setLoading(true); - setTimeout(() => { - setLoading(false); - dispatch(newSuccessToast("Verify element successfully")); - }, 1500); - }; - return ( - - Verify - - ); -} diff --git a/packages/next-common/components/people/judgement/element/elementAddressRow.jsx b/packages/next-common/components/people/judgement/element/elementAddressRow.jsx new file mode 100644 index 0000000000..62635035e6 --- /dev/null +++ b/packages/next-common/components/people/judgement/element/elementAddressRow.jsx @@ -0,0 +1,10 @@ +export default function ElementAccountRow({ elementAccount }) { + return ( +
+
+ Matrix ID: + {elementAccount} +
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/element/elementCardHeader.jsx b/packages/next-common/components/people/judgement/element/elementCardHeader.jsx new file mode 100644 index 0000000000..48450e0725 --- /dev/null +++ b/packages/next-common/components/people/judgement/element/elementCardHeader.jsx @@ -0,0 +1,17 @@ +import { LinkElement } from "@osn/icons/subsquare"; + +export default function ElementCardHeader({ tag, actions }) { + return ( +
+
+
+ + · +

Element

+
+
{tag}
+
+ {actions} +
+ ); +} diff --git a/packages/next-common/components/people/judgement/element/elementVerificationTips.jsx b/packages/next-common/components/people/judgement/element/elementVerificationTips.jsx new file mode 100644 index 0000000000..448441ac13 --- /dev/null +++ b/packages/next-common/components/people/judgement/element/elementVerificationTips.jsx @@ -0,0 +1,11 @@ +export default function ElementVerificationTips() { + return ( +
+

+ Tips: Please login to your Element + account and accept our invitation to complete the verification. Code + expires in 10 minutes. +

+
+ ); +} diff --git a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js new file mode 100644 index 0000000000..eebd643dde --- /dev/null +++ b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js @@ -0,0 +1,54 @@ +import { useCallback, useState } from "react"; +import { useDispatch } from "react-redux"; +import { nextApi } from "next-common/services/nextApi"; +import { newErrorToast } from "next-common/store/reducers/toastSlice"; + +export default function useFinishElementVerification({ + who, + setError, + onVerified, +}) { + const dispatch = useDispatch(); + const [verifying, setVerifying] = useState(false); + + const verify = useCallback(async () => { + setError?.(""); + if (!who) { + setError?.("Unable to determine who"); + return; + } + + setVerifying(true); + try { + const { result, error: startError } = await nextApi.fetch( + "people/judgement/requests/pending", + { who }, + ); + if (startError) { + const message = startError.message || "Failed to start verification"; + setError?.(message); + dispatch(newErrorToast(message)); + return; + } + + const request = result.items[0]; + + const isElementVerified = request?.verification.element === true; + if (isElementVerified) { + onVerified?.(); + } else { + const message = + "Element verification is not passed yet, please check your Element app."; + // setError?.(message); + dispatch(newErrorToast(message)); + } + } finally { + setVerifying(false); + } + }, [dispatch, onVerified, setError, who]); + + return { + verifying, + verify, + }; +} diff --git a/packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js b/packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js new file mode 100644 index 0000000000..5b6018fe1c --- /dev/null +++ b/packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js @@ -0,0 +1,54 @@ +import { useCallback, useState } from "react"; +import { useDispatch } from "react-redux"; +import { useEnsureLogin } from "next-common/hooks/useEnsureLogin"; +import { nextApi } from "next-common/services/nextApi"; +import { + newErrorToast, + newSuccessToast, +} from "next-common/store/reducers/toastSlice"; + +export default function useStartElementVerification({ + who, + setError, + onStarted, +}) { + const dispatch = useDispatch(); + const { ensureLogin } = useEnsureLogin(); + const [starting, setStarting] = useState(false); + + const startVerify = useCallback(async () => { + setError?.(""); + if (!who) { + setError?.("Unable to determine who"); + return; + } + + setStarting(true); + try { + if (!(await ensureLogin())) { + return; + } + + const { result, error: startError } = await nextApi.post( + "people/judgement/auth/element/start", + { who }, + ); + if (startError) { + const message = startError.message || "Failed to start verification"; + setError?.(message); + dispatch(newErrorToast(message)); + return; + } + + onStarted?.(result?.code); + dispatch(newSuccessToast("Verification code sent")); + } finally { + setStarting(false); + } + }, [dispatch, ensureLogin, onStarted, setError, who]); + + return { + starting, + startVerify, + }; +} diff --git a/packages/next-common/components/people/judgement/element/index.jsx b/packages/next-common/components/people/judgement/element/index.jsx new file mode 100644 index 0000000000..f0a6554efc --- /dev/null +++ b/packages/next-common/components/people/judgement/element/index.jsx @@ -0,0 +1,25 @@ +import { useEffect, useState } from "react"; +import PendingElementCard from "./pendingElementCard"; +import VerifiedElementCard from "./verifiedElementCard"; + +export default function Element({ request }) { + const elementAccount = request?.info?.matrix || ""; + const initialVerified = request?.verification?.element === true; + const [verified, setVerified] = useState(initialVerified); + + useEffect(() => { + setVerified(initialVerified); + }, [initialVerified]); + + if (verified) { + return ; + } + + return ( + setVerified(true)} + /> + ); +} diff --git a/packages/next-common/components/people/judgement/element/pendingElementCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementCard.jsx new file mode 100644 index 0000000000..79b2c4e259 --- /dev/null +++ b/packages/next-common/components/people/judgement/element/pendingElementCard.jsx @@ -0,0 +1,38 @@ +import { useState } from "react"; +import PendingElementNotStartedCard from "./pendingElementNotStartedCard"; +import PendingElementStartedCard from "./pendingElementStartedCard"; + +export default function PendingElementCard({ + request, + elementAccount, + onVerified, +}) { + const isVerifying = request?.verification?.element?.verifying === true; + const verifyingCode = request?.verification?.element?.code || ""; + const [code, setCode] = useState(verifyingCode); + const [hasStarted, setHasStarted] = useState(isVerifying); + + if (!hasStarted) { + return ( + { + setCode(verificationCode); + setHasStarted(true); + }} + /> + ); + } + + return ( + { + onVerified?.(); + }} + /> + ); +} diff --git a/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx new file mode 100644 index 0000000000..9917077520 --- /dev/null +++ b/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import SecondaryButton from "next-common/lib/button/secondary"; +import ElementCardHeader from "./elementCardHeader"; +import ElementAccountRow from "./elementAddressRow"; +import useStartElementVerification from "./hooks/useStartElementVerification"; + +export default function PendingElementNotStartedCard({ + request, + elementAccount, + onStarted, +}) { + const [error, setError] = useState(""); + const who = request?.who || ""; + + const { starting, startVerify } = useStartElementVerification({ + who, + setError, + onStarted, + }); + + return ( +
+ Pending} + actions={ +
+ + Start verify + +
+ } + /> + + {error &&
{error}
} +
+ ); +} diff --git a/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx new file mode 100644 index 0000000000..b729de2ad3 --- /dev/null +++ b/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx @@ -0,0 +1,54 @@ +import { useState } from "react"; +import { ClosedTag } from "next-common/components/tags/state/styled"; +import PrimaryButton from "next-common/lib/button/primary"; +import Input from "next-common/lib/input"; +import ElementCardHeader from "./elementCardHeader"; +import ElementAddressRow from "./elementAddressRow"; +import ElementVerificationTips from "./elementVerificationTips"; +import useFinishElementVerification from "./hooks/useFinishElementVerification"; + +export default function PendingElementStartedCard({ + request, + elementAccount, + verificationCode, + onVerified, +}) { + const [error, setError] = useState(""); + const who = request?.who || ""; + + const { verifying, verify } = useFinishElementVerification({ + who, + code: verificationCode, + setError, + onVerified: () => { + onVerified?.(); + }, + }); + + return ( +
+ Pending} + actions={ +
+ + {"I've finished the verification"} + +
+ } + /> + + + + + +
+ +
+ +
+ {error &&
{error}
} +
+
+ ); +} diff --git a/packages/next-common/components/people/judgement/element/verifiedElementCard.jsx b/packages/next-common/components/people/judgement/element/verifiedElementCard.jsx new file mode 100644 index 0000000000..374d91ad6c --- /dev/null +++ b/packages/next-common/components/people/judgement/element/verifiedElementCard.jsx @@ -0,0 +1,13 @@ +import { PositiveTag } from "next-common/components/tags/state/styled"; +import ElementCardHeader from "./elementCardHeader"; +import ElementAccountRow from "./elementAddressRow"; + +export default function VerifiedElementCard({ elementAccount }) { + return ( +
+ Verified} /> + + +
+ ); +} From 2a5aa7acca9c61b9aa9577c59e9ceda944e64424 Mon Sep 17 00:00:00 2001 From: chaojun Date: Mon, 12 Jan 2026 11:35:51 +0800 Subject: [PATCH 19/41] Improve button tooltip, #6881 --- .../element/pendingElementNotStartedCard.jsx | 17 +++++++----- .../element/pendingElementStartedCard.jsx | 9 ++++--- .../people/judgement/socialConnect.jsx | 26 ++++++++++++------- .../components/people/judgement/summary.js | 6 +++-- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx index 9917077520..0de2a35723 100644 --- a/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx +++ b/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx @@ -4,6 +4,7 @@ import SecondaryButton from "next-common/lib/button/secondary"; import ElementCardHeader from "./elementCardHeader"; import ElementAccountRow from "./elementAddressRow"; import useStartElementVerification from "./hooks/useStartElementVerification"; +import Tooltip from "next-common/components/tooltip"; export default function PendingElementNotStartedCard({ request, @@ -25,13 +26,15 @@ export default function PendingElementNotStartedCard({ tag={Pending} actions={
- - Start verify - + + + Start verify + +
} /> diff --git a/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx index b729de2ad3..93b8a04991 100644 --- a/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx +++ b/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { ClosedTag } from "next-common/components/tags/state/styled"; import PrimaryButton from "next-common/lib/button/primary"; import Input from "next-common/lib/input"; +import Tooltip from "next-common/components/tooltip"; import ElementCardHeader from "./elementCardHeader"; import ElementAddressRow from "./elementAddressRow"; import ElementVerificationTips from "./elementVerificationTips"; @@ -31,9 +32,11 @@ export default function PendingElementStartedCard({ tag={Pending} actions={
- - {"I've finished the verification"} - + + + Confirm finished + +
} /> diff --git a/packages/next-common/components/people/judgement/socialConnect.jsx b/packages/next-common/components/people/judgement/socialConnect.jsx index 89fae9bb1c..cc5bf7fb51 100644 --- a/packages/next-common/components/people/judgement/socialConnect.jsx +++ b/packages/next-common/components/people/judgement/socialConnect.jsx @@ -2,6 +2,7 @@ import { ClosedTag, PositiveTag, } from "next-common/components/tags/state/styled"; +import Tooltip from "next-common/components/tooltip"; import PrimaryButton from "next-common/lib/button/primary"; export default function PeopleJudgementSocialConnect({ @@ -12,6 +13,11 @@ export default function PeopleJudgementSocialConnect({ loading, onConnect, }) { + let message = `Click to verify your ${title} account`; + if (connected) { + message = `${title} account already verified`; + } + return (
@@ -23,7 +29,7 @@ export default function PeopleJudgementSocialConnect({
{connected ? ( - Connected + Verified ) : ( Pending )} @@ -37,14 +43,16 @@ export default function PeopleJudgementSocialConnect({ {username}
- - Connect {title} - + + + Verify {title} + +
); diff --git a/packages/next-common/components/people/judgement/summary.js b/packages/next-common/components/people/judgement/summary.js index ac98aa2c51..64e3d32dd8 100644 --- a/packages/next-common/components/people/judgement/summary.js +++ b/packages/next-common/components/people/judgement/summary.js @@ -13,10 +13,12 @@ export default function JudgementSummary({ request, loading }) { const info = request?.info || {}; const verification = request?.verification || {}; const totalSocials = allSocialTypes.filter((key) => - Boolean(info[key]), + Boolean(info[key === "element" ? "matrix" : key]), ).length; const verifiedSocials = allSocialTypes.filter( - (key) => Boolean(info[key]) && verification?.[key] === true, + (key) => + Boolean(info[key === "element" ? "matrix" : key]) && + verification?.[key] === true, ).length; const pendingSocials = totalSocials - verifiedSocials; From c4b6d2139ce38984b3935a765315b50859a4091d Mon Sep 17 00:00:00 2001 From: chaojun Date: Mon, 12 Jan 2026 11:43:05 +0800 Subject: [PATCH 20/41] Improve button tooltip, #6881 --- .../email/pendingEmailNotSentCard.jsx | 13 ++++-- .../judgement/email/pendingEmailSentCard.jsx | 42 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx b/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx index 18aa0bb37f..f248d3d67c 100644 --- a/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx +++ b/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx @@ -4,6 +4,7 @@ import SecondaryButton from "next-common/lib/button/secondary"; import EmailCardHeader from "./emailCardHeader"; import EmailAddressRow from "./emailAddressRow"; import useSendJudgementEmailCode from "./hooks/useSendJudgementEmailCode"; +import Tooltip from "next-common/components/tooltip"; export default function PendingEmailNotSentCard({ request, email, onSent }) { const [error, setError] = useState(""); @@ -21,9 +22,15 @@ export default function PendingEmailNotSentCard({ request, email, onSent }) { tag={Pending} actions={
- - Send code - + + + Send code + +
} /> diff --git a/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx b/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx index 3644478bbf..257da29142 100644 --- a/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx +++ b/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx @@ -8,6 +8,7 @@ import EmailAddressRow from "./emailAddressRow"; import EmailVerificationTips from "./emailVerificationTips"; import useSendJudgementEmailCode from "./hooks/useSendJudgementEmailCode"; import useVerifyJudgementEmailCode from "./hooks/useVerifyJudgementEmailCode"; +import Tooltip from "next-common/components/tooltip"; export default function PendingEmailSentCard({ request, @@ -39,28 +40,37 @@ export default function PendingEmailSentCard({ }, }); + let message = "Click to verify the code"; + if (!canVerify) { + message = "Please enter the verification code to verify"; + } + return (
Pending} actions={
- 0} - size="small" - > - Resend{countdown > 0 ? ` ${countdown}s` : ""} - - - Start verify - + + 0} + size="small" + > + Resend{countdown > 0 ? ` ${countdown}s` : ""} + + + + + Verify code + +
} /> From 7832c50e4c6301b76195ecbbaaa3b49bb5354300 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Tue, 13 Jan 2026 23:23:47 +0800 Subject: [PATCH 21/41] fix: should subscribe to people chain identity if it has a people chain (#7102) * Remove track select field from stable spend template on hydration chain, #7100 (#7101) * fix: should subscribe to people chain identity if it has a people chain * fix: useIdentityApi * Add error log * Update --------- Co-authored-by: wolyshaw --- .../index.js | 8 ++--- packages/next-common/context/api/index.js | 18 ++++++++--- packages/next-common/context/people/api.js | 16 +++------- .../hooks/people/useSubMyIdentityInfo.js | 6 ++-- .../hooks/useAddressVestingData.js | 4 +++ .../next-common/hooks/useFetchIdentityInfo.js | 10 +++--- packages/next-common/hooks/useIdentityApi.js | 31 +++++++++++++++++++ packages/next-common/hooks/useIdentityInfo.js | 20 +++++++++--- packages/next-common/utils/chain.js | 20 ++++++------ .../next/pages/people/identities/index.jsx | 30 +++--------------- .../next/pages/people/registrars/index.jsx | 30 +++--------------- 11 files changed, 96 insertions(+), 97 deletions(-) create mode 100644 packages/next-common/hooks/useIdentityApi.js diff --git a/packages/next-common/components/summary/newProposalQuickStart/createHOLLARTreasuryReferendumInnerPopupContent/index.js b/packages/next-common/components/summary/newProposalQuickStart/createHOLLARTreasuryReferendumInnerPopupContent/index.js index 38914ec9a2..0f2e22f2b7 100644 --- a/packages/next-common/components/summary/newProposalQuickStart/createHOLLARTreasuryReferendumInnerPopupContent/index.js +++ b/packages/next-common/components/summary/newProposalQuickStart/createHOLLARTreasuryReferendumInnerPopupContent/index.js @@ -16,7 +16,6 @@ import { useState } from "react"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; import AmountInputWithHint from "next-common/components/popup/fields/amountInputWithHint"; import { TreasuryProvider } from "next-common/context/treasury"; -import useTrackField from "../common/useTrackField"; const treasurerTrackId = 5; @@ -29,10 +28,8 @@ function NewHOLLARTreasuryReferendumInnerPopupContentImpl() { const { data: currencyInfo, loading } = useHydrationCurrencyInfo( STABLE_CURRENCY.id, ); - const { value: trackId, component: trackField } = - useTrackField(treasurerTrackId); const { value: enactment, component: enactmentField } = - useEnactmentBlocksField(trackId); + useEnactmentBlocksField(treasurerTrackId); const { encodedHash, encodedLength, notePreimageTx } = useHydrationTreasurySpendPreimageTx( @@ -42,7 +39,7 @@ function NewHOLLARTreasuryReferendumInnerPopupContentImpl() { ); const { isLoading, component: submitButton } = useCreateProposalSubmitButton({ - trackId, + trackId: treasurerTrackId, enactment, encodedHash, encodedLength, @@ -75,7 +72,6 @@ function NewHOLLARTreasuryReferendumInnerPopupContentImpl() { setInputAmount={setInputBalance} />
{beneficiaryField}
- {trackField} {enactmentField} diff --git a/packages/next-common/context/api/index.js b/packages/next-common/context/api/index.js index 781d346187..dc5c69e82b 100644 --- a/packages/next-common/context/api/index.js +++ b/packages/next-common/context/api/index.js @@ -20,11 +20,19 @@ export default function ApiProvider({ children }) { return; } - getOriginApi(chain, currentEndpoint).then((api) => { - if (isMounted()) { - setNowApi(api); - } - }); + getOriginApi(chain, currentEndpoint) + .then((api) => { + if (isMounted()) { + setNowApi(api); + } + }) + .catch((error) => { + console.error("Failed to getOriginApi", { + chain, + currentEndpoint, + error, + }); + }); }, [currentEndpoint, dispatch, endpoints, chain, isMounted]); return {children}; diff --git a/packages/next-common/context/people/api.js b/packages/next-common/context/people/api.js index 27e052d4aa..e6c62a9b6e 100644 --- a/packages/next-common/context/people/api.js +++ b/packages/next-common/context/people/api.js @@ -1,10 +1,4 @@ -import { - isCollectivesChain, - isKusamaChain, - isPolkadotChain, - isWestendChain, - isPaseoChain, -} from "next-common/utils/chain"; +import { getRelayChain } from "next-common/utils/chain"; import { createContext, useContext, useEffect, useMemo, useState } from "react"; import { useChain } from "../chain"; import getChainSettings from "next-common/utils/consts/settings"; @@ -50,19 +44,19 @@ export function usePeopleApi() { } export function getPeopleChain(chain) { - if (isPolkadotChain(chain) || isCollectivesChain(chain)) { + if (getRelayChain(chain) === Chains.polkadot) { return Chains.polkadotPeople; } - if (isKusamaChain(chain)) { + if (getRelayChain(chain) === Chains.kusama) { return Chains.kusamaPeople; } - if (isWestendChain(chain)) { + if (getRelayChain(chain) === Chains.westend) { return Chains.westendPeople; } - if (isPaseoChain(chain)) { + if (getRelayChain(chain) === Chains.paseo) { return Chains.paseoPeople; } diff --git a/packages/next-common/hooks/people/useSubMyIdentityInfo.js b/packages/next-common/hooks/people/useSubMyIdentityInfo.js index d57705b12e..934e7803fc 100644 --- a/packages/next-common/hooks/people/useSubMyIdentityInfo.js +++ b/packages/next-common/hooks/people/useSubMyIdentityInfo.js @@ -1,12 +1,12 @@ import { useEffect, useState } from "react"; -import { useContextApi } from "next-common/context/api"; +import { useIdentityApi } from "../useIdentityApi"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; import { useIdentityOf } from "next-common/hooks/identity/useIdentityOf"; import { fetchIdentityOf } from "../identity/identityFetch"; function useSuperOfIdentityDisplayName(identity) { const address = useRealAddress(); - const api = useContextApi(); + const api = useIdentityApi(); const [subDisplay, setSubDisplay] = useState(null); useEffect(() => { @@ -61,7 +61,7 @@ function useSuperOfIdentityDisplayName(identity) { } export default function useSubMyIdentityInfo() { - const api = useContextApi(); + const api = useIdentityApi(); const address = useRealAddress(); const { info, judgements, isLoading } = useIdentityOf(api, address); const { result: superResult } = useSuperOfIdentityDisplayName(info); diff --git a/packages/next-common/hooks/useAddressVestingData.js b/packages/next-common/hooks/useAddressVestingData.js index f103972e9a..3186d269f7 100644 --- a/packages/next-common/hooks/useAddressVestingData.js +++ b/packages/next-common/hooks/useAddressVestingData.js @@ -19,6 +19,10 @@ export default function useAddressVestingData(address) { return; } + if (!api.query?.vesting?.vesting) { + return; + } + try { if (!silent) { setIsLoading(true); diff --git a/packages/next-common/hooks/useFetchIdentityInfo.js b/packages/next-common/hooks/useFetchIdentityInfo.js index 45247be4ba..11f6467956 100644 --- a/packages/next-common/hooks/useFetchIdentityInfo.js +++ b/packages/next-common/hooks/useFetchIdentityInfo.js @@ -4,7 +4,7 @@ import { backendApi } from "next-common/services/nextApi"; import { useAsync } from "react-use"; import { isPolkadotChain, isKusamaChain } from "next-common/utils/chain"; -function useIdentityApi(address = "") { +function useIdentityApiUrl(address = "") { const chain = useChain(); return useMemo(() => { @@ -22,17 +22,17 @@ function useIdentityApi(address = "") { } export default function useFetchIdentityInfo(address = "") { - const identityApi = useIdentityApi(address); + const identityApiUrl = useIdentityApiUrl(address); const [isLoading, setIsLoading] = useState(true); const { value } = useAsync(async () => { setIsLoading(true); - if (!identityApi) { + if (!identityApiUrl) { return {}; } try { - const resp = await backendApi.fetch(identityApi); + const resp = await backendApi.fetch(identityApiUrl); const { subs = [], info } = resp?.result || {}; return { @@ -44,7 +44,7 @@ export default function useFetchIdentityInfo(address = "") { } finally { setIsLoading(false); } - }, [identityApi]); + }, [identityApiUrl]); return { data: value, diff --git a/packages/next-common/hooks/useIdentityApi.js b/packages/next-common/hooks/useIdentityApi.js new file mode 100644 index 0000000000..cfa0b111de --- /dev/null +++ b/packages/next-common/hooks/useIdentityApi.js @@ -0,0 +1,31 @@ +import { useEffect, useState } from "react"; +import { useContextApi } from "next-common/context/api"; +import { getPeopleChain } from "next-common/context/people/api"; +import { useChain } from "next-common/context/chain"; +import { getChainApi } from "next-common/utils/getChainApi"; +import getChainSettings from "next-common/utils/consts/settings"; +import { isPeopleChain } from "next-common/utils/chain"; + +export function useIdentityApi() { + const chain = useChain(); + const defaultApi = useContextApi(); + const [peopleApi, setPeopleApi] = useState(null); + const peopleChain = getPeopleChain(chain); + + useEffect(() => { + if (!peopleChain || isPeopleChain(chain)) { + return; + } + const endpointUrls = getChainSettings(peopleChain)?.endpoints?.map( + (item) => item.url, + ); + if (endpointUrls?.length > 0) { + getChainApi(endpointUrls).then(setPeopleApi); + } + }, [peopleChain, chain]); + + if (!peopleChain || isPeopleChain(chain)) { + return defaultApi; + } + return peopleApi; +} diff --git a/packages/next-common/hooks/useIdentityInfo.js b/packages/next-common/hooks/useIdentityInfo.js index 883cd70e99..345181c90f 100644 --- a/packages/next-common/hooks/useIdentityInfo.js +++ b/packages/next-common/hooks/useIdentityInfo.js @@ -8,7 +8,6 @@ import { getCachedBountyIdentity, } from "next-common/services/identity"; import getChainSettings from "next-common/utils/consts/settings"; -import { isPeopleChain } from "next-common/utils/chain"; import { cloneDeep } from "lodash-es"; import { isNil } from "lodash-es"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; @@ -20,8 +19,10 @@ const emptyIdentityInfo = {}; export function useChainAddressIdentityInfo(chain, address, realAddress = "") { const { identity: identityChain } = getChainSettings(chain); + const identityContextData = useIdentityInfoContext(); const { displayName, info: myIdentityInfo = emptyIdentityInfo } = - useIdentityInfoContext() || {}; + identityContextData || {}; + const hasIdentityContext = !isNil(identityContextData); // Render the identity immediately if it's already in cache const encodedAddress = encodeAddressToChain(address, identityChain); @@ -44,7 +45,7 @@ export function useChainAddressIdentityInfo(chain, address, realAddress = "") { fetchIdentity(identityChain, encodeAddressToChain(address, identityChain)) .then((identity) => { - if (!isPeopleChain(chain) || !isSameAddress(realAddress, address)) { + if (!hasIdentityContext || !isSameAddress(realAddress, address)) { resolvedIdentity = identity; setIdentity(identity); return; @@ -100,11 +101,20 @@ export function useChainAddressIdentityInfo(chain, address, realAddress = "") { setIsLoading(false); }); - }, [address, identityChain, myIdentityInfo, displayName, chain, realAddress]); + }, [ + address, + identityChain, + hasIdentityContext, + myIdentityInfo, + displayName, + chain, + realAddress, + ]); return { identity, - hasIdentity: identity && identity?.info?.status !== "NO_ID", + hasIdentity: + !isNil(identity?.info?.status) && identity.info.status !== "NO_ID", isLoading, }; } diff --git a/packages/next-common/utils/chain.js b/packages/next-common/utils/chain.js index 30613d94cb..9e2ed477de 100644 --- a/packages/next-common/utils/chain.js +++ b/packages/next-common/utils/chain.js @@ -154,24 +154,24 @@ export function getRelayChain(chain) { return chain; } else if (isPolkadotAssetHubChain(chain)) { return Chains.polkadot; - } else if (isKusamaAssetHubChain(chain)) { - return Chains.kusama; - } else if (isWestendAssetHubChain(chain)) { - return Chains.westend; - } else if (isPaseoAssetHubChain(chain)) { - return Chains.paseo; } else if (isCollectivesChain(chain)) { return Chains.polkadot; } else if (isPolkadotPeopleChain(chain)) { return Chains.polkadot; + } else if (isHyperBridgeChain(chain)) { + return Chains.polkadot; + } else if (isKusamaAssetHubChain(chain)) { + return Chains.kusama; } else if (isKusamaPeopleChain(chain)) { return Chains.kusama; - } else if (isPaseoPeopleChain(chain)) { - return Chains.paseo; + } else if (isWestendAssetHubChain(chain)) { + return Chains.westend; } else if (isWestendPeopleChain(chain)) { return Chains.westend; - } else if (isHyperBridgeChain(chain)) { - return Chains.polkadot; + } else if (isPaseoAssetHubChain(chain)) { + return Chains.paseo; + } else if (isPaseoPeopleChain(chain)) { + return Chains.paseo; } throw new Error("Unsupported relay chain"); diff --git a/packages/next/pages/people/identities/index.jsx b/packages/next/pages/people/identities/index.jsx index 09c3290a8e..a484a85017 100644 --- a/packages/next/pages/people/identities/index.jsx +++ b/packages/next/pages/people/identities/index.jsx @@ -1,13 +1,8 @@ import { withCommonProps } from "next-common/lib"; -import { createStore } from "next-common/store"; -import ChainProvider from "next-common/context/chain"; -import ApiProvider from "next-common/context/api"; -import { Provider } from "react-redux"; -import { commonReducers } from "next-common/store/reducers"; import { CHAIN } from "next-common/utils/constants"; import getChainSettings from "next-common/utils/consts/settings"; -import RelayInfoProvider from "next-common/context/relayInfo"; import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; +import { PeopleGlobalProvider } from ".."; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; @@ -15,32 +10,15 @@ const PeopleIdentitiesPageImpl = dynamicClientOnly(() => import("next-common/components/people/identities"), ); -let chain; -let store; - -if (isPeopleSupported) { - chain = `${CHAIN}-people`; - store = createStore({ - chain, - reducer: commonReducers, - }); -} - export default function PeopleIdentitiesPage() { if (!isPeopleSupported) { return null; } return ( - - - - - - - - - + + + ); } diff --git a/packages/next/pages/people/registrars/index.jsx b/packages/next/pages/people/registrars/index.jsx index b9a41ab888..b1f13f4759 100644 --- a/packages/next/pages/people/registrars/index.jsx +++ b/packages/next/pages/people/registrars/index.jsx @@ -1,13 +1,8 @@ import { withCommonProps } from "next-common/lib"; -import { createStore } from "next-common/store"; -import ChainProvider from "next-common/context/chain"; -import ApiProvider from "next-common/context/api"; -import { Provider } from "react-redux"; -import { commonReducers } from "next-common/store/reducers"; import { CHAIN } from "next-common/utils/constants"; import getChainSettings from "next-common/utils/consts/settings"; -import RelayInfoProvider from "next-common/context/relayInfo"; import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; +import { PeopleGlobalProvider } from ".."; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; @@ -15,32 +10,15 @@ const PeopleRegistrarsPageImpl = dynamicClientOnly(() => import("next-common/components/people/registrars"), ); -let chain; -let store; - -if (isPeopleSupported) { - chain = `${CHAIN}-people`; - store = createStore({ - chain, - reducer: commonReducers, - }); -} - export default function PeopleRegistrarsPage() { if (!isPeopleSupported) { return null; } return ( - - - - - - - - - + + + ); } From 6845fb5bb35a0ba4f309ce5d9a2e1693ef6f7c3f Mon Sep 17 00:00:00 2001 From: chaojun Date: Wed, 14 Jan 2026 10:47:00 +0800 Subject: [PATCH 22/41] fix: missing api param on fetchIdentityOf call, #6881 --- packages/next-common/hooks/people/useSubMyIdentityInfo.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next-common/hooks/people/useSubMyIdentityInfo.js b/packages/next-common/hooks/people/useSubMyIdentityInfo.js index 934e7803fc..db69ca3c1f 100644 --- a/packages/next-common/hooks/people/useSubMyIdentityInfo.js +++ b/packages/next-common/hooks/people/useSubMyIdentityInfo.js @@ -34,6 +34,7 @@ function useSuperOfIdentityDisplayName(identity) { } const identityResult = await fetchIdentityOf( + api, superOfResult.parentAddress, ).then((res) => res.info); From fd0c054be352d4d9e6ffb0f899a17c24d147941e Mon Sep 17 00:00:00 2001 From: chaojun Date: Wed, 14 Jan 2026 16:54:15 +0800 Subject: [PATCH 23/41] fix: add people check on Page, #6881 --- packages/next/pages/people/judgement/index.jsx | 4 ++++ packages/next/pages/people/usernames/index.jsx | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/packages/next/pages/people/judgement/index.jsx b/packages/next/pages/people/judgement/index.jsx index 478ea43d62..dbc6f94e2e 100644 --- a/packages/next/pages/people/judgement/index.jsx +++ b/packages/next/pages/people/judgement/index.jsx @@ -11,6 +11,10 @@ const PeopleOverviewPageImpl = dynamicClientOnly(() => ); export default function PeoplePage() { + if (!isPeopleSupported) { + return null; + } + return ( diff --git a/packages/next/pages/people/usernames/index.jsx b/packages/next/pages/people/usernames/index.jsx index bccd0685f8..37431de54e 100644 --- a/packages/next/pages/people/usernames/index.jsx +++ b/packages/next/pages/people/usernames/index.jsx @@ -1,12 +1,20 @@ import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; import { PeopleGlobalProvider } from ".."; +import getChainSettings from "next-common/utils/consts/settings"; +import { CHAIN } from "next-common/utils/constants"; export { getServerSideProps } from "../index"; const PeopleUsernamesPageImpl = dynamicClientOnly(() => import("next-common/components/people/usernames"), ); +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; + export default function PeopleUsernamesPage() { + if (!isPeopleSupported) { + return null; + } + return ( From d12319497d73e42e08bf40c15c988b9701c85654 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Thu, 15 Jan 2026 09:55:16 +0800 Subject: [PATCH 24/41] Identity judgement overview, #6881 (#7108) * Identity judgement overview prompt , #6881 * fix, #6881 * Update should prompt judgement request, #6881 * Refactor judgement request handling by removing unused hook and simplifying loading checks, #6881 --- .../components/useSetIdentityPrompt.js | 91 ++++++++++++++++--- .../people/hooks/useMyJudgementRequest.js | 2 +- .../components/people/judgement/content.jsx | 4 +- .../overview/identity/checkJudgement.jsx | 2 +- .../context/people/identityInfoContext.js | 2 +- .../next-common/utils/consts/menu/people.jsx | 7 ++ 6 files changed, 88 insertions(+), 20 deletions(-) diff --git a/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js b/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js index 16ce2922b3..9bc612ba06 100644 --- a/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js +++ b/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js @@ -5,15 +5,79 @@ import { } from "next-common/components/scrollPrompt"; import { CACHE_KEY } from "next-common/utils/constants"; import { useCookieValue } from "next-common/utils/hooks/useCookieValue"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { useChainSettings } from "next-common/context/chain"; import { useRouter } from "next/router"; import useIdentityInfo from "next-common/hooks/useIdentityInfo"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; import { isEmpty } from "lodash-es"; +import useMyJudgementRequest from "next-common/components/people/hooks/useMyJudgementRequest"; +import RequestJudgementPopup from "next-common/components/requestJudgementPopup"; +import { useIdentityInfoContext } from "next-common/context/people/identityInfoContext"; const identityPage = "/people"; -const judgementPage = "/people?tab=judgements"; + +function RequestJudgementPromptContent() { + const [showPopup, setShowPopup] = useState(false); + + return ( +
+ Your on-chain identity is not verified yet.  + setShowPopup(true)} + > + Request judgement + + {showPopup && ( + setShowPopup(false)} /> + )} +
+ ); +} + +function NavigateToJudgementPagePrompt() { + return ( +
+ Actions is required to verify your identity social accounts.  + + Go to Judgement page + + . +
+ ); +} + +function analyzeJudgements(judgements) { + return { + hasPending: judgements.some(({ status }) => ["FeePaid"].includes(status)), + hasPositive: judgements.some(({ status }) => + ["KnownGood", "Reasonable"].includes(status), + ), + hasOutdated: judgements.some(({ status }) => status === "OutOfDate"), + hasNegative: judgements.some(({ status }) => + ["LowQuality", "Erroneous"].includes(status), + ), + }; +} + +function useShouldPromptJudgementRequest() { + const { judgements } = useIdentityInfoContext(); + + if (!judgements || judgements.length === 0) { + return true; + } + + const { hasPending, hasPositive, hasNegative } = + analyzeJudgements(judgements); + + if (hasPending || hasPositive || hasNegative) { + return false; + } + + return true; +} export default function useSetIdentityPrompt() { const router = useRouter(); @@ -25,7 +89,6 @@ export default function useSetIdentityPrompt() { const supportedPeople = modules?.people; const isPeoplePage = pathName?.startsWith(identityPage); - const isJudgementPage = router.asPath?.startsWith(judgementPage); const isNotVerified = useMemo(() => { return identity?.info?.status === "NOT_VERIFIED"; @@ -41,22 +104,21 @@ export default function useSetIdentityPrompt() { const [visible, setVisible] = useCookieValue(cacheKey, true); + const { value: myJudgementRequest } = useMyJudgementRequest(); + const shouldRequestJudgement = useShouldPromptJudgementRequest(); + return useMemo(() => { if (!visible || !supportedPeople) { return {}; } let message; - if (hasIdentity && isNotVerified && !isJudgementPage) { - message = ( -
- Your on-chain identity is not verified yet. Request judgements  - - here - - . -
- ); + if (hasIdentity && isNotVerified) { + if (shouldRequestJudgement) { + message = ; + } else if (myJudgementRequest) { + message = ; + } } else if (!hasIdentity && !isPeoplePage) { message = (
@@ -82,12 +144,13 @@ export default function useSetIdentityPrompt() { }, [ cacheKey, hasIdentity, - isJudgementPage, isNotVerified, isPeoplePage, setVisible, supportedPeople, visible, + shouldRequestJudgement, + myJudgementRequest, ]); } export function IdentityPrompt({ onClose }) { diff --git a/packages/next-common/components/people/hooks/useMyJudgementRequest.js b/packages/next-common/components/people/hooks/useMyJudgementRequest.js index ad51fa2ee5..c412916b7b 100644 --- a/packages/next-common/components/people/hooks/useMyJudgementRequest.js +++ b/packages/next-common/components/people/hooks/useMyJudgementRequest.js @@ -14,7 +14,7 @@ export default function useMyJudgementRequest() { `people/judgement/requests/pending?who=${realAddress}`, ); if (result) { - return result; + return result.items[0] || null; } return null; diff --git a/packages/next-common/components/people/judgement/content.jsx b/packages/next-common/components/people/judgement/content.jsx index a076d43112..f98cddb66d 100644 --- a/packages/next-common/components/people/judgement/content.jsx +++ b/packages/next-common/components/people/judgement/content.jsx @@ -8,11 +8,9 @@ import JudgementSummary from "./summary"; import Twitter from "./twitter"; export default function JudgementPageContent() { - const { value, loading: isLoadingMyJudgementRequest } = + const { value: request, loading: isLoadingMyJudgementRequest } = useMyJudgementRequest(); - const request = value?.items[0] || null; - return ( <> - + Request Judgement {showPopup && } diff --git a/packages/next-common/context/people/identityInfoContext.js b/packages/next-common/context/people/identityInfoContext.js index 6fdb97cabd..759107824e 100644 --- a/packages/next-common/context/people/identityInfoContext.js +++ b/packages/next-common/context/people/identityInfoContext.js @@ -16,5 +16,5 @@ export default function IdentityInfoProvider({ children }) { } export function useIdentityInfoContext() { - return useContext(IdentityInfoContext); + return useContext(IdentityInfoContext) || {}; } diff --git a/packages/next-common/utils/consts/menu/people.jsx b/packages/next-common/utils/consts/menu/people.jsx index cf6e21c62b..82eae3eec2 100644 --- a/packages/next-common/utils/consts/menu/people.jsx +++ b/packages/next-common/utils/consts/menu/people.jsx @@ -5,6 +5,7 @@ import { MenuData, MenuRegistrars, InfoUsers, + // MenuJudgements, } from "@osn/icons/subsquare"; import { NAV_MENU_TYPE } from "next-common/utils/constants"; @@ -39,6 +40,12 @@ export const peopleMenu = { pathname: "/people/registrars", icon: , }, + // { + // name: "Judgement", + // value: "judgement", + // pathname: "/people/judgement", + // icon: , + // }, { type: "divider", }, From 7682bf3e5c18324dfac6a6239e8e7c219b78503d Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Thu, 15 Jan 2026 11:28:07 +0800 Subject: [PATCH 25/41] Refactor identity prompts, #6881 (#7109) * Identity judgement overview prompt , #6881 * fix, #6881 * Update should prompt judgement request, #6881 * Refactor judgement request handling by removing unused hook and simplifying loading checks, #6881 * Refactor identity prompts, #6881 --- .../navigateToJudgementPagePromptItem.js | 69 +++++++ .../components/requestJudgementPromptItem.js | 109 +++++++++++ .../components/setIdentityPromptItem.js | 79 ++++++++ .../components/useSetIdentityPrompt.js | 185 ++---------------- packages/next-common/utils/constants.js | 1 + 5 files changed, 276 insertions(+), 167 deletions(-) create mode 100644 packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js create mode 100644 packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js create mode 100644 packages/next-common/components/overview/accountInfo/components/setIdentityPromptItem.js diff --git a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js new file mode 100644 index 0000000000..1ed92a25ed --- /dev/null +++ b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js @@ -0,0 +1,69 @@ +import Link from "next-common/components/link"; +import { + PromptTypes, + ScrollPromptItemWrapper, +} from "next-common/components/scrollPrompt"; +import { CACHE_KEY } from "next-common/utils/constants"; +import { useCookieValue } from "next-common/utils/hooks/useCookieValue"; +import { useMemo } from "react"; +import useMyJudgementRequest from "next-common/components/people/hooks/useMyJudgementRequest"; + +function NavigateToJudgementPagePrompt() { + return ( +
+ Actions is required to verify your identity social accounts.  + + Go to Judgement page + + . +
+ ); +} + +function useNavigateToJudgementPagePromptItem() { + const { value: myJudgementRequest } = useMyJudgementRequest(); + + const needAction = + myJudgementRequest && + Object.values(myJudgementRequest.verification).some( + (verified) => verified !== true, + ); + + const [visible, setVisible] = useCookieValue( + CACHE_KEY.navigateToJudgementPagePrompt, + true, + ); + + return useMemo(() => { + if (!visible || !needAction) { + return {}; + } + + return { + key: CACHE_KEY.navigateToJudgementPagePrompt, + message: , + type: PromptTypes.INFO, + close: () => setVisible(false, { expires: 15 }), + }; + }, [needAction, setVisible, visible]); +} + +export default function NavigateToJudgementPagePromptItem({ onClose }) { + const prompt = useNavigateToJudgementPagePromptItem(); + + if (!prompt?.message) { + return null; + } + + return ( + { + onClose?.(); + prompt?.close?.(); + }, + }} + /> + ); +} diff --git a/packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js b/packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js new file mode 100644 index 0000000000..289a2a220b --- /dev/null +++ b/packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js @@ -0,0 +1,109 @@ +import { + PromptTypes, + ScrollPromptItemWrapper, +} from "next-common/components/scrollPrompt"; +import { CACHE_KEY } from "next-common/utils/constants"; +import { useCookieValue } from "next-common/utils/hooks/useCookieValue"; +import { useMemo, useState } from "react"; +import { useIdentityInfoContext } from "next-common/context/people/identityInfoContext"; +import RequestJudgementPopup from "next-common/components/requestJudgementPopup"; + +function analyzeJudgements(judgements) { + return { + hasPending: judgements.some(({ status }) => ["FeePaid"].includes(status)), + hasPositive: judgements.some(({ status }) => + ["KnownGood", "Reasonable"].includes(status), + ), + hasOutdated: judgements.some(({ status }) => status === "OutOfDate"), + hasNegative: judgements.some(({ status }) => + ["LowQuality", "Erroneous"].includes(status), + ), + }; +} + +function useShouldPromptJudgementRequest() { + const { info, judgements, isLoading, displayName } = useIdentityInfoContext(); + + if (isLoading) { + return false; + } + + if (!displayName && Object.values(info || {}).every((v) => !v)) { + return false; + } + + if (!judgements || judgements.length === 0) { + return true; + } + + const { hasPending, hasPositive, hasNegative } = + analyzeJudgements(judgements); + + if (hasPending || hasPositive || hasNegative) { + return false; + } + + return true; +} + +function RequestJudgementPromptContent() { + const [showPopup, setShowPopup] = useState(false); + + return ( +
+ Your on-chain identity is not verified yet.  + setShowPopup(true)} + > + Request judgement + + {showPopup && ( + setShowPopup(false)} /> + )} +
+ ); +} + +function useRequestJudgementPromptItem() { + const shouldRequestJudgement = useShouldPromptJudgementRequest(); + + const [visible, setVisible] = useCookieValue( + CACHE_KEY.requestJudgementPrompt, + true, + ); + + return useMemo(() => { + if (!visible || !shouldRequestJudgement) { + return {}; + } + + return { + key: CACHE_KEY.requestJudgementPrompt, + message: , + type: PromptTypes.INFO, + close: () => setVisible(false, { expires: 15 }), + }; + }, [setVisible, shouldRequestJudgement, visible]); +} + +export default function RequestJudgementPromptItem({ onClose }) { + const prompt = useRequestJudgementPromptItem(); + + if (!prompt?.message) { + return null; + } + + return ( + { + onClose?.(); + prompt?.close?.(); + }, + }} + /> + ); +} diff --git a/packages/next-common/components/overview/accountInfo/components/setIdentityPromptItem.js b/packages/next-common/components/overview/accountInfo/components/setIdentityPromptItem.js new file mode 100644 index 0000000000..8b023be27e --- /dev/null +++ b/packages/next-common/components/overview/accountInfo/components/setIdentityPromptItem.js @@ -0,0 +1,79 @@ +import Link from "next-common/components/link"; +import { + PromptTypes, + ScrollPromptItemWrapper, +} from "next-common/components/scrollPrompt"; +import { CACHE_KEY } from "next-common/utils/constants"; +import { useCookieValue } from "next-common/utils/hooks/useCookieValue"; +import { useMemo } from "react"; +import { useChainSettings } from "next-common/context/chain"; +import { useRouter } from "next/router"; +import useIdentityInfo from "next-common/hooks/useIdentityInfo"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; + +const identityPage = "/people"; + +function useSetIdentityPromptItem() { + const router = useRouter(); + const chainSettings = useChainSettings(); + const address = useRealAddress(); + const { hasIdentity, identity } = useIdentityInfo(address); + + const pathName = router.pathname; + const { modules } = chainSettings; + const supportedPeople = modules?.people; + + const isPeoplePage = pathName?.startsWith(identityPage); + + const isNotVerified = useMemo(() => { + return identity?.info?.status === "NOT_VERIFIED"; + }, [identity?.info?.status]); + + const shouldShow = !hasIdentity && !isPeoplePage; + + const [visible, setVisible] = useCookieValue( + CACHE_KEY.setIdentityPromptVisible, + true, + ); + + return useMemo(() => { + if (!visible || !supportedPeople || !shouldShow || isNotVerified) { + return {}; + } + + return { + key: CACHE_KEY.setIdentityPromptVisible, + message: ( +
+ Set your on-chain identity  + + here + + . +
+ ), + type: PromptTypes.INFO, + close: () => setVisible(false, { expires: 15 }), + }; + }, [isNotVerified, setVisible, shouldShow, supportedPeople, visible]); +} + +export default function SetIdentityPromptItem({ onClose }) { + const prompt = useSetIdentityPromptItem(); + + if (!prompt?.message) { + return null; + } + + return ( + { + onClose?.(); + prompt?.close?.(); + }, + }} + /> + ); +} diff --git a/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js b/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js index 9bc612ba06..39af2d4544 100644 --- a/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js +++ b/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js @@ -1,172 +1,23 @@ -import Link from "next-common/components/link"; -import { - PromptTypes, - ScrollPromptItemWrapper, -} from "next-common/components/scrollPrompt"; -import { CACHE_KEY } from "next-common/utils/constants"; -import { useCookieValue } from "next-common/utils/hooks/useCookieValue"; -import { useMemo, useState } from "react"; -import { useChainSettings } from "next-common/context/chain"; -import { useRouter } from "next/router"; -import useIdentityInfo from "next-common/hooks/useIdentityInfo"; -import useRealAddress from "next-common/utils/hooks/useRealAddress"; -import { isEmpty } from "lodash-es"; -import useMyJudgementRequest from "next-common/components/people/hooks/useMyJudgementRequest"; -import RequestJudgementPopup from "next-common/components/requestJudgementPopup"; -import { useIdentityInfoContext } from "next-common/context/people/identityInfoContext"; +import SetIdentityPromptItem from "./setIdentityPromptItem"; +import RequestJudgementPromptItem from "./requestJudgementPromptItem"; +import NavigateToJudgementPagePromptItem from "./navigateToJudgementPagePromptItem"; +import { useChain } from "next-common/context/chain"; +import { isPeopleChain } from "next-common/utils/chain"; + +export { + SetIdentityPromptItem, + RequestJudgementPromptItem, + NavigateToJudgementPagePromptItem, +}; -const identityPage = "/people"; - -function RequestJudgementPromptContent() { - const [showPopup, setShowPopup] = useState(false); - - return ( -
- Your on-chain identity is not verified yet.  - setShowPopup(true)} - > - Request judgement - - {showPopup && ( - setShowPopup(false)} /> - )} -
- ); -} - -function NavigateToJudgementPagePrompt() { - return ( -
- Actions is required to verify your identity social accounts.  - - Go to Judgement page - - . -
- ); -} - -function analyzeJudgements(judgements) { - return { - hasPending: judgements.some(({ status }) => ["FeePaid"].includes(status)), - hasPositive: judgements.some(({ status }) => - ["KnownGood", "Reasonable"].includes(status), - ), - hasOutdated: judgements.some(({ status }) => status === "OutOfDate"), - hasNegative: judgements.some(({ status }) => - ["LowQuality", "Erroneous"].includes(status), - ), - }; -} - -function useShouldPromptJudgementRequest() { - const { judgements } = useIdentityInfoContext(); - - if (!judgements || judgements.length === 0) { - return true; - } - - const { hasPending, hasPositive, hasNegative } = - analyzeJudgements(judgements); - - if (hasPending || hasPositive || hasNegative) { - return false; - } - - return true; -} - -export default function useSetIdentityPrompt() { - const router = useRouter(); - const chainSettings = useChainSettings(); - const address = useRealAddress(); - const { hasIdentity, identity } = useIdentityInfo(address); - const pathName = router.pathname; - const { modules } = chainSettings; - const supportedPeople = modules?.people; - - const isPeoplePage = pathName?.startsWith(identityPage); - - const isNotVerified = useMemo(() => { - return identity?.info?.status === "NOT_VERIFIED"; - }, [identity?.info?.status]); - - const cacheKey = useMemo( - () => - isNotVerified - ? CACHE_KEY.requestJudgementPrompt - : CACHE_KEY.setIdentityPromptVisible, - [isNotVerified], - ); - - const [visible, setVisible] = useCookieValue(cacheKey, true); - - const { value: myJudgementRequest } = useMyJudgementRequest(); - const shouldRequestJudgement = useShouldPromptJudgementRequest(); - - return useMemo(() => { - if (!visible || !supportedPeople) { - return {}; - } - - let message; - if (hasIdentity && isNotVerified) { - if (shouldRequestJudgement) { - message = ; - } else if (myJudgementRequest) { - message = ; - } - } else if (!hasIdentity && !isPeoplePage) { - message = ( -
- Set your on-chain identity  - - here - - . -
- ); - } - - if (message) { - return { - key: cacheKey, - message, - type: PromptTypes.INFO, - close: () => setVisible(false, { expires: 15 }), - }; - } - - return {}; - }, [ - cacheKey, - hasIdentity, - isNotVerified, - isPeoplePage, - setVisible, - supportedPeople, - visible, - shouldRequestJudgement, - myJudgementRequest, - ]); -} export function IdentityPrompt({ onClose }) { - const prompt = useSetIdentityPrompt(); - if (isEmpty(prompt)) { - return null; - } + const chain = useChain(); + return ( - { - onClose?.(); - prompt?.close(); - }, - }} - /> + <> + + + {isPeopleChain(chain) && } + ); } diff --git a/packages/next-common/utils/constants.js b/packages/next-common/utils/constants.js index 6b4b69bde1..d7cf40f816 100644 --- a/packages/next-common/utils/constants.js +++ b/packages/next-common/utils/constants.js @@ -137,6 +137,7 @@ export const CACHE_KEY = { assetsPromptVisible: "assets-management-prompt-visible", multisigManagementPromptVisible: "multisig-management-prompt-visible", requestJudgementPrompt: "request-judgement-prompt", + navigateToJudgementPagePrompt: "navigate-to-judgement-page-prompt", walletConnectSession: "walletconnect-session", totalRequestingAssets: "total-requesting-assets", dvApplyPromptVisible: "dv-apply-prompt-visible", From c92b0d126f4b530675755168e8ae031b0ac7559c Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Thu, 15 Jan 2026 16:09:39 +0800 Subject: [PATCH 26/41] Add judgement requests admin page, #6881 (#7111) * Add judgement requests page entry menu, #6881 * Remove unused people menu, #6881 * refactor, #6881 --- .../hooks/usePendingJudgementRequests.js | 16 ++ .../people/judgementRequests/index.jsx | 20 ++ .../people/judgementRequests/list.jsx | 177 ++++++++++++++++++ packages/next-common/context/nav/index.jsx | 12 +- packages/next-common/hooks/useMainMenuData.js | 5 + .../next-common/utils/consts/menu/index.js | 7 +- .../next-common/utils/consts/menu/people.jsx | 101 +++++----- .../next/pages/people/identities/index.jsx | 4 - .../pages/people/judgement-requests/index.jsx | 52 +++++ .../next/pages/people/judgement/index.jsx | 4 - .../next/pages/people/registrars/index.jsx | 4 - .../next/pages/people/usernames/index.jsx | 8 - 12 files changed, 336 insertions(+), 74 deletions(-) create mode 100644 packages/next-common/components/people/hooks/usePendingJudgementRequests.js create mode 100644 packages/next-common/components/people/judgementRequests/index.jsx create mode 100644 packages/next-common/components/people/judgementRequests/list.jsx create mode 100644 packages/next/pages/people/judgement-requests/index.jsx diff --git a/packages/next-common/components/people/hooks/usePendingJudgementRequests.js b/packages/next-common/components/people/hooks/usePendingJudgementRequests.js new file mode 100644 index 0000000000..9018df754e --- /dev/null +++ b/packages/next-common/components/people/hooks/usePendingJudgementRequests.js @@ -0,0 +1,16 @@ +import { backendApi } from "next-common/services/nextApi"; +import { useAsync } from "react-use"; + +export default function usePendingJudgementRequests(page, pageSize) { + return useAsync(async () => { + if (!page || page < 1 || !pageSize || pageSize < 1) { + return null; + } + + const { result } = await backendApi.fetch( + `people/judgement/requests/pending?page=${page}&pageSize=${pageSize}`, + ); + + return result || null; + }, [page, pageSize]); +} diff --git a/packages/next-common/components/people/judgementRequests/index.jsx b/packages/next-common/components/people/judgementRequests/index.jsx new file mode 100644 index 0000000000..f8848f72f1 --- /dev/null +++ b/packages/next-common/components/people/judgementRequests/index.jsx @@ -0,0 +1,20 @@ +import ListLayout from "next-common/components/layout/ListLayout"; +import ChainSocialLinks from "next-common/components/chain/socialLinks"; +import PeopleCommonProvider from "next-common/components/people/common/commonProvider"; +import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; +import JudgementRequestsList from "./list"; + +export default function JudgementRequestsPage() { + return ( + + } + > + + + + ); +} diff --git a/packages/next-common/components/people/judgementRequests/list.jsx b/packages/next-common/components/people/judgementRequests/list.jsx new file mode 100644 index 0000000000..b82f92e7fb --- /dev/null +++ b/packages/next-common/components/people/judgementRequests/list.jsx @@ -0,0 +1,177 @@ +import { SystemVoteAye } from "@osn/icons/subsquare"; +import { useEffect, useMemo, useState } from "react"; + +import { SecondaryCard } from "next-common/components/styled/containers/secondaryCard"; +import ScrollerX from "next-common/components/styled/containers/scrollerX"; +import { MapDataList } from "next-common/components/dataList"; +import ListTitleBar from "next-common/components/listTitleBar"; +import Tooltip from "next-common/components/tooltip"; +import usePaginationComponent from "next-common/components/pagination/usePaginationComponent"; +import { AddressUser } from "next-common/components/user"; + +import usePendingJudgementRequests from "next-common/components/people/hooks/usePendingJudgementRequests"; +import { PeopleSocialType } from "next-common/components/people/judgement/consts"; + +const PAGE_SIZE = 20; + +function getSocialInfoKey(type) { + return type === PeopleSocialType.element ? "matrix" : type; +} + +function getConfiguredSocialAccounts(info = {}) { + const allTypes = Object.values(PeopleSocialType); + return allTypes + .map((type) => { + const infoKey = getSocialInfoKey(type); + const value = info?.[infoKey]; + if (!value) { + return null; + } + return { type, value }; + }) + .filter(Boolean); +} + +function getSocialCounts(info = {}, verification = {}) { + const configured = getConfiguredSocialAccounts(info); + const total = configured.length; + const verified = configured.filter( + ({ type }) => verification?.[type] === true, + ).length; + return { verified, total }; +} + +function SocialAccountsTooltipContent({ judgementRequest }) { + const info = judgementRequest?.info || {}; + const verification = judgementRequest?.verification || {}; + const configured = getConfiguredSocialAccounts(info); + + if (!configured.length) { + return
No social accounts set
; + } + + return ( +
+ {configured.map(({ type, value }) => ( +
+ + {type} + + {value} + + {verification?.[type] === true && ( + + )} + +
+ ))} +
+ ); +} + +function SocialAccounts({ judgementRequest }) { + const { verified, total } = getSocialCounts( + judgementRequest?.info, + judgementRequest?.verification, + ); + + return ( + + } + > + + {verified} + / + {total} + + + ); +} + +function AllVerified({ judgementRequest }) { + const { verified, total } = getSocialCounts( + judgementRequest?.info, + judgementRequest?.verification, + ); + + const allVerified = total > 0 && verified === total; + return allVerified ? ( + + ) : ( + - + ); +} + +export default function JudgementRequestsList() { + const [total, setTotal] = useState(0); + const { page, component: pagination } = usePaginationComponent( + total, + PAGE_SIZE, + { + buttonMode: true, + }, + ); + + const { value: pageData, loading } = usePendingJudgementRequests( + page, + PAGE_SIZE, + ); + + useEffect(() => { + setTotal(pageData?.total || 0); + }, [pageData?.total]); + + const items = pageData?.items || []; + + const columnsDef = useMemo( + () => [ + { + name: "Applicant", + style: { textAlign: "left", minWidth: "240px" }, + render: (judgementRequest) => ( + + ), + }, + { + name: "Social Accounts", + style: { textAlign: "left", width: "220px", minWidth: "220px" }, + render: (judgementRequest) => ( + + ), + }, + { + name: "All Verified", + style: { textAlign: "left", width: "120px", minWidth: "120px" }, + render: (judgementRequest) => ( + + ), + }, + ], + [], + ); + + return ( +
+
+ +
+ + + + + `${item?.who || ""}-${item?.indexer?.blockHeight || ""}` + } + /> + + {pagination} + +
+ ); +} diff --git a/packages/next-common/context/nav/index.jsx b/packages/next-common/context/nav/index.jsx index 1448a92a88..90ab90b262 100644 --- a/packages/next-common/context/nav/index.jsx +++ b/packages/next-common/context/nav/index.jsx @@ -4,6 +4,8 @@ import { useCookieValue } from "next-common/utils/hooks/useCookieValue"; import { createContext, useContext, useMemo, useState } from "react"; import { useIsomorphicLayoutEffect } from "react-use"; import { matchNewMenu } from "next-common/utils/consts/menu"; +import { useRouter } from "next/router"; +import useIsAdmin from "next-common/hooks/useIsAdmin"; const NavCollapsedContext = createContext([]); const NavSubmenuVisibleContext = createContext([]); @@ -29,6 +31,7 @@ export default function NavProvider({ export function useNavSubmenuVisible() { return useContext(NavSubmenuVisibleContext); } + function NavCollapsedProvider({ children, value }) { try { value = JSON.parse(value); @@ -50,6 +53,7 @@ function NavCollapsedProvider({ children, value }) { export function useNavCollapsed() { return useContext(NavCollapsedContext); } + function NavSubmenuVisibleProvider({ children, value }) { try { value = JSON.parse(decodeURIComponent(value)); @@ -71,14 +75,15 @@ function NavSubmenuVisibleProvider({ children, value }) { ); } -const menu = getMainMenu(); export function useNavMenuType() { return useContext(NavMenuTypeContext); } -import { useRouter } from "next/router"; function NavMenuTypeProvider({ children }) { const router = useRouter(); + const isAdmin = useIsAdmin(); + const menu = useMemo(() => getMainMenu({ isAdmin }), [isAdmin]); + const matchMenu = useMemo(() => { return ( matchNewMenu(menu, router.pathname) || { @@ -86,7 +91,8 @@ function NavMenuTypeProvider({ children }) { menu: null, } ); - }, [router.pathname]); + }, [router.pathname, menu]); + const [navMenuType, setNavMenuType] = useState(matchMenu); useIsomorphicLayoutEffect(() => { diff --git a/packages/next-common/hooks/useMainMenuData.js b/packages/next-common/hooks/useMainMenuData.js index e76c4b9c32..ca605962f6 100644 --- a/packages/next-common/hooks/useMainMenuData.js +++ b/packages/next-common/hooks/useMainMenuData.js @@ -2,11 +2,14 @@ import { usePageProps } from "next-common/context/page"; import { getMainMenu } from "next-common/utils/consts/menu"; import { useUser } from "next-common/context/user"; import { useMemo } from "react"; +import useIsAdmin from "./useIsAdmin"; export default function useMainMenuData() { const user = useUser(); const { tracks, fellowshipTracks, summary, detail, ambassadorTracks } = usePageProps(); + const isAdmin = useIsAdmin(); + return useMemo(() => { return getMainMenu({ tracks, @@ -14,6 +17,7 @@ export default function useMainMenuData() { ambassadorTracks, summary, currentTrackId: detail?.track, + isAdmin, }).filter((item) => { if (item.value === "account") { // not connect wallet @@ -28,5 +32,6 @@ export default function useMainMenuData() { summary, tracks, user?.address, + isAdmin, ]); } diff --git a/packages/next-common/utils/consts/menu/index.js b/packages/next-common/utils/consts/menu/index.js index c77329fbd4..6e8fd8b437 100644 --- a/packages/next-common/utils/consts/menu/index.js +++ b/packages/next-common/utils/consts/menu/index.js @@ -17,7 +17,7 @@ import { getCommunityTreasuryMenu } from "./communityTreasury"; import getChainSettings from "../settings"; import getArchivedMenu from "./archived"; import { coretimeMenu } from "./coretime"; -import { peopleMenu } from "./people"; +import { getPeopleMenu } from "./people"; import { stakingMenu } from "./staking"; import whitelist from "./whitelist"; import Data from "./data"; @@ -36,6 +36,7 @@ export function getHomeMenu({ summary = {}, ambassadorTracks = [], currentTrackId, + isAdmin = false, } = {}) { const { modules, hasMultisig = false } = getChainSettings(CHAIN); @@ -56,7 +57,7 @@ export function getHomeMenu({ modules?.alliance && getAllianceMenu(summary), modules?.communityCouncil && getCommunityCouncilMenu(summary), modules?.staking && stakingMenu, - modules?.people && peopleMenu, + modules?.people && getPeopleMenu(isAdmin), modules?.coretime && coretimeMenu, getAdvancedMenu( [ @@ -90,6 +91,7 @@ export function getMainMenu({ fellowshipTracks = [], ambassadorTracks = [], currentTrackId, + isAdmin = false, } = {}) { const { hotMenu = {} } = getChainSettings(CHAIN); @@ -104,6 +106,7 @@ export function getMainMenu({ fellowshipTracks, ambassadorTracks, currentTrackId, + isAdmin, }); const activeModulesMenu = []; diff --git a/packages/next-common/utils/consts/menu/people.jsx b/packages/next-common/utils/consts/menu/people.jsx index 82eae3eec2..191fd62db7 100644 --- a/packages/next-common/utils/consts/menu/people.jsx +++ b/packages/next-common/utils/consts/menu/people.jsx @@ -5,55 +5,58 @@ import { MenuData, MenuRegistrars, InfoUsers, - // MenuJudgements, + MenuJudgements, } from "@osn/icons/subsquare"; import { NAV_MENU_TYPE } from "next-common/utils/constants"; -export const peopleMenu = { - name: "People", - value: "people", - pathname: "/people", - icon: , - extra: , - type: NAV_MENU_TYPE.subspace, - items: [ - { - name: "Overview", - value: "overview", - pathname: "/people", - icon: , - extraMatchNavMenuActivePathnames: [ - "/people/judgement", - "/people/judgement/auth/discord", - "/people/judgement/auth/twitter", - ], - }, - { - name: "Identities", - value: "identities", - pathname: "/people/identities", - icon: , - }, - { - name: "Registrars", - value: "registrars", - pathname: "/people/registrars", - icon: , - }, - // { - // name: "Judgement", - // value: "judgement", - // pathname: "/people/judgement", - // icon: , - // }, - { - type: "divider", - }, - { - name: "Usernames", - value: "usernames", - pathname: "/people/usernames", - icon: , - }, - ], -}; +export function getPeopleMenu(isAdmin) { + return { + name: "People", + value: "people", + pathname: "/people", + icon: , + extra: , + type: NAV_MENU_TYPE.subspace, + items: [ + { + name: "Overview", + value: "overview", + pathname: "/people", + icon: , + extraMatchNavMenuActivePathnames: [ + "/people/judgement", + "/people/judgement/auth/discord", + "/people/judgement/auth/twitter", + ], + }, + { + name: "Identities", + value: "identities", + pathname: "/people/identities", + icon: , + }, + { + name: "Registrars", + value: "registrars", + pathname: "/people/registrars", + icon: , + }, + isAdmin && { + name: "Judgement Requests", + value: "judgement-requests", + pathname: "/people/judgement-requests", + icon: , + adminOnly: true, + }, + { + type: "divider", + }, + { + name: "Usernames", + value: "usernames", + pathname: "/people/usernames", + icon: , + }, + ].filter(Boolean), + }; +} diff --git a/packages/next/pages/people/identities/index.jsx b/packages/next/pages/people/identities/index.jsx index a484a85017..6bc45d3745 100644 --- a/packages/next/pages/people/identities/index.jsx +++ b/packages/next/pages/people/identities/index.jsx @@ -11,10 +11,6 @@ const PeopleIdentitiesPageImpl = dynamicClientOnly(() => ); export default function PeopleIdentitiesPage() { - if (!isPeopleSupported) { - return null; - } - return ( diff --git a/packages/next/pages/people/judgement-requests/index.jsx b/packages/next/pages/people/judgement-requests/index.jsx new file mode 100644 index 0000000000..74f9e866e8 --- /dev/null +++ b/packages/next/pages/people/judgement-requests/index.jsx @@ -0,0 +1,52 @@ +import { withCommonProps } from "next-common/lib"; +import { CHAIN } from "next-common/utils/constants"; +import getChainSettings from "next-common/utils/consts/settings"; +import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; +import { PeopleGlobalProvider } from ".."; +import useIsAdmin from "next-common/hooks/useIsAdmin"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; + +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; + +const JudgementRequestsPageImpl = dynamicClientOnly(() => + import("next-common/components/people/judgementRequests"), +); + +export default function PeopleJudgementRequestsPage() { + const router = useRouter(); + const isAdmin = useIsAdmin(); + + useEffect(() => { + if (!isAdmin) { + const timer = setTimeout(() => { + router.replace("/people"); + }, 3000); + return () => clearTimeout(timer); + } + }, [isAdmin, router]); + + if (!isAdmin) { + return
Only admins can access this page. redirecting...
; + } + + return ( + + + + ); +} + +export const getServerSideProps = async (ctx) => { + if (!isPeopleSupported) { + return { + notFound: true, + }; + } + + return withCommonProps(async () => { + return { + props: {}, + }; + })(ctx); +}; diff --git a/packages/next/pages/people/judgement/index.jsx b/packages/next/pages/people/judgement/index.jsx index dbc6f94e2e..478ea43d62 100644 --- a/packages/next/pages/people/judgement/index.jsx +++ b/packages/next/pages/people/judgement/index.jsx @@ -11,10 +11,6 @@ const PeopleOverviewPageImpl = dynamicClientOnly(() => ); export default function PeoplePage() { - if (!isPeopleSupported) { - return null; - } - return ( diff --git a/packages/next/pages/people/registrars/index.jsx b/packages/next/pages/people/registrars/index.jsx index b1f13f4759..d434eeb513 100644 --- a/packages/next/pages/people/registrars/index.jsx +++ b/packages/next/pages/people/registrars/index.jsx @@ -11,10 +11,6 @@ const PeopleRegistrarsPageImpl = dynamicClientOnly(() => ); export default function PeopleRegistrarsPage() { - if (!isPeopleSupported) { - return null; - } - return ( diff --git a/packages/next/pages/people/usernames/index.jsx b/packages/next/pages/people/usernames/index.jsx index 37431de54e..bccd0685f8 100644 --- a/packages/next/pages/people/usernames/index.jsx +++ b/packages/next/pages/people/usernames/index.jsx @@ -1,20 +1,12 @@ import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; import { PeopleGlobalProvider } from ".."; -import getChainSettings from "next-common/utils/consts/settings"; -import { CHAIN } from "next-common/utils/constants"; export { getServerSideProps } from "../index"; const PeopleUsernamesPageImpl = dynamicClientOnly(() => import("next-common/components/people/usernames"), ); -const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; - export default function PeopleUsernamesPage() { - if (!isPeopleSupported) { - return null; - } - return ( From 9934ba8a8f10f61c4158dd2f8d8f0b9e8c8d8a99 Mon Sep 17 00:00:00 2001 From: chaojun Date: Wed, 21 Jan 2026 12:49:22 +0800 Subject: [PATCH 27/41] Use new api to get user's judgement request, #6881 --- .../components/people/hooks/useMyJudgementRequest.js | 4 ++-- .../judgement/element/hooks/useFinishElementVerification.js | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/next-common/components/people/hooks/useMyJudgementRequest.js b/packages/next-common/components/people/hooks/useMyJudgementRequest.js index c412916b7b..608034312f 100644 --- a/packages/next-common/components/people/hooks/useMyJudgementRequest.js +++ b/packages/next-common/components/people/hooks/useMyJudgementRequest.js @@ -11,10 +11,10 @@ export default function useMyJudgementRequest() { } const { result } = await backendApi.fetch( - `people/judgement/requests/pending?who=${realAddress}`, + `people/judgement/requests/${realAddress}/pending`, ); if (result) { - return result.items[0] || null; + return result || null; } return null; diff --git a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js index eebd643dde..9a7738db0a 100644 --- a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js +++ b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js @@ -21,8 +21,7 @@ export default function useFinishElementVerification({ setVerifying(true); try { const { result, error: startError } = await nextApi.fetch( - "people/judgement/requests/pending", - { who }, + `people/judgement/requests/${who}/pending`, ); if (startError) { const message = startError.message || "Failed to start verification"; @@ -31,7 +30,7 @@ export default function useFinishElementVerification({ return; } - const request = result.items[0]; + const request = result || null; const isElementVerified = request?.verification.element === true; if (isElementVerified) { From 82aeeb330601a6c8e426f536a639f634e5d8b3bc Mon Sep 17 00:00:00 2001 From: chaojun Date: Wed, 21 Jan 2026 14:32:57 +0800 Subject: [PATCH 28/41] Improve API url, #6881 --- .../components/people/hooks/useMyJudgementRequest.js | 2 +- .../components/people/hooks/usePendingJudgementRequests.js | 2 +- .../judgement/element/hooks/useFinishElementVerification.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next-common/components/people/hooks/useMyJudgementRequest.js b/packages/next-common/components/people/hooks/useMyJudgementRequest.js index 608034312f..ac57350bff 100644 --- a/packages/next-common/components/people/hooks/useMyJudgementRequest.js +++ b/packages/next-common/components/people/hooks/useMyJudgementRequest.js @@ -11,7 +11,7 @@ export default function useMyJudgementRequest() { } const { result } = await backendApi.fetch( - `people/judgement/requests/${realAddress}/pending`, + `/people/identities/${realAddress}/active-request`, ); if (result) { return result || null; diff --git a/packages/next-common/components/people/hooks/usePendingJudgementRequests.js b/packages/next-common/components/people/hooks/usePendingJudgementRequests.js index 9018df754e..0eb625536e 100644 --- a/packages/next-common/components/people/hooks/usePendingJudgementRequests.js +++ b/packages/next-common/components/people/hooks/usePendingJudgementRequests.js @@ -8,7 +8,7 @@ export default function usePendingJudgementRequests(page, pageSize) { } const { result } = await backendApi.fetch( - `people/judgement/requests/pending?page=${page}&pageSize=${pageSize}`, + `people/identities/active-requests?page=${page}&pageSize=${pageSize}`, ); return result || null; diff --git a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js index 9a7738db0a..381ff1be04 100644 --- a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js +++ b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js @@ -21,7 +21,7 @@ export default function useFinishElementVerification({ setVerifying(true); try { const { result, error: startError } = await nextApi.fetch( - `people/judgement/requests/${who}/pending`, + `/people/identities/${who}/active-request`, ); if (startError) { const message = startError.message || "Failed to start verification"; From e468800dd28eb1c91e1cad983e039517a19c91fd Mon Sep 17 00:00:00 2001 From: chaojun Date: Thu, 22 Jan 2026 09:50:59 +0800 Subject: [PATCH 29/41] fix --- .../components/people/hooks/useMyJudgementRequest.js | 2 +- .../judgement/element/hooks/useFinishElementVerification.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next-common/components/people/hooks/useMyJudgementRequest.js b/packages/next-common/components/people/hooks/useMyJudgementRequest.js index ac57350bff..e8400538ab 100644 --- a/packages/next-common/components/people/hooks/useMyJudgementRequest.js +++ b/packages/next-common/components/people/hooks/useMyJudgementRequest.js @@ -11,7 +11,7 @@ export default function useMyJudgementRequest() { } const { result } = await backendApi.fetch( - `/people/identities/${realAddress}/active-request`, + `people/identities/${realAddress}/active-request`, ); if (result) { return result || null; diff --git a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js index 381ff1be04..124894b84f 100644 --- a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js +++ b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js @@ -21,7 +21,7 @@ export default function useFinishElementVerification({ setVerifying(true); try { const { result, error: startError } = await nextApi.fetch( - `/people/identities/${who}/active-request`, + `people/identities/${who}/active-request`, ); if (startError) { const message = startError.message || "Failed to start verification"; From b3713f56c9f3f6803f8844008154f9e8449164a7 Mon Sep 17 00:00:00 2001 From: chaojun Date: Thu, 22 Jan 2026 16:14:48 +0800 Subject: [PATCH 30/41] Rename verification to verifications, #6881 --- .../components/people/judgement/discord.jsx | 2 +- .../element/hooks/useFinishElementVerification.js | 2 +- .../components/people/judgement/element/index.jsx | 2 +- .../people/judgement/element/pendingElementCard.jsx | 4 ++-- .../components/people/judgement/email/index.jsx | 2 +- .../components/people/judgement/github.jsx | 2 +- .../components/people/judgement/summary.js | 4 ++-- .../components/people/judgement/twitter.jsx | 2 +- .../components/people/judgementRequests/list.jsx | 12 ++++++------ 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index 36681a321e..2355953244 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -4,7 +4,7 @@ import PeopleJudgementSocialConnect from "./socialConnect"; import { PeopleSocialType } from "./consts"; export default function Discord({ request }) { - const isVerified = request?.verification?.discord === true; + const isVerified = request?.verifications?.discord === true; const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.discord, authUrlPath: "people/judgement/auth/discord/auth-url", diff --git a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js index 124894b84f..c6fcc06331 100644 --- a/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js +++ b/packages/next-common/components/people/judgement/element/hooks/useFinishElementVerification.js @@ -32,7 +32,7 @@ export default function useFinishElementVerification({ const request = result || null; - const isElementVerified = request?.verification.element === true; + const isElementVerified = request?.verifications.element === true; if (isElementVerified) { onVerified?.(); } else { diff --git a/packages/next-common/components/people/judgement/element/index.jsx b/packages/next-common/components/people/judgement/element/index.jsx index f0a6554efc..c0a26481be 100644 --- a/packages/next-common/components/people/judgement/element/index.jsx +++ b/packages/next-common/components/people/judgement/element/index.jsx @@ -4,7 +4,7 @@ import VerifiedElementCard from "./verifiedElementCard"; export default function Element({ request }) { const elementAccount = request?.info?.matrix || ""; - const initialVerified = request?.verification?.element === true; + const initialVerified = request?.verifications?.element === true; const [verified, setVerified] = useState(initialVerified); useEffect(() => { diff --git a/packages/next-common/components/people/judgement/element/pendingElementCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementCard.jsx index 79b2c4e259..447edcbbcc 100644 --- a/packages/next-common/components/people/judgement/element/pendingElementCard.jsx +++ b/packages/next-common/components/people/judgement/element/pendingElementCard.jsx @@ -7,8 +7,8 @@ export default function PendingElementCard({ elementAccount, onVerified, }) { - const isVerifying = request?.verification?.element?.verifying === true; - const verifyingCode = request?.verification?.element?.code || ""; + const isVerifying = request?.verifications?.element?.verifying === true; + const verifyingCode = request?.verifications?.element?.code || ""; const [code, setCode] = useState(verifyingCode); const [hasStarted, setHasStarted] = useState(isVerifying); diff --git a/packages/next-common/components/people/judgement/email/index.jsx b/packages/next-common/components/people/judgement/email/index.jsx index 38d7aae84b..929413357e 100644 --- a/packages/next-common/components/people/judgement/email/index.jsx +++ b/packages/next-common/components/people/judgement/email/index.jsx @@ -4,7 +4,7 @@ import VerifiedEmailCard from "./verifiedEmailCard"; export default function Email({ request }) { const email = request?.info?.email || ""; - const initialVerified = request?.verification?.email === true; + const initialVerified = request?.verifications?.email === true; const [verified, setVerified] = useState(initialVerified); useEffect(() => { diff --git a/packages/next-common/components/people/judgement/github.jsx b/packages/next-common/components/people/judgement/github.jsx index f4e7765e1c..c69eb47e76 100644 --- a/packages/next-common/components/people/judgement/github.jsx +++ b/packages/next-common/components/people/judgement/github.jsx @@ -4,7 +4,7 @@ import PeopleJudgementSocialConnect from "./socialConnect"; import { PeopleSocialType } from "./consts"; export default function Github({ request }) { - const isVerified = request?.verification?.github === true; + const isVerified = request?.verifications?.github === true; const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.github, diff --git a/packages/next-common/components/people/judgement/summary.js b/packages/next-common/components/people/judgement/summary.js index 64e3d32dd8..1c6517dc4d 100644 --- a/packages/next-common/components/people/judgement/summary.js +++ b/packages/next-common/components/people/judgement/summary.js @@ -11,14 +11,14 @@ export default function JudgementSummary({ request, loading }) { const allSocialTypes = Object.values(PeopleSocialType); const info = request?.info || {}; - const verification = request?.verification || {}; + const verifications = request?.verifications || {}; const totalSocials = allSocialTypes.filter((key) => Boolean(info[key === "element" ? "matrix" : key]), ).length; const verifiedSocials = allSocialTypes.filter( (key) => Boolean(info[key === "element" ? "matrix" : key]) && - verification?.[key] === true, + verifications?.[key] === true, ).length; const pendingSocials = totalSocials - verifiedSocials; diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index bfe7f2360d..b88bc508bf 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -4,7 +4,7 @@ import PeopleJudgementSocialConnect from "./socialConnect"; import { PeopleSocialType } from "./consts"; export default function Twitter({ request }) { - const isVerified = request?.verification?.twitter === true; + const isVerified = request?.verifications?.twitter === true; const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.twitter, diff --git a/packages/next-common/components/people/judgementRequests/list.jsx b/packages/next-common/components/people/judgementRequests/list.jsx index b82f92e7fb..59b478e46b 100644 --- a/packages/next-common/components/people/judgementRequests/list.jsx +++ b/packages/next-common/components/people/judgementRequests/list.jsx @@ -32,18 +32,18 @@ function getConfiguredSocialAccounts(info = {}) { .filter(Boolean); } -function getSocialCounts(info = {}, verification = {}) { +function getSocialCounts(info = {}, verifications = {}) { const configured = getConfiguredSocialAccounts(info); const total = configured.length; const verified = configured.filter( - ({ type }) => verification?.[type] === true, + ({ type }) => verifications?.[type] === true, ).length; return { verified, total }; } function SocialAccountsTooltipContent({ judgementRequest }) { const info = judgementRequest?.info || {}; - const verification = judgementRequest?.verification || {}; + const verifications = judgementRequest?.verifications || {}; const configured = getConfiguredSocialAccounts(info); if (!configured.length) { @@ -59,7 +59,7 @@ function SocialAccountsTooltipContent({ judgementRequest }) { {value} - {verification?.[type] === true && ( + {verifications?.[type] === true && ( )} @@ -72,7 +72,7 @@ function SocialAccountsTooltipContent({ judgementRequest }) { function SocialAccounts({ judgementRequest }) { const { verified, total } = getSocialCounts( judgementRequest?.info, - judgementRequest?.verification, + judgementRequest?.verifications, ); return ( @@ -93,7 +93,7 @@ function SocialAccounts({ judgementRequest }) { function AllVerified({ judgementRequest }) { const { verified, total } = getSocialCounts( judgementRequest?.info, - judgementRequest?.verification, + judgementRequest?.verifications, ); const allVerified = total > 0 && verified === total; From f2d4166516559db77e24ba77127538fdec066403 Mon Sep 17 00:00:00 2001 From: chaojun Date: Thu, 22 Jan 2026 16:17:40 +0800 Subject: [PATCH 31/41] Rename auth routes, #6881 --- packages/next-common/components/people/judgement/discord.jsx | 4 ++-- .../judgement/element/hooks/useStartElementVerification.js | 2 +- .../people/judgement/email/hooks/useSendJudgementEmailCode.js | 2 +- .../judgement/email/hooks/useVerifyJudgementEmailCode.js | 2 +- packages/next-common/components/people/judgement/github.jsx | 4 ++-- packages/next-common/components/people/judgement/twitter.jsx | 4 ++-- packages/next-common/utils/consts/menu/people.jsx | 4 ++-- packages/next/pages/people/judgement/auth/discord.jsx | 2 +- packages/next/pages/people/judgement/auth/github.jsx | 2 +- packages/next/pages/people/judgement/auth/twitter.jsx | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index 2355953244..faa802b18c 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -7,8 +7,8 @@ export default function Discord({ request }) { const isVerified = request?.verifications?.discord === true; const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.discord, - authUrlPath: "people/judgement/auth/discord/auth-url", - redirectPath: "/people/judgement/auth/discord", + authUrlPath: "people/verifications/auth/discord/auth-url", + redirectPath: "/people/verifications/auth/discord", isVerified, }); diff --git a/packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js b/packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js index 5b6018fe1c..e357c99c59 100644 --- a/packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js +++ b/packages/next-common/components/people/judgement/element/hooks/useStartElementVerification.js @@ -30,7 +30,7 @@ export default function useStartElementVerification({ } const { result, error: startError } = await nextApi.post( - "people/judgement/auth/element/start", + "people/verifications/auth/element/start", { who }, ); if (startError) { diff --git a/packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js b/packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js index c7807c4a29..e371aeca40 100644 --- a/packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js +++ b/packages/next-common/components/people/judgement/email/hooks/useSendJudgementEmailCode.js @@ -26,7 +26,7 @@ export default function useSendJudgementEmailCode({ who, setError, onSent }) { } const { error: sendError } = await nextApi.post( - "people/judgement/auth/email/send-code", + "people/verifications/auth/email/send-code", { who }, ); if (sendError) { diff --git a/packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js b/packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js index 9f0fc57b0f..9e963f4aa7 100644 --- a/packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js +++ b/packages/next-common/components/people/judgement/email/hooks/useVerifyJudgementEmailCode.js @@ -33,7 +33,7 @@ export default function useVerifyJudgementEmailCode({ } const { error: verifyError } = await nextApi.post( - "people/judgement/auth/email/verify-code", + "people/verifications/auth/email/verify-code", { who, code }, ); if (verifyError) { diff --git a/packages/next-common/components/people/judgement/github.jsx b/packages/next-common/components/people/judgement/github.jsx index c69eb47e76..d6998d8fe1 100644 --- a/packages/next-common/components/people/judgement/github.jsx +++ b/packages/next-common/components/people/judgement/github.jsx @@ -8,8 +8,8 @@ export default function Github({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.github, - authUrlPath: "people/judgement/auth/github/auth-url", - redirectPath: "/people/judgement/auth/github", + authUrlPath: "people/verifications/auth/github/auth-url", + redirectPath: "/people/verifications/auth/github", isVerified, }); diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index b88bc508bf..ae6cf81ee6 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -8,8 +8,8 @@ export default function Twitter({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.twitter, - authUrlPath: "people/judgement/auth/twitter/auth-url", - redirectPath: "/people/judgement/auth/twitter", + authUrlPath: "people/verifications/auth/twitter/auth-url", + redirectPath: "/people/verifications/auth/twitter", isVerified, }); diff --git a/packages/next-common/utils/consts/menu/people.jsx b/packages/next-common/utils/consts/menu/people.jsx index 191fd62db7..b81ae73f54 100644 --- a/packages/next-common/utils/consts/menu/people.jsx +++ b/packages/next-common/utils/consts/menu/people.jsx @@ -25,8 +25,8 @@ export function getPeopleMenu(isAdmin) { icon: , extraMatchNavMenuActivePathnames: [ "/people/judgement", - "/people/judgement/auth/discord", - "/people/judgement/auth/twitter", + "/people/verifications/auth/discord", + "/people/verifications/auth/twitter", ], }, { diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/judgement/auth/discord.jsx index c22123441a..68b34b6d30 100644 --- a/packages/next/pages/people/judgement/auth/discord.jsx +++ b/packages/next/pages/people/judgement/auth/discord.jsx @@ -13,7 +13,7 @@ export default function Page({ code, state, error, errorDescription }) { Date: Thu, 22 Jan 2026 16:37:44 +0800 Subject: [PATCH 32/41] fix, #6881 --- .../accountInfo/components/navigateToJudgementPagePromptItem.js | 2 +- packages/next-common/components/people/judgement/discord.jsx | 2 +- packages/next-common/components/people/judgement/github.jsx | 2 +- packages/next-common/components/people/judgement/twitter.jsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js index 1ed92a25ed..a14cce1d18 100644 --- a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js +++ b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js @@ -25,7 +25,7 @@ function useNavigateToJudgementPagePromptItem() { const needAction = myJudgementRequest && - Object.values(myJudgementRequest.verification).some( + Object.values(myJudgementRequest.verifications).some( (verified) => verified !== true, ); diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index faa802b18c..47a7fac51f 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -8,7 +8,7 @@ export default function Discord({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.discord, authUrlPath: "people/verifications/auth/discord/auth-url", - redirectPath: "/people/verifications/auth/discord", + redirectPath: "/people/judgement/auth/discord", isVerified, }); diff --git a/packages/next-common/components/people/judgement/github.jsx b/packages/next-common/components/people/judgement/github.jsx index d6998d8fe1..62786776c6 100644 --- a/packages/next-common/components/people/judgement/github.jsx +++ b/packages/next-common/components/people/judgement/github.jsx @@ -9,7 +9,7 @@ export default function Github({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.github, authUrlPath: "people/verifications/auth/github/auth-url", - redirectPath: "/people/verifications/auth/github", + redirectPath: "/people/judgement/auth/github", isVerified, }); diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index ae6cf81ee6..5b787b3e8b 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -9,7 +9,7 @@ export default function Twitter({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.twitter, authUrlPath: "people/verifications/auth/twitter/auth-url", - redirectPath: "/people/verifications/auth/twitter", + redirectPath: "/people/judgement/auth/twitter", isVerified, }); From 1cfc49a44cd5591d8342afd5ba2e74eb54f8d4bf Mon Sep 17 00:00:00 2001 From: chaojun Date: Thu, 29 Jan 2026 09:48:50 +0800 Subject: [PATCH 33/41] Improve UI style and fix data refresh issue --- .../people/hooks/useMyJudgementRequest.js | 39 ++++++++++++++----- .../people/judgement/cardHeaderLayout.jsx | 15 +++++++ .../components/people/judgement/content.jsx | 15 +++---- .../components/people/judgement/context.js | 30 ++++++++++++++ .../components/people/judgement/discord.jsx | 2 +- ...ntAddressRow.jsx => elementAccountRow.jsx} | 4 +- .../judgement/element/elementCardHeader.jsx | 18 ++++----- .../people/judgement/element/index.jsx | 7 +++- .../element/pendingElementNotStartedCard.jsx | 6 +-- .../element/pendingElementStartedCard.jsx | 23 +++++++---- .../judgement/element/verifiedElementCard.jsx | 2 +- .../judgement/email/emailAddressRow.jsx | 6 ++- .../judgement/email/emailCardHeader.jsx | 18 ++++----- .../people/judgement/email/index.jsx | 7 +++- .../email/pendingEmailNotSentCard.jsx | 4 +- .../judgement/email/pendingEmailSentCard.jsx | 9 ++--- .../components/people/judgement/github.jsx | 2 +- .../hooks/usePeopleJudgementSocialAuth.js | 5 ++- .../components/people/judgement/index.jsx | 5 ++- .../people/judgement/socialConnect.jsx | 30 +++++--------- .../components/people/judgement/summary.js | 9 ----- .../components/people/judgement/twitter.jsx | 2 +- .../identity/directIdentityActions.jsx | 4 +- .../components/tags/state/styled.js | 4 ++ 24 files changed, 163 insertions(+), 103 deletions(-) create mode 100644 packages/next-common/components/people/judgement/cardHeaderLayout.jsx create mode 100644 packages/next-common/components/people/judgement/context.js rename packages/next-common/components/people/judgement/element/{elementAddressRow.jsx => elementAccountRow.jsx} (54%) diff --git a/packages/next-common/components/people/hooks/useMyJudgementRequest.js b/packages/next-common/components/people/hooks/useMyJudgementRequest.js index e8400538ab..ab0d1bdea9 100644 --- a/packages/next-common/components/people/hooks/useMyJudgementRequest.js +++ b/packages/next-common/components/people/hooks/useMyJudgementRequest.js @@ -1,22 +1,41 @@ import { backendApi } from "next-common/services/nextApi"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; -import { useAsync } from "react-use"; +import { useCallback, useEffect, useState } from "react"; export default function useMyJudgementRequest() { const realAddress = useRealAddress(); + const [value, setValue] = useState(null); + const [loading, setLoading] = useState(true); - return useAsync(async () => { + const fetch = useCallback(async () => { if (!realAddress) { - return null; + setValue(null); + setLoading(false); + return; } - const { result } = await backendApi.fetch( - `people/identities/${realAddress}/active-request`, - ); - if (result) { - return result || null; + setLoading(true); + try { + const { result } = await backendApi.fetch( + `people/identities/${realAddress}/active-request`, + ); + if (result) { + setValue(result); + return; + } + setValue(null); + } finally { + setLoading(false); } - - return null; }, [realAddress]); + + useEffect(() => { + fetch(); + }, [fetch]); + + return { + value, + loading, + fetch, + }; } diff --git a/packages/next-common/components/people/judgement/cardHeaderLayout.jsx b/packages/next-common/components/people/judgement/cardHeaderLayout.jsx new file mode 100644 index 0000000000..e325800da7 --- /dev/null +++ b/packages/next-common/components/people/judgement/cardHeaderLayout.jsx @@ -0,0 +1,15 @@ +export default function CardHeaderLayout({ tag, actions, Icon, title }) { + return ( +
+
+
+ + · + {title} +
+
{tag}
+
+ {actions} +
+ ); +} diff --git a/packages/next-common/components/people/judgement/content.jsx b/packages/next-common/components/people/judgement/content.jsx index f98cddb66d..5bfb2ad32a 100644 --- a/packages/next-common/components/people/judgement/content.jsx +++ b/packages/next-common/components/people/judgement/content.jsx @@ -1,24 +1,21 @@ import Loading from "next-common/components/loading"; -import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; import Discord from "./discord"; import Element from "./element"; import Email from "./email"; import Github from "./github"; import JudgementSummary from "./summary"; import Twitter from "./twitter"; +import { useJudgementContext } from "./context"; export default function JudgementPageContent() { - const { value: request, loading: isLoadingMyJudgementRequest } = - useMyJudgementRequest(); + const { myJudgementRequest: request, isLoadingMyJudgementRequest: loading } = + useJudgementContext(); return ( <> - -
- {isLoadingMyJudgementRequest && !request ? ( + +
+ {loading && !request ? (
diff --git a/packages/next-common/components/people/judgement/context.js b/packages/next-common/components/people/judgement/context.js new file mode 100644 index 0000000000..0a13052695 --- /dev/null +++ b/packages/next-common/components/people/judgement/context.js @@ -0,0 +1,30 @@ +import { createContext, useContext } from "react"; +import useMyJudgementRequest from "../hooks/useMyJudgementRequest"; + +const JudgementContext = createContext(null); + +export default JudgementContext; + +export function JudgementContextProvider({ children }) { + const { + value: myJudgementRequest, + loading: isLoadingMyJudgementRequest, + fetch: fetchMyJudgementRequest, + } = useMyJudgementRequest(); + + return ( + + {children} + + ); +} + +export function useJudgementContext() { + return useContext(JudgementContext); +} diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index 47a7fac51f..ed11c78899 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -14,7 +14,7 @@ export default function Discord({ request }) { return ( } + Icon={LinkDiscord} title="Discord" username={request?.info?.discord} connected={connected} diff --git a/packages/next-common/components/people/judgement/element/elementAddressRow.jsx b/packages/next-common/components/people/judgement/element/elementAccountRow.jsx similarity index 54% rename from packages/next-common/components/people/judgement/element/elementAddressRow.jsx rename to packages/next-common/components/people/judgement/element/elementAccountRow.jsx index 62635035e6..40c4937aeb 100644 --- a/packages/next-common/components/people/judgement/element/elementAddressRow.jsx +++ b/packages/next-common/components/people/judgement/element/elementAccountRow.jsx @@ -2,8 +2,8 @@ export default function ElementAccountRow({ elementAccount }) { return (
- Matrix ID: - {elementAccount} + Matrix ID: + {elementAccount}
); diff --git a/packages/next-common/components/people/judgement/element/elementCardHeader.jsx b/packages/next-common/components/people/judgement/element/elementCardHeader.jsx index 48450e0725..3da0c6d392 100644 --- a/packages/next-common/components/people/judgement/element/elementCardHeader.jsx +++ b/packages/next-common/components/people/judgement/element/elementCardHeader.jsx @@ -1,17 +1,13 @@ +import CardHeaderLayout from "../cardHeaderLayout"; import { LinkElement } from "@osn/icons/subsquare"; export default function ElementCardHeader({ tag, actions }) { return ( -
-
-
- - · -

Element

-
-
{tag}
-
- {actions} -
+ ); } diff --git a/packages/next-common/components/people/judgement/element/index.jsx b/packages/next-common/components/people/judgement/element/index.jsx index c0a26481be..df1d0bbd00 100644 --- a/packages/next-common/components/people/judgement/element/index.jsx +++ b/packages/next-common/components/people/judgement/element/index.jsx @@ -1,8 +1,10 @@ import { useEffect, useState } from "react"; import PendingElementCard from "./pendingElementCard"; import VerifiedElementCard from "./verifiedElementCard"; +import { useJudgementContext } from "../context"; export default function Element({ request }) { + const { fetchMyJudgementRequest } = useJudgementContext(); const elementAccount = request?.info?.matrix || ""; const initialVerified = request?.verifications?.element === true; const [verified, setVerified] = useState(initialVerified); @@ -19,7 +21,10 @@ export default function Element({ request }) { setVerified(true)} + onVerified={() => { + setVerified(true); + fetchMyJudgementRequest(); + }} /> ); } diff --git a/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx index 0de2a35723..e0bb5c3596 100644 --- a/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx +++ b/packages/next-common/components/people/judgement/element/pendingElementNotStartedCard.jsx @@ -1,8 +1,8 @@ import { useState } from "react"; -import { ClosedTag } from "next-common/components/tags/state/styled"; +import { WarningTag } from "next-common/components/tags/state/styled"; import SecondaryButton from "next-common/lib/button/secondary"; import ElementCardHeader from "./elementCardHeader"; -import ElementAccountRow from "./elementAddressRow"; +import ElementAccountRow from "./elementAccountRow"; import useStartElementVerification from "./hooks/useStartElementVerification"; import Tooltip from "next-common/components/tooltip"; @@ -23,7 +23,7 @@ export default function PendingElementNotStartedCard({ return (
Pending} + tag={Pending} actions={
diff --git a/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx b/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx index 93b8a04991..f96c85801d 100644 --- a/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx +++ b/packages/next-common/components/people/judgement/element/pendingElementStartedCard.jsx @@ -1,12 +1,13 @@ import { useState } from "react"; -import { ClosedTag } from "next-common/components/tags/state/styled"; -import PrimaryButton from "next-common/lib/button/primary"; -import Input from "next-common/lib/input"; +import { WarningTag } from "next-common/components/tags/state/styled"; +import SecondaryButton from "next-common/lib/button/secondary"; import Tooltip from "next-common/components/tooltip"; import ElementCardHeader from "./elementCardHeader"; -import ElementAddressRow from "./elementAddressRow"; +import ElementAddressRow from "./elementAccountRow"; import ElementVerificationTips from "./elementVerificationTips"; import useFinishElementVerification from "./hooks/useFinishElementVerification"; +import Copyable from "next-common/components/copyable"; +import { GreyPanel } from "next-common/components/styled/containers/greyPanel"; export default function PendingElementStartedCard({ request, @@ -29,13 +30,17 @@ export default function PendingElementStartedCard({ return (
Pending} + tag={Pending} actions={
- + Confirm finished - +
} @@ -48,7 +53,9 @@ export default function PendingElementStartedCard({
- + + {verificationCode} +
{error &&
{error}
}
diff --git a/packages/next-common/components/people/judgement/element/verifiedElementCard.jsx b/packages/next-common/components/people/judgement/element/verifiedElementCard.jsx index 374d91ad6c..3971e4c76f 100644 --- a/packages/next-common/components/people/judgement/element/verifiedElementCard.jsx +++ b/packages/next-common/components/people/judgement/element/verifiedElementCard.jsx @@ -1,6 +1,6 @@ import { PositiveTag } from "next-common/components/tags/state/styled"; import ElementCardHeader from "./elementCardHeader"; -import ElementAccountRow from "./elementAddressRow"; +import ElementAccountRow from "./elementAccountRow"; export default function VerifiedElementCard({ elementAccount }) { return ( diff --git a/packages/next-common/components/people/judgement/email/emailAddressRow.jsx b/packages/next-common/components/people/judgement/email/emailAddressRow.jsx index 812b15a3c0..89726276c3 100644 --- a/packages/next-common/components/people/judgement/email/emailAddressRow.jsx +++ b/packages/next-common/components/people/judgement/email/emailAddressRow.jsx @@ -2,8 +2,10 @@ export default function EmailAddressRow({ email }) { return (
- Email Address: - {email} + + Email Address: + + {email}
); diff --git a/packages/next-common/components/people/judgement/email/emailCardHeader.jsx b/packages/next-common/components/people/judgement/email/emailCardHeader.jsx index ac988a5a2f..b33632c1b5 100644 --- a/packages/next-common/components/people/judgement/email/emailCardHeader.jsx +++ b/packages/next-common/components/people/judgement/email/emailCardHeader.jsx @@ -1,17 +1,13 @@ +import CardHeaderLayout from "../cardHeaderLayout"; import { LinkEmail } from "@osn/icons/subsquare"; export default function EmailCardHeader({ tag, actions }) { return ( -
-
-
- - · -

Email

-
-
{tag}
-
- {actions} -
+ ); } diff --git a/packages/next-common/components/people/judgement/email/index.jsx b/packages/next-common/components/people/judgement/email/index.jsx index 929413357e..a3bcb67bf8 100644 --- a/packages/next-common/components/people/judgement/email/index.jsx +++ b/packages/next-common/components/people/judgement/email/index.jsx @@ -1,8 +1,10 @@ import { useEffect, useState } from "react"; import PendingEmailCard from "./pendingEmailCard"; import VerifiedEmailCard from "./verifiedEmailCard"; +import { useJudgementContext } from "../context"; export default function Email({ request }) { + const { fetchMyJudgementRequest } = useJudgementContext(); const email = request?.info?.email || ""; const initialVerified = request?.verifications?.email === true; const [verified, setVerified] = useState(initialVerified); @@ -19,7 +21,10 @@ export default function Email({ request }) { setVerified(true)} + onVerified={() => { + setVerified(true); + fetchMyJudgementRequest(); + }} /> ); } diff --git a/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx b/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx index f248d3d67c..d4305da993 100644 --- a/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx +++ b/packages/next-common/components/people/judgement/email/pendingEmailNotSentCard.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { ClosedTag } from "next-common/components/tags/state/styled"; +import { WarningTag } from "next-common/components/tags/state/styled"; import SecondaryButton from "next-common/lib/button/secondary"; import EmailCardHeader from "./emailCardHeader"; import EmailAddressRow from "./emailAddressRow"; @@ -19,7 +19,7 @@ export default function PendingEmailNotSentCard({ request, email, onSent }) { return (
Pending} + tag={Pending} actions={
diff --git a/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx b/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx index 257da29142..d72bbb756b 100644 --- a/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx +++ b/packages/next-common/components/people/judgement/email/pendingEmailSentCard.jsx @@ -1,6 +1,5 @@ import { useState } from "react"; -import { ClosedTag } from "next-common/components/tags/state/styled"; -import PrimaryButton from "next-common/lib/button/primary"; +import { WarningTag } from "next-common/components/tags/state/styled"; import SecondaryButton from "next-common/lib/button/secondary"; import Input from "next-common/lib/input"; import EmailCardHeader from "./emailCardHeader"; @@ -48,7 +47,7 @@ export default function PendingEmailSentCard({ return (
Pending} + tag={Pending} actions={
@@ -62,14 +61,14 @@ export default function PendingEmailSentCard({ - Verify code - +
} diff --git a/packages/next-common/components/people/judgement/github.jsx b/packages/next-common/components/people/judgement/github.jsx index 62786776c6..6b17facf19 100644 --- a/packages/next-common/components/people/judgement/github.jsx +++ b/packages/next-common/components/people/judgement/github.jsx @@ -15,7 +15,7 @@ export default function Github({ request }) { return ( } + Icon={LinkGithub} title="GitHub" username={request?.info?.github} connected={connected} diff --git a/packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js b/packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js index 575f1eef11..f3091f95cc 100644 --- a/packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js +++ b/packages/next-common/components/people/judgement/hooks/usePeopleJudgementSocialAuth.js @@ -3,6 +3,7 @@ import useRealAddress from "next-common/utils/hooks/useRealAddress"; import { trimEndSlash } from "next-common/utils/url"; import { useCallback, useEffect, useState } from "react"; import { PEOPLE_JUDGEMENT_AUTH_MESSAGE_TYPE } from "next-common/components/people/judgement/consts"; +import { useJudgementContext } from "../context"; function isJudgementAuthOpenerMessage(data, provider) { return ( @@ -17,6 +18,7 @@ export default function usePeopleJudgementSocialAuth({ redirectPath, isVerified, }) { + const { fetchMyJudgementRequest } = useJudgementContext(); const realAddress = useRealAddress(); const [loading, setLoading] = useState(false); @@ -71,6 +73,7 @@ export default function usePeopleJudgementSocialAuth({ if (data?.ok && data?.who && data.who === realAddress) { setConnected(true); + fetchMyJudgementRequest(); } }; @@ -79,7 +82,7 @@ export default function usePeopleJudgementSocialAuth({ return () => { window.removeEventListener("message", handleMessage); }; - }, [provider, realAddress]); + }, [provider, realAddress, fetchMyJudgementRequest]); return { loading, diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index 4fddc037bb..69a57c4737 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -5,6 +5,7 @@ import generateLayoutRawTitle from "next-common/utils/generateLayoutRawTitle"; import NoWalletConnected from "next-common/components/noWalletConnected"; import useRealAddress from "next-common/utils/hooks/useRealAddress"; import JudgementPageContent from "./content"; +import { JudgementContextProvider } from "./context"; export const tabs = [ { @@ -31,7 +32,9 @@ export default function JudgementPage() {
) : ( - + + + )} diff --git a/packages/next-common/components/people/judgement/socialConnect.jsx b/packages/next-common/components/people/judgement/socialConnect.jsx index cc5bf7fb51..4a5b7cb100 100644 --- a/packages/next-common/components/people/judgement/socialConnect.jsx +++ b/packages/next-common/components/people/judgement/socialConnect.jsx @@ -1,12 +1,13 @@ import { - ClosedTag, + WarningTag, PositiveTag, } from "next-common/components/tags/state/styled"; import Tooltip from "next-common/components/tooltip"; import PrimaryButton from "next-common/lib/button/primary"; +import CardHeaderLayout from "./cardHeaderLayout"; export default function PeopleJudgementSocialConnect({ - icon, + Icon, title, username, connected, @@ -14,33 +15,20 @@ export default function PeopleJudgementSocialConnect({ onConnect, }) { let message = `Click to verify your ${title} account`; + let tag = Pending; if (connected) { message = `${title} account already verified`; + tag = Verified; } return (
-
-
-
- {icon} - · -

{title}

-
-
- {connected ? ( - Verified - ) : ( - Pending - )} -
-
-
+
- Username: - {username} + Username: + {username}
@@ -50,7 +38,7 @@ export default function PeopleJudgementSocialConnect({ onClick={onConnect} size="small" > - Verify {title} + Verify
diff --git a/packages/next-common/components/people/judgement/summary.js b/packages/next-common/components/people/judgement/summary.js index 1c6517dc4d..3163b25f22 100644 --- a/packages/next-common/components/people/judgement/summary.js +++ b/packages/next-common/components/people/judgement/summary.js @@ -1,6 +1,5 @@ import useRealAddress from "next-common/utils/hooks/useRealAddress"; import { PeopleSocialType } from "./consts"; -import { SummaryGreyText } from "next-common/components/summary/styled"; import SummaryLayout from "next-common/components/summary/layout/layout"; import SummaryItem from "next-common/components/summary/layout/item"; import Account from "next-common/components/account"; @@ -38,14 +37,6 @@ export default function JudgementSummary({ request, loading }) { {pendingSocials} - - - - {totalSocials} - / {allSocialTypes.length} - - -
); diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index 5b787b3e8b..a50a4cc1f4 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -15,7 +15,7 @@ export default function Twitter({ request }) { return ( } + Icon={LinkTwitter} title="Twitter" username={request?.info?.twitter} connected={connected} diff --git a/packages/next-common/components/people/overview/identity/directIdentityActions.jsx b/packages/next-common/components/people/overview/identity/directIdentityActions.jsx index 1cd1a4d618..1e16d0019f 100644 --- a/packages/next-common/components/people/overview/identity/directIdentityActions.jsx +++ b/packages/next-common/components/people/overview/identity/directIdentityActions.jsx @@ -75,11 +75,11 @@ export function DirectIdentityActions() { )} onClick={() => setShowSetIdentityPopup(true)} > - +
- +
diff --git a/packages/next-common/components/tags/state/styled.js b/packages/next-common/components/tags/state/styled.js index d048222526..7c090d00bb 100644 --- a/packages/next-common/components/tags/state/styled.js +++ b/packages/next-common/components/tags/state/styled.js @@ -32,6 +32,10 @@ export const NegativeTag = styled(Common)` background: var(--red500); `; +export const WarningTag = styled(Common)` + background: var(--orange500); +`; + export const ClosedTag = styled(Common)` background: var(--neutral500); `; From aec2e335b00520f5cef2faf35dd44b7a060136a0 Mon Sep 17 00:00:00 2001 From: Chaojun Huang Date: Fri, 30 Jan 2026 09:38:25 +0800 Subject: [PATCH 34/41] Improve people menu and rename page routes (#7183) * Improve people menu and rename page routes * Update icon * Add AccountPanelJudgementScrollPrompt and related components for judgement prompts --- .../overview/accountInfo/accountInfoPanel.js | 2 + .../components/ScrollPromptContainer.js | 113 +++++++++++++++++ .../accountPanelJudgementScrollPrompt.js | 8 ++ .../components/accountPanelScrollPrompt.js | 116 +----------------- .../accountInfo/components/judgementPrompt.js | 15 +++ .../navigateToJudgementPagePromptItem.js | 2 +- .../components/useSetIdentityPrompt.js | 14 --- .../pages/people/judgement/authCallback.jsx | 2 +- .../people/common/getServerSideProps.js | 7 ++ .../hooks/useHasActiveJudgementRequest.js | 6 + .../components/people/judgement/content.jsx | 23 ++-- .../components/people/judgement/discord.jsx | 2 +- .../components/people/judgement/github.jsx | 2 +- .../components/people/judgement/index.jsx | 21 ++-- .../components/people/judgement/twitter.jsx | 2 +- packages/next-common/context/global.js | 16 +-- packages/next-common/context/nav/index.jsx | 7 +- packages/next-common/hooks/useMainMenuData.js | 5 + .../next-common/utils/consts/menu/index.js | 5 +- .../next-common/utils/consts/menu/people.jsx | 20 +-- .../next/pages/people/identities/index.jsx | 8 +- packages/next/pages/people/index.jsx | 8 +- .../pages/people/judgement-requests/index.jsx | 8 +- .../next/pages/people/judgement/index.jsx | 33 ----- .../next/pages/people/registrars/index.jsx | 8 +- .../next/pages/people/usernames/index.jsx | 16 ++- .../auth/discord.jsx | 0 .../auth/github.jsx | 0 .../auth/twitter.jsx | 0 .../next/pages/people/verifications/index.jsx | 48 ++++++++ 30 files changed, 286 insertions(+), 231 deletions(-) create mode 100644 packages/next-common/components/overview/accountInfo/components/ScrollPromptContainer.js create mode 100644 packages/next-common/components/overview/accountInfo/components/accountPanelJudgementScrollPrompt.js create mode 100644 packages/next-common/components/overview/accountInfo/components/judgementPrompt.js create mode 100644 packages/next-common/components/people/common/getServerSideProps.js create mode 100644 packages/next-common/components/people/hooks/useHasActiveJudgementRequest.js delete mode 100644 packages/next/pages/people/judgement/index.jsx rename packages/next/pages/people/{judgement => verifications}/auth/discord.jsx (100%) rename packages/next/pages/people/{judgement => verifications}/auth/github.jsx (100%) rename packages/next/pages/people/{judgement => verifications}/auth/twitter.jsx (100%) create mode 100644 packages/next/pages/people/verifications/index.jsx diff --git a/packages/next-common/components/overview/accountInfo/accountInfoPanel.js b/packages/next-common/components/overview/accountInfo/accountInfoPanel.js index 500a72635c..717a4250d1 100644 --- a/packages/next-common/components/overview/accountInfo/accountInfoPanel.js +++ b/packages/next-common/components/overview/accountInfo/accountInfoPanel.js @@ -29,6 +29,7 @@ import Avatar from "next-common/components/avatar"; import getIpfsLink from "next-common/utils/env/ipfsEndpoint"; import { AvatarImg } from "next-common/components/user/styled"; import Gravatar from "next-common/components/gravatar"; +import AccountPanelJudgementScrollPrompt from "./components/accountPanelJudgementScrollPrompt"; const ParaChainTeleportPopup = dynamic(() => import("next-common/components/paraChainTeleportPopup").then( @@ -314,6 +315,7 @@ export default function AccountInfoPanel() { + ); } diff --git a/packages/next-common/components/overview/accountInfo/components/ScrollPromptContainer.js b/packages/next-common/components/overview/accountInfo/components/ScrollPromptContainer.js new file mode 100644 index 0000000000..2b5addaf27 --- /dev/null +++ b/packages/next-common/components/overview/accountInfo/components/ScrollPromptContainer.js @@ -0,0 +1,113 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { animate } from "framer-motion"; +import { useWindowWidthContext } from "next-common/context/windowSize"; +import { Fragment } from "react"; + +const ITEM_HEIGHT = 40; +const MOBILE_ITEM_HEIGHT = 60; +const ITEM_GAP = 4; + +export default function ScrollPromptContainer({ components = [] }) { + const wrapperRef = useRef(); + const width = useWindowWidthContext(); + const isMobile = width < 768; + const [total, setTotal] = useState(0); + const pauseRef = useRef(false); + + const pageSize = total > 2 ? 2 : 1; + + const wrapperHeight = useMemo(() => { + if (!total) { + return 0; + } + if (isMobile) { + return pageSize * MOBILE_ITEM_HEIGHT + ITEM_GAP * (pageSize - 1); + } + return pageSize * ITEM_HEIGHT + ITEM_GAP * (pageSize - 1); + }, [isMobile, pageSize, total]); + + const [random, setRandom] = useState(1); + useEffect(() => { + if (!wrapperRef.current) { + return; + } + + const updateTotal = () => { + setTotal(Math.floor(wrapperRef.current.children.length / 3)); + }; + + updateTotal(); + + const observer = new MutationObserver(updateTotal); + observer.observe(wrapperRef.current, { childList: true }); + + return () => observer.disconnect(); + }, [random]); + + const marginTop = useMemo(() => { + if (isMobile) { + return MOBILE_ITEM_HEIGHT + ITEM_GAP; + } + return ITEM_HEIGHT + ITEM_GAP; + }, [isMobile]); + + const indexRef = useRef(0); + + useEffect(() => { + indexRef.current = 0; + wrapperRef.current?.scrollTo({ top: 0 }); + + if (total < 2) { + return; + } + + const interval = setInterval(() => { + if (pauseRef.current || !wrapperRef.current) { + return; + } + + const nextIndex = indexRef.current + 1; + const from = indexRef.current * marginTop; + const to = nextIndex * marginTop; + + animate(from, to, { + duration: 1, + onUpdate: (latest) => { + wrapperRef.current?.scrollTo({ top: latest }); + }, + onComplete: () => { + indexRef.current = nextIndex; + if (indexRef.current >= total) { + indexRef.current -= total; + wrapperRef.current?.scrollTo({ top: indexRef.current * marginTop }); + } + }, + }); + }, 6500); + return () => clearInterval(interval); + }, [marginTop, total]); + + return ( +
(pauseRef.current = true)} + onMouseLeave={() => (pauseRef.current = false)} + > + {[...components, ...components, ...components].map((Item, index) => { + return ( + + setRandom(random + 1)} + /> + + ); + })} +
+ ); +} diff --git a/packages/next-common/components/overview/accountInfo/components/accountPanelJudgementScrollPrompt.js b/packages/next-common/components/overview/accountInfo/components/accountPanelJudgementScrollPrompt.js new file mode 100644 index 0000000000..a488efb4ac --- /dev/null +++ b/packages/next-common/components/overview/accountInfo/components/accountPanelJudgementScrollPrompt.js @@ -0,0 +1,8 @@ +import ScrollPromptContainer from "./ScrollPromptContainer"; +import { JudgementPrompt } from "./judgementPrompt"; + +const promptComponents = [JudgementPrompt]; + +export default function AccountPanelJudgementScrollPrompt() { + return ; +} diff --git a/packages/next-common/components/overview/accountInfo/components/accountPanelScrollPrompt.js b/packages/next-common/components/overview/accountInfo/components/accountPanelScrollPrompt.js index c3e81f2dc7..8410435296 100644 --- a/packages/next-common/components/overview/accountInfo/components/accountPanelScrollPrompt.js +++ b/packages/next-common/components/overview/accountInfo/components/accountPanelScrollPrompt.js @@ -3,10 +3,7 @@ import { AvatarPrompt } from "./useSetAvatarPrompt"; import { IdentityPrompt } from "./useSetIdentityPrompt"; import { MultisigPrompt } from "./useMultisigPrompt"; import AssetsManagePrompt from "./useAssetsManagePrompt"; -import { useEffect, useMemo, useRef, useState } from "react"; -import { animate } from "framer-motion"; -import { useWindowWidthContext } from "next-common/context/windowSize"; -import { Fragment } from "react"; +import ScrollPromptContainer from "./ScrollPromptContainer"; import AccountUnlockBalancePrompt from "./accountUnlockBalancePrompt"; import VestingUnlockablePrompt from "./vestingUnlockablePrompt"; import NominatorWithdrawUnbondedPrompt from "./nominatorWithdrawUnbondedPrompt"; @@ -14,10 +11,6 @@ import NominatorClaimRewardPrompt from "./nominatorClaimRewardPrompt"; import PoolWithdrawUnbondedPrompt from "./poolWithdrawUnbondedPrompt"; import PoolClaimRewardPrompt from "./poolClaimRewardPrompt"; -const ITEM_HEIGHT = 40; -const MOBILE_ITEM_HEIGHT = 60; -const ITEM_GAP = 4; - const promptComponents = [ DelegationPrompt, AvatarPrompt, @@ -33,110 +26,5 @@ const promptComponents = [ ]; export default function AccountPanelScrollPrompt() { - const wrapperRef = useRef(); - const width = useWindowWidthContext(); - const isMobile = width < 768; - const [total, setTotal] = useState(0); - const pauseRef = useRef(false); - - const pageSize = total > 2 ? 2 : 1; - - const wrapperHeight = useMemo(() => { - if (!total) { - return 0; - } - if (isMobile) { - return pageSize * MOBILE_ITEM_HEIGHT + ITEM_GAP * (pageSize - 1); - } - return pageSize * ITEM_HEIGHT + ITEM_GAP * (pageSize - 1); - }, [isMobile, pageSize, total]); - - const [random, setRandom] = useState(1); - useEffect(() => { - if (!wrapperRef.current) { - return; - } - - const updateTotal = () => { - setTotal(Math.floor(wrapperRef.current.children.length / 3)); - }; - - updateTotal(); - - const observer = new MutationObserver(updateTotal); - observer.observe(wrapperRef.current, { childList: true }); - - return () => observer.disconnect(); - }, [random]); - - const marginTop = useMemo(() => { - if (isMobile) { - return MOBILE_ITEM_HEIGHT + ITEM_GAP; - } - return ITEM_HEIGHT + ITEM_GAP; - }, [isMobile]); - - const indexRef = useRef(0); - - useEffect(() => { - indexRef.current = 0; - wrapperRef.current?.scrollTo({ top: 0 }); - - if (total < 2) { - return; - } - - const interval = setInterval(() => { - if (pauseRef.current || !wrapperRef.current) { - return; - } - - const nextIndex = indexRef.current + 1; - const from = indexRef.current * marginTop; - const to = nextIndex * marginTop; - - animate(from, to, { - duration: 1, - onUpdate: (latest) => { - wrapperRef.current?.scrollTo({ top: latest }); - }, - onComplete: () => { - indexRef.current = nextIndex; - if (indexRef.current >= total) { - indexRef.current -= total; - wrapperRef.current?.scrollTo({ top: indexRef.current * marginTop }); - } - }, - }); - }, 6500); - return () => clearInterval(interval); - }, [marginTop, total]); - - return ( -
(pauseRef.current = true)} - onMouseLeave={() => (pauseRef.current = false)} - > - {[...promptComponents, ...promptComponents, ...promptComponents].map( - (Item, index) => { - return ( - - { - setRandom(random + 1); - }} - /> - - ); - }, - )} -
- ); + return ; } diff --git a/packages/next-common/components/overview/accountInfo/components/judgementPrompt.js b/packages/next-common/components/overview/accountInfo/components/judgementPrompt.js new file mode 100644 index 0000000000..e482961320 --- /dev/null +++ b/packages/next-common/components/overview/accountInfo/components/judgementPrompt.js @@ -0,0 +1,15 @@ +import RequestJudgementPromptItem from "./requestJudgementPromptItem"; +import NavigateToJudgementPagePromptItem from "./navigateToJudgementPagePromptItem"; +import { useChain } from "next-common/context/chain"; +import { isPeopleChain } from "next-common/utils/chain"; + +export function JudgementPrompt({ onClose }) { + const chain = useChain(); + + return ( + <> + + {isPeopleChain(chain) && } + + ); +} diff --git a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js index a14cce1d18..77351cc2a3 100644 --- a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js +++ b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js @@ -12,7 +12,7 @@ function NavigateToJudgementPagePrompt() { return (
Actions is required to verify your identity social accounts.  - + Go to Judgement page . diff --git a/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js b/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js index 39af2d4544..ff0838dba7 100644 --- a/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js +++ b/packages/next-common/components/overview/accountInfo/components/useSetIdentityPrompt.js @@ -1,23 +1,9 @@ import SetIdentityPromptItem from "./setIdentityPromptItem"; -import RequestJudgementPromptItem from "./requestJudgementPromptItem"; -import NavigateToJudgementPagePromptItem from "./navigateToJudgementPagePromptItem"; -import { useChain } from "next-common/context/chain"; -import { isPeopleChain } from "next-common/utils/chain"; - -export { - SetIdentityPromptItem, - RequestJudgementPromptItem, - NavigateToJudgementPagePromptItem, -}; export function IdentityPrompt({ onClose }) { - const chain = useChain(); - return ( <> - - {isPeopleChain(chain) && } ); } diff --git a/packages/next-common/components/pages/people/judgement/authCallback.jsx b/packages/next-common/components/pages/people/judgement/authCallback.jsx index e557d22e60..488c72e37c 100644 --- a/packages/next-common/components/pages/people/judgement/authCallback.jsx +++ b/packages/next-common/components/pages/people/judgement/authCallback.jsx @@ -173,7 +173,7 @@ function AuthResultCard({ providerLabel, result, time }) { /> )}
- + Go to Judgement Detail
diff --git a/packages/next-common/components/people/common/getServerSideProps.js b/packages/next-common/components/people/common/getServerSideProps.js new file mode 100644 index 0000000000..e2d7ea893a --- /dev/null +++ b/packages/next-common/components/people/common/getServerSideProps.js @@ -0,0 +1,7 @@ +import { withCommonProps } from "next-common/lib"; + +export const getPeopleServerSideProps = withCommonProps(async () => { + return { + props: {}, + }; +}); diff --git a/packages/next-common/components/people/hooks/useHasActiveJudgementRequest.js b/packages/next-common/components/people/hooks/useHasActiveJudgementRequest.js new file mode 100644 index 0000000000..8935ffa8d2 --- /dev/null +++ b/packages/next-common/components/people/hooks/useHasActiveJudgementRequest.js @@ -0,0 +1,6 @@ +import useMyJudgementRequest from "./useMyJudgementRequest"; + +export default function useHasActiveJudgementRequest() { + const { value } = useMyJudgementRequest(); + return !!value; +} diff --git a/packages/next-common/components/people/judgement/content.jsx b/packages/next-common/components/people/judgement/content.jsx index 5bfb2ad32a..b629b1cee7 100644 --- a/packages/next-common/components/people/judgement/content.jsx +++ b/packages/next-common/components/people/judgement/content.jsx @@ -1,3 +1,4 @@ +import tw from "tailwind-styled-components"; import Loading from "next-common/components/loading"; import Discord from "./discord"; import Element from "./element"; @@ -7,6 +8,8 @@ import JudgementSummary from "./summary"; import Twitter from "./twitter"; import { useJudgementContext } from "./context"; +const SocialAccountWrapper = tw.div`flex bg-neutral100 border-b border-neutral300 p-4 rounded-lg`; + export default function JudgementPageContent() { const { myJudgementRequest: request, isLoadingMyJudgementRequest: loading } = useJudgementContext(); @@ -22,29 +25,29 @@ export default function JudgementPageContent() { ) : ( <> {request?.info?.email && ( -
+ -
+ )} {request?.info?.matrix && ( -
+ -
+ )} {request?.info?.discord && ( -
+ -
+ )} {request?.info?.twitter && ( -
+ -
+ )} {request?.info?.github && ( -
+ -
+ )} )} diff --git a/packages/next-common/components/people/judgement/discord.jsx b/packages/next-common/components/people/judgement/discord.jsx index ed11c78899..66f685dbdb 100644 --- a/packages/next-common/components/people/judgement/discord.jsx +++ b/packages/next-common/components/people/judgement/discord.jsx @@ -8,7 +8,7 @@ export default function Discord({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.discord, authUrlPath: "people/verifications/auth/discord/auth-url", - redirectPath: "/people/judgement/auth/discord", + redirectPath: "/people/verifications/auth/discord", isVerified, }); diff --git a/packages/next-common/components/people/judgement/github.jsx b/packages/next-common/components/people/judgement/github.jsx index 6b17facf19..a8f002ce64 100644 --- a/packages/next-common/components/people/judgement/github.jsx +++ b/packages/next-common/components/people/judgement/github.jsx @@ -9,7 +9,7 @@ export default function Github({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.github, authUrlPath: "people/verifications/auth/github/auth-url", - redirectPath: "/people/judgement/auth/github", + redirectPath: "/people/verifications/auth/github", isVerified, }); diff --git a/packages/next-common/components/people/judgement/index.jsx b/packages/next-common/components/people/judgement/index.jsx index 69a57c4737..1e67bfecc7 100644 --- a/packages/next-common/components/people/judgement/index.jsx +++ b/packages/next-common/components/people/judgement/index.jsx @@ -7,24 +7,19 @@ import useRealAddress from "next-common/utils/hooks/useRealAddress"; import JudgementPageContent from "./content"; import { JudgementContextProvider } from "./context"; -export const tabs = [ - { - value: "Judgement", - label: "Judgement", - url: "/people/judgement", - exactMatch: false, - }, -]; - -export default function JudgementPage() { +export default function VerificationsPage() { const realAddress = useRealAddress(); return ( } > {!realAddress ? ( diff --git a/packages/next-common/components/people/judgement/twitter.jsx b/packages/next-common/components/people/judgement/twitter.jsx index a50a4cc1f4..d28c68bf47 100644 --- a/packages/next-common/components/people/judgement/twitter.jsx +++ b/packages/next-common/components/people/judgement/twitter.jsx @@ -9,7 +9,7 @@ export default function Twitter({ request }) { const { loading, connected, openAuthWindow } = usePeopleJudgementSocialAuth({ provider: PeopleSocialType.twitter, authUrlPath: "people/verifications/auth/twitter/auth-url", - redirectPath: "/people/judgement/auth/twitter", + redirectPath: "/people/verifications/auth/twitter", isVerified, }); diff --git a/packages/next-common/context/global.js b/packages/next-common/context/global.js index 0654bf3b04..653d1cb431 100644 --- a/packages/next-common/context/global.js +++ b/packages/next-common/context/global.js @@ -39,12 +39,12 @@ export default function GlobalProvider({ - - + + - - + + diff --git a/packages/next-common/context/nav/index.jsx b/packages/next-common/context/nav/index.jsx index 90ab90b262..3b26deeb3a 100644 --- a/packages/next-common/context/nav/index.jsx +++ b/packages/next-common/context/nav/index.jsx @@ -6,6 +6,7 @@ import { useIsomorphicLayoutEffect } from "react-use"; import { matchNewMenu } from "next-common/utils/consts/menu"; import { useRouter } from "next/router"; import useIsAdmin from "next-common/hooks/useIsAdmin"; +import useHasActiveJudgementRequest from "next-common/components/people/hooks/useHasActiveJudgementRequest"; const NavCollapsedContext = createContext([]); const NavSubmenuVisibleContext = createContext([]); @@ -80,9 +81,13 @@ export function useNavMenuType() { } function NavMenuTypeProvider({ children }) { + const hasActiveJudgementRequest = useHasActiveJudgementRequest(); const router = useRouter(); const isAdmin = useIsAdmin(); - const menu = useMemo(() => getMainMenu({ isAdmin }), [isAdmin]); + const menu = useMemo( + () => getMainMenu({ isAdmin, hasActiveJudgementRequest }), + [isAdmin, hasActiveJudgementRequest], + ); const matchMenu = useMemo(() => { return ( diff --git a/packages/next-common/hooks/useMainMenuData.js b/packages/next-common/hooks/useMainMenuData.js index ca605962f6..9530747c66 100644 --- a/packages/next-common/hooks/useMainMenuData.js +++ b/packages/next-common/hooks/useMainMenuData.js @@ -3,11 +3,14 @@ import { getMainMenu } from "next-common/utils/consts/menu"; import { useUser } from "next-common/context/user"; import { useMemo } from "react"; import useIsAdmin from "./useIsAdmin"; +import useHasActiveJudgementRequest from "next-common/components/people/hooks/useHasActiveJudgementRequest"; export default function useMainMenuData() { const user = useUser(); const { tracks, fellowshipTracks, summary, detail, ambassadorTracks } = usePageProps(); + const hasActiveJudgementRequest = useHasActiveJudgementRequest(); + const isAdmin = useIsAdmin(); return useMemo(() => { @@ -18,6 +21,7 @@ export default function useMainMenuData() { summary, currentTrackId: detail?.track, isAdmin, + hasActiveJudgementRequest, }).filter((item) => { if (item.value === "account") { // not connect wallet @@ -33,5 +37,6 @@ export default function useMainMenuData() { tracks, user?.address, isAdmin, + hasActiveJudgementRequest, ]); } diff --git a/packages/next-common/utils/consts/menu/index.js b/packages/next-common/utils/consts/menu/index.js index 6e8fd8b437..81f4a45943 100644 --- a/packages/next-common/utils/consts/menu/index.js +++ b/packages/next-common/utils/consts/menu/index.js @@ -37,6 +37,7 @@ export function getHomeMenu({ ambassadorTracks = [], currentTrackId, isAdmin = false, + hasActiveJudgementRequest = false, } = {}) { const { modules, hasMultisig = false } = getChainSettings(CHAIN); @@ -57,7 +58,7 @@ export function getHomeMenu({ modules?.alliance && getAllianceMenu(summary), modules?.communityCouncil && getCommunityCouncilMenu(summary), modules?.staking && stakingMenu, - modules?.people && getPeopleMenu(isAdmin), + modules?.people && getPeopleMenu({ isAdmin, hasActiveJudgementRequest }), modules?.coretime && coretimeMenu, getAdvancedMenu( [ @@ -92,6 +93,7 @@ export function getMainMenu({ ambassadorTracks = [], currentTrackId, isAdmin = false, + hasActiveJudgementRequest = false, } = {}) { const { hotMenu = {} } = getChainSettings(CHAIN); @@ -107,6 +109,7 @@ export function getMainMenu({ ambassadorTracks, currentTrackId, isAdmin, + hasActiveJudgementRequest, }); const activeModulesMenu = []; diff --git a/packages/next-common/utils/consts/menu/people.jsx b/packages/next-common/utils/consts/menu/people.jsx index b81ae73f54..21c28b1618 100644 --- a/packages/next-common/utils/consts/menu/people.jsx +++ b/packages/next-common/utils/consts/menu/people.jsx @@ -6,10 +6,11 @@ import { MenuRegistrars, InfoUsers, MenuJudgements, + MenuIdentity, } from "@osn/icons/subsquare"; import { NAV_MENU_TYPE } from "next-common/utils/constants"; -export function getPeopleMenu(isAdmin) { +export function getPeopleMenu({ isAdmin, hasActiveJudgementRequest } = {}) { return { name: "People", value: "people", @@ -23,11 +24,6 @@ export function getPeopleMenu(isAdmin) { value: "overview", pathname: "/people", icon: , - extraMatchNavMenuActivePathnames: [ - "/people/judgement", - "/people/verifications/auth/discord", - "/people/verifications/auth/twitter", - ], }, { name: "Identities", @@ -41,12 +37,22 @@ export function getPeopleMenu(isAdmin) { pathname: "/people/registrars", icon: , }, + hasActiveJudgementRequest && { + name: "Verifications", + value: "verifications", + pathname: "/people/verifications", + icon: , + extraMatchNavMenuActivePathnames: [ + "/people/verifications/auth/discord", + "/people/verifications/auth/twitter", + "/people/verifications/auth/github", + ], + }, isAdmin && { name: "Judgement Requests", value: "judgement-requests", pathname: "/people/judgement-requests", icon: , - adminOnly: true, }, { type: "divider", diff --git a/packages/next/pages/people/identities/index.jsx b/packages/next/pages/people/identities/index.jsx index 6bc45d3745..c2d5cb140d 100644 --- a/packages/next/pages/people/identities/index.jsx +++ b/packages/next/pages/people/identities/index.jsx @@ -1,8 +1,8 @@ -import { withCommonProps } from "next-common/lib"; import { CHAIN } from "next-common/utils/constants"; import getChainSettings from "next-common/utils/consts/settings"; import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; import { PeopleGlobalProvider } from ".."; +import { getPeopleServerSideProps } from "next-common/components/people/common/getServerSideProps"; const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; @@ -25,9 +25,5 @@ export const getServerSideProps = async (ctx) => { }; } - return withCommonProps(async () => { - return { - props: {}, - }; - })(ctx); + return await getPeopleServerSideProps(ctx); }; diff --git a/packages/next/pages/people/index.jsx b/packages/next/pages/people/index.jsx index 2846bb1072..5bc47aa7fb 100644 --- a/packages/next/pages/people/index.jsx +++ b/packages/next/pages/people/index.jsx @@ -1,7 +1,7 @@ import { CHAIN } from "next-common/utils/constants"; import { createStore } from "next-common/store"; import { commonReducers } from "next-common/store/reducers"; -import { withCommonProps } from "next-common/lib"; +import { getPeopleServerSideProps } from "next-common/components/people/common/getServerSideProps"; import RelayInfoProvider from "next-common/context/relayInfo"; import ApiProvider from "next-common/context/api"; import ChainProvider from "next-common/context/chain"; @@ -57,9 +57,5 @@ export const getServerSideProps = async (ctx) => { }; } - return withCommonProps(async () => { - return { - props: {}, - }; - })(ctx); + return await getPeopleServerSideProps(ctx); }; diff --git a/packages/next/pages/people/judgement-requests/index.jsx b/packages/next/pages/people/judgement-requests/index.jsx index 74f9e866e8..e901549ecd 100644 --- a/packages/next/pages/people/judgement-requests/index.jsx +++ b/packages/next/pages/people/judgement-requests/index.jsx @@ -1,4 +1,4 @@ -import { withCommonProps } from "next-common/lib"; +import { getPeopleServerSideProps } from "next-common/components/people/common/getServerSideProps"; import { CHAIN } from "next-common/utils/constants"; import getChainSettings from "next-common/utils/consts/settings"; import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; @@ -44,9 +44,5 @@ export const getServerSideProps = async (ctx) => { }; } - return withCommonProps(async () => { - return { - props: {}, - }; - })(ctx); + return await getPeopleServerSideProps(ctx); }; diff --git a/packages/next/pages/people/judgement/index.jsx b/packages/next/pages/people/judgement/index.jsx deleted file mode 100644 index 478ea43d62..0000000000 --- a/packages/next/pages/people/judgement/index.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import { CHAIN } from "next-common/utils/constants"; -import { withCommonProps } from "next-common/lib"; -import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; -import getChainSettings from "next-common/utils/consts/settings"; -import { PeopleGlobalProvider } from ".."; - -const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; - -const PeopleOverviewPageImpl = dynamicClientOnly(() => - import("next-common/components/people/judgement"), -); - -export default function PeoplePage() { - return ( - - - - ); -} - -export const getServerSideProps = async (ctx) => { - if (!isPeopleSupported) { - return { - notFound: true, - }; - } - - return withCommonProps(async () => { - return { - props: {}, - }; - })(ctx); -}; diff --git a/packages/next/pages/people/registrars/index.jsx b/packages/next/pages/people/registrars/index.jsx index d434eeb513..0709272048 100644 --- a/packages/next/pages/people/registrars/index.jsx +++ b/packages/next/pages/people/registrars/index.jsx @@ -1,4 +1,4 @@ -import { withCommonProps } from "next-common/lib"; +import { getPeopleServerSideProps } from "next-common/components/people/common/getServerSideProps"; import { CHAIN } from "next-common/utils/constants"; import getChainSettings from "next-common/utils/consts/settings"; import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; @@ -25,9 +25,5 @@ export const getServerSideProps = async (ctx) => { }; } - return withCommonProps(async () => { - return { - props: {}, - }; - })(ctx); + return await getPeopleServerSideProps(ctx); }; diff --git a/packages/next/pages/people/usernames/index.jsx b/packages/next/pages/people/usernames/index.jsx index bccd0685f8..6fb5a377b9 100644 --- a/packages/next/pages/people/usernames/index.jsx +++ b/packages/next/pages/people/usernames/index.jsx @@ -1,6 +1,10 @@ +import { getPeopleServerSideProps } from "next-common/components/people/common/getServerSideProps"; import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; import { PeopleGlobalProvider } from ".."; -export { getServerSideProps } from "../index"; +import getChainSettings from "next-common/utils/consts/settings"; +import { CHAIN } from "next-common/utils/constants"; + +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; const PeopleUsernamesPageImpl = dynamicClientOnly(() => import("next-common/components/people/usernames"), @@ -13,3 +17,13 @@ export default function PeopleUsernamesPage() { ); } + +export const getServerSideProps = async (ctx) => { + if (!isPeopleSupported) { + return { + notFound: true, + }; + } + + return await getPeopleServerSideProps(ctx); +}; diff --git a/packages/next/pages/people/judgement/auth/discord.jsx b/packages/next/pages/people/verifications/auth/discord.jsx similarity index 100% rename from packages/next/pages/people/judgement/auth/discord.jsx rename to packages/next/pages/people/verifications/auth/discord.jsx diff --git a/packages/next/pages/people/judgement/auth/github.jsx b/packages/next/pages/people/verifications/auth/github.jsx similarity index 100% rename from packages/next/pages/people/judgement/auth/github.jsx rename to packages/next/pages/people/verifications/auth/github.jsx diff --git a/packages/next/pages/people/judgement/auth/twitter.jsx b/packages/next/pages/people/verifications/auth/twitter.jsx similarity index 100% rename from packages/next/pages/people/judgement/auth/twitter.jsx rename to packages/next/pages/people/verifications/auth/twitter.jsx diff --git a/packages/next/pages/people/verifications/index.jsx b/packages/next/pages/people/verifications/index.jsx new file mode 100644 index 0000000000..5525aea32e --- /dev/null +++ b/packages/next/pages/people/verifications/index.jsx @@ -0,0 +1,48 @@ +import { getPeopleServerSideProps } from "next-common/components/people/common/getServerSideProps"; +import { CHAIN } from "next-common/utils/constants"; +import dynamicClientOnly from "next-common/lib/dynamic/clientOnly"; +import getChainSettings from "next-common/utils/consts/settings"; +import { PeopleGlobalProvider } from ".."; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { useEffect } from "react"; +import { useRouter } from "next/router"; + +const isPeopleSupported = !!getChainSettings(CHAIN).modules?.people; + +const PeopleOverviewPageImpl = dynamicClientOnly(() => + import("next-common/components/people/judgement"), +); + +export default function VerificationPage() { + const router = useRouter(); + const realAddress = useRealAddress(); + + useEffect(() => { + if (!realAddress) { + const timer = setTimeout(() => { + router.replace("/people"); + }, 3000); + return () => clearTimeout(timer); + } + }, [realAddress, router]); + + if (!realAddress) { + return
Only connected users can access this page. redirecting...
; + } + + return ( + + + + ); +} + +export const getServerSideProps = async (ctx) => { + if (!isPeopleSupported) { + return { + notFound: true, + }; + } + + return await getPeopleServerSideProps(ctx); +}; From 37751acf220b07bf5d63438d8b3de9e46c302df3 Mon Sep 17 00:00:00 2001 From: chaojun Date: Fri, 30 Jan 2026 15:15:30 +0800 Subject: [PATCH 35/41] Re-sort prompt and update prompt color, #7183 --- .../components/overview/accountInfo/accountInfoPanel.js | 2 +- .../accountInfo/components/navigateToJudgementPagePromptItem.js | 2 +- .../accountInfo/components/requestJudgementPromptItem.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/next-common/components/overview/accountInfo/accountInfoPanel.js b/packages/next-common/components/overview/accountInfo/accountInfoPanel.js index 717a4250d1..69936b74ef 100644 --- a/packages/next-common/components/overview/accountInfo/accountInfoPanel.js +++ b/packages/next-common/components/overview/accountInfo/accountInfoPanel.js @@ -314,8 +314,8 @@ export default function AccountInfoPanel() { - + ); } diff --git a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js index 77351cc2a3..8aa22ab3bc 100644 --- a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js +++ b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js @@ -42,7 +42,7 @@ function useNavigateToJudgementPagePromptItem() { return { key: CACHE_KEY.navigateToJudgementPagePrompt, message: , - type: PromptTypes.INFO, + type: PromptTypes.WARNING, close: () => setVisible(false, { expires: 15 }), }; }, [needAction, setVisible, visible]); diff --git a/packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js b/packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js index 289a2a220b..0261ce7bb8 100644 --- a/packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js +++ b/packages/next-common/components/overview/accountInfo/components/requestJudgementPromptItem.js @@ -82,7 +82,7 @@ function useRequestJudgementPromptItem() { return { key: CACHE_KEY.requestJudgementPrompt, message: , - type: PromptTypes.INFO, + type: PromptTypes.WARNING, close: () => setVisible(false, { expires: 15 }), }; }, [setVisible, shouldRequestJudgement, visible]); From 9e16f3304be9034b7ecac3627834232c0abd6e68 Mon Sep 17 00:00:00 2001 From: chaojun Date: Fri, 30 Jan 2026 15:27:51 +0800 Subject: [PATCH 36/41] Hide the verify button when account connected, #7183 --- .../people/judgement/socialConnect.jsx | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/next-common/components/people/judgement/socialConnect.jsx b/packages/next-common/components/people/judgement/socialConnect.jsx index 4a5b7cb100..2b5e735332 100644 --- a/packages/next-common/components/people/judgement/socialConnect.jsx +++ b/packages/next-common/components/people/judgement/socialConnect.jsx @@ -31,16 +31,18 @@ export default function PeopleJudgementSocialConnect({ {username}
- - - Verify - - + {!connected && ( + + + Verify + + + )}
); From 1432e3fe2bcd40bead4336bfa1c79f613aefacb4 Mon Sep 17 00:00:00 2001 From: chaojun Date: Fri, 30 Jan 2026 16:27:28 +0800 Subject: [PATCH 37/41] Update prompt message text, #7183 --- .../accountInfo/components/navigateToJudgementPagePromptItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js index 8aa22ab3bc..91ac29eb5f 100644 --- a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js +++ b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js @@ -11,7 +11,7 @@ import useMyJudgementRequest from "next-common/components/people/hooks/useMyJudg function NavigateToJudgementPagePrompt() { return (
- Actions is required to verify your identity social accounts.  + Verify your social accounts for identity judgement.  Go to Judgement page From 749625a1e0fb6f6bf4122de6e0f1a69392a6b5c7 Mon Sep 17 00:00:00 2001 From: chaojun Date: Fri, 30 Jan 2026 16:47:28 +0800 Subject: [PATCH 38/41] fix: identity not shown, #6881 --- packages/next-common/context/people/identityInfoContext.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-common/context/people/identityInfoContext.js b/packages/next-common/context/people/identityInfoContext.js index 759107824e..6fdb97cabd 100644 --- a/packages/next-common/context/people/identityInfoContext.js +++ b/packages/next-common/context/people/identityInfoContext.js @@ -16,5 +16,5 @@ export default function IdentityInfoProvider({ children }) { } export function useIdentityInfoContext() { - return useContext(IdentityInfoContext) || {}; + return useContext(IdentityInfoContext); } From 96e0533c12c541699678fe009c8323beaafa5be6 Mon Sep 17 00:00:00 2001 From: chaojun Date: Fri, 30 Jan 2026 16:52:10 +0800 Subject: [PATCH 39/41] Update prompt message text, #6881 --- .../accountInfo/components/navigateToJudgementPagePromptItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js index 91ac29eb5f..668a9696ce 100644 --- a/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js +++ b/packages/next-common/components/overview/accountInfo/components/navigateToJudgementPagePromptItem.js @@ -13,7 +13,7 @@ function NavigateToJudgementPagePrompt() {
Verify your social accounts for identity judgement.  - Go to Judgement page + Go to Verifications page .
From ee7e19eb4d18dd6ddb8736bb888a3c8ebf11c065 Mon Sep 17 00:00:00 2001 From: chaojun Date: Fri, 30 Jan 2026 17:18:29 +0800 Subject: [PATCH 40/41] feat: add verification done notification to judgement summary, #6881 --- .../components/people/judgement/content.jsx | 25 ++++++++- .../components/people/judgement/summary.js | 55 +++++++++---------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/packages/next-common/components/people/judgement/content.jsx b/packages/next-common/components/people/judgement/content.jsx index b629b1cee7..5921a4df62 100644 --- a/packages/next-common/components/people/judgement/content.jsx +++ b/packages/next-common/components/people/judgement/content.jsx @@ -7,16 +7,39 @@ import Github from "./github"; import JudgementSummary from "./summary"; import Twitter from "./twitter"; import { useJudgementContext } from "./context"; +import { PeopleSocialType } from "./consts"; const SocialAccountWrapper = tw.div`flex bg-neutral100 border-b border-neutral300 p-4 rounded-lg`; +function calcVerificationNumbers(request) { + const allSocialTypes = Object.values(PeopleSocialType); + const info = request?.info || {}; + const verifications = request?.verifications || {}; + const totalSocials = allSocialTypes.filter((key) => + Boolean(info[key === "element" ? "matrix" : key]), + ).length; + const verified = allSocialTypes.filter( + (key) => + Boolean(info[key === "element" ? "matrix" : key]) && + verifications?.[key] === true, + ).length; + const pending = totalSocials - verified; + return { verified, pending }; +} + export default function JudgementPageContent() { const { myJudgementRequest: request, isLoadingMyJudgementRequest: loading } = useJudgementContext(); + const { verified, pending } = calcVerificationNumbers(request); + return ( <> - +
{loading && !request ? (
diff --git a/packages/next-common/components/people/judgement/summary.js b/packages/next-common/components/people/judgement/summary.js index 3163b25f22..9e68fa4f56 100644 --- a/packages/next-common/components/people/judgement/summary.js +++ b/packages/next-common/components/people/judgement/summary.js @@ -1,43 +1,38 @@ import useRealAddress from "next-common/utils/hooks/useRealAddress"; -import { PeopleSocialType } from "./consts"; import SummaryLayout from "next-common/components/summary/layout/layout"; import SummaryItem from "next-common/components/summary/layout/item"; import Account from "next-common/components/account"; import LoadableContent from "next-common/components/common/loadableContent"; +import { InfoMessage } from "next-common/components/setting/styled"; -export default function JudgementSummary({ request, loading }) { +export default function JudgementSummary({ verified, pending, loading }) { const address = useRealAddress(); - const allSocialTypes = Object.values(PeopleSocialType); - const info = request?.info || {}; - const verifications = request?.verifications || {}; - const totalSocials = allSocialTypes.filter((key) => - Boolean(info[key === "element" ? "matrix" : key]), - ).length; - const verifiedSocials = allSocialTypes.filter( - (key) => - Boolean(info[key === "element" ? "matrix" : key]) && - verifications?.[key] === true, - ).length; - const pendingSocials = totalSocials - verifiedSocials; - return ( -
-
- +
+
+
+ +
+ + + + {verified} + + + + + {pending} + + +
- - - - {verifiedSocials} - - - - - {pendingSocials} - - - + {!loading && pending === 0 && ( + + All social account verifications are done. Our registrar will do a + final check and give the judgement soon. + + )}
); } From 0fa2915d32973d1791621511a02450e85eeabe80 Mon Sep 17 00:00:00 2001 From: chaojun Date: Mon, 2 Feb 2026 12:19:48 +0800 Subject: [PATCH 41/41] fix empty judgement verifications page, #6881 --- .../components/nav/menu/item/index.jsx | 6 +- .../components/nav/menu/item/item.jsx | 5 ++ .../components/people/judgement/content.jsx | 83 +++---------------- .../people/judgement/contentEmpty.js | 19 +++++ .../people/judgement/contentLoading.js | 35 ++++++++ .../people/judgement/contentVerifications.js | 63 ++++++++++++++ .../components/people/judgement/summary.js | 13 +-- .../next-common/utils/consts/menu/people.jsx | 3 +- 8 files changed, 144 insertions(+), 83 deletions(-) create mode 100644 packages/next-common/components/people/judgement/contentEmpty.js create mode 100644 packages/next-common/components/people/judgement/contentLoading.js create mode 100644 packages/next-common/components/people/judgement/contentVerifications.js diff --git a/packages/next-common/components/nav/menu/item/index.jsx b/packages/next-common/components/nav/menu/item/index.jsx index cde537358a..660ff51571 100644 --- a/packages/next-common/components/nav/menu/item/index.jsx +++ b/packages/next-common/components/nav/menu/item/index.jsx @@ -5,7 +5,7 @@ import NavMenuDivider from "../../divider"; import { useNavSubmenuVisible } from "next-common/context/nav"; export default function NavMenuItem({ collapsed, ...menu } = {}) { - const { type, items } = menu || {}; + const { type, items, visible = true } = menu || {}; const router = useRouter(); const routePathname = router.asPath.split("?")[0]; const [navSubmenuVisible, setNavSubmenuVisible] = useNavSubmenuVisible(); @@ -14,6 +14,10 @@ export default function NavMenuItem({ collapsed, ...menu } = {}) { return ; } + if (visible === false) { + return null; + } + if (items?.length && !menu?.hideItemsOnMenu) { return ( - Boolean(info[key === "element" ? "matrix" : key]), - ).length; - const verified = allSocialTypes.filter( - (key) => - Boolean(info[key === "element" ? "matrix" : key]) && - verifications?.[key] === true, - ).length; - const pending = totalSocials - verified; - return { verified, pending }; -} +import ContentVerifications from "./contentVerifications"; +import ContentLoading from "./contentLoading"; +import ContentEmpty from "./contentEmpty"; export default function JudgementPageContent() { const { myJudgementRequest: request, isLoadingMyJudgementRequest: loading } = useJudgementContext(); - const { verified, pending } = calcVerificationNumbers(request); + if (loading) { + return ; + } + + if (!request) { + return ; + } - return ( - <> - -
- {loading && !request ? ( -
- -
- ) : ( - <> - {request?.info?.email && ( - - - - )} - {request?.info?.matrix && ( - - - - )} - {request?.info?.discord && ( - - - - )} - {request?.info?.twitter && ( - - - - )} - {request?.info?.github && ( - - - - )} - - )} -
- - ); + return ; } diff --git a/packages/next-common/components/people/judgement/contentEmpty.js b/packages/next-common/components/people/judgement/contentEmpty.js new file mode 100644 index 0000000000..54af8addb7 --- /dev/null +++ b/packages/next-common/components/people/judgement/contentEmpty.js @@ -0,0 +1,19 @@ +import Account from "next-common/components/account"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { InfoMessage } from "next-common/components/setting/styled"; + +export default function ContentEmpty() { + const address = useRealAddress(); + return ( +
+
+
+ +
+
+ + You have no identity social account verifications. + +
+ ); +} diff --git a/packages/next-common/components/people/judgement/contentLoading.js b/packages/next-common/components/people/judgement/contentLoading.js new file mode 100644 index 0000000000..069963a296 --- /dev/null +++ b/packages/next-common/components/people/judgement/contentLoading.js @@ -0,0 +1,35 @@ +import Loading from "next-common/components/loading"; +import Account from "next-common/components/account"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import SummaryLayout from "next-common/components/summary/layout/layout"; +import SummaryItem from "next-common/components/summary/layout/item"; +import LoadableContent from "next-common/components/common/loadableContent"; + +export default function ContentLoading() { + const address = useRealAddress(); + + return ( + <> +
+
+
+ +
+ + + + + + + + +
+
+
+
+ +
+
+ + ); +} diff --git a/packages/next-common/components/people/judgement/contentVerifications.js b/packages/next-common/components/people/judgement/contentVerifications.js new file mode 100644 index 0000000000..c7a05ad551 --- /dev/null +++ b/packages/next-common/components/people/judgement/contentVerifications.js @@ -0,0 +1,63 @@ +import tw from "tailwind-styled-components"; +import Discord from "./discord"; +import Element from "./element"; +import Email from "./email"; +import Github from "./github"; +import JudgementSummary from "./summary"; +import Twitter from "./twitter"; +import { PeopleSocialType } from "./consts"; + +const SocialAccountWrapper = tw.div`flex bg-neutral100 border-b border-neutral300 p-4 rounded-lg`; + +function calcVerificationNumbers(request) { + const allSocialTypes = Object.values(PeopleSocialType); + const info = request?.info || {}; + const verifications = request?.verifications || {}; + const totalSocials = allSocialTypes.filter((key) => + Boolean(info[key === "element" ? "matrix" : key]), + ).length; + const verified = allSocialTypes.filter( + (key) => + Boolean(info[key === "element" ? "matrix" : key]) && + verifications?.[key] === true, + ).length; + const pending = totalSocials - verified; + return { verified, pending }; +} + +export default function ContentVerifications({ request }) { + const { verified, pending } = calcVerificationNumbers(request); + + return ( + <> + +
+ {request?.info?.email && ( + + + + )} + {request?.info?.matrix && ( + + + + )} + {request?.info?.discord && ( + + + + )} + {request?.info?.twitter && ( + + + + )} + {request?.info?.github && ( + + + + )} +
+ + ); +} diff --git a/packages/next-common/components/people/judgement/summary.js b/packages/next-common/components/people/judgement/summary.js index 9e68fa4f56..c46e30900b 100644 --- a/packages/next-common/components/people/judgement/summary.js +++ b/packages/next-common/components/people/judgement/summary.js @@ -2,10 +2,9 @@ import useRealAddress from "next-common/utils/hooks/useRealAddress"; import SummaryLayout from "next-common/components/summary/layout/layout"; import SummaryItem from "next-common/components/summary/layout/item"; import Account from "next-common/components/account"; -import LoadableContent from "next-common/components/common/loadableContent"; import { InfoMessage } from "next-common/components/setting/styled"; -export default function JudgementSummary({ verified, pending, loading }) { +export default function JudgementSummary({ verified, pending }) { const address = useRealAddress(); return ( @@ -16,18 +15,14 @@ export default function JudgementSummary({ verified, pending, loading }) {
- - {verified} - + {verified} - - {pending} - + {pending}
- {!loading && pending === 0 && ( + {pending === 0 && ( All social account verifications are done. Our registrar will do a final check and give the judgement soon. diff --git a/packages/next-common/utils/consts/menu/people.jsx b/packages/next-common/utils/consts/menu/people.jsx index 21c28b1618..331cc2748d 100644 --- a/packages/next-common/utils/consts/menu/people.jsx +++ b/packages/next-common/utils/consts/menu/people.jsx @@ -37,7 +37,7 @@ export function getPeopleMenu({ isAdmin, hasActiveJudgementRequest } = {}) { pathname: "/people/registrars", icon: , }, - hasActiveJudgementRequest && { + { name: "Verifications", value: "verifications", pathname: "/people/verifications", @@ -47,6 +47,7 @@ export function getPeopleMenu({ isAdmin, hasActiveJudgementRequest } = {}) { "/people/verifications/auth/twitter", "/people/verifications/auth/github", ], + visible: hasActiveJudgementRequest, }, isAdmin && { name: "Judgement Requests",