diff --git a/apps/renderer/src/modules/power/my-wallet-section/index.tsx b/apps/renderer/src/modules/power/my-wallet-section/index.tsx
index 94901d8532..0b67388c72 100644
--- a/apps/renderer/src/modules/power/my-wallet-section/index.tsx
+++ b/apps/renderer/src/modules/power/my-wallet-section/index.tsx
@@ -3,7 +3,6 @@ import { Trans, useTranslation } from "react-i18next"
import { Button } from "~/components/ui/button"
import { CopyButton } from "~/components/ui/code-highlighter"
-import { Divider } from "~/components/ui/divider"
import { LoadingWithIcon } from "~/components/ui/loading"
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "~/components/ui/tooltip"
import { DAILY_CLAIM_AMOUNT } from "~/constants"
@@ -127,8 +126,6 @@ export const MyWalletSection = () => {
-
-
>
)
}
diff --git a/apps/renderer/src/modules/power/ranking/index.tsx b/apps/renderer/src/modules/power/ranking/index.tsx
new file mode 100644
index 0000000000..5b04d55924
--- /dev/null
+++ b/apps/renderer/src/modules/power/ranking/index.tsx
@@ -0,0 +1,126 @@
+import { Fragment } from "react"
+import { useTranslation } from "react-i18next"
+
+import { useWhoami } from "~/atoms/user"
+import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar"
+import { MotionButtonBase } from "~/components/ui/button"
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "~/components/ui/table"
+import { EllipsisHorizontalTextWithTooltip } from "~/components/ui/typography"
+import { replaceImgUrlIfNeed } from "~/lib/img-proxy"
+import { cn } from "~/lib/utils"
+import { usePresentUserProfileModal } from "~/modules/profile/hooks"
+import { SettingSectionTitle } from "~/modules/settings/section"
+import { Balance } from "~/modules/wallet/balance"
+import type { useWalletTransactions } from "~/queries/wallet"
+import { useWalletRanking } from "~/queries/wallet"
+
+const medals = ["", "🥇", "🥈", "🥉"]
+const rankNumber = (index: number) => {
+ if (index < medals.length) {
+ return {medals[index]}
+ }
+ return {index}
+}
+
+export const PowerRanking: Component = ({ className }) => {
+ const { t } = useTranslation("settings")
+ const ranking = useWalletRanking()
+
+ return (
+
+
+
+
+
+
+ #
+ {t("wallet.ranking.name")}
+ {t("wallet.ranking.power")}
+ {t("wallet.ranking.level")}
+
+
+
+ {ranking.data?.map((row, index) => {
+ const lastRow = ranking.data?.[index - 1]
+ const hasGap = lastRow?.rank && row.rank && row.rank - lastRow.rank !== 1
+
+ return (
+
+ {!!hasGap && (
+ <>
+
+ ...
+
+
+
+
+ >
+ )}
+
+ {!!row.rank && rankNumber(row.rank)}
+
+
+
+
+ {row.powerToken}
+
+ {row.level}
+
+
+ )
+ })}
+
+ ...
+
+
+
+
+
+ )
+}
+
+const UserRenderer = ({
+ user,
+}: {
+ user?: NonNullable["data"]>[number][
+ | "fromUser"
+ | "toUser"]
+}) => {
+ const { t } = useTranslation("settings")
+ const me = useWhoami()
+ const isMe = user?.id === me?.id
+
+ const name = isMe ? t("wallet.transactions.you") : user?.name || APP_NAME
+
+ const presentUserModal = usePresentUserProfileModal("drawer")
+ return (
+ {
+ if (user?.id) presentUserModal(user.id)
+ }}
+ className="flex w-full min-w-0 cursor-button items-center gap-2"
+ >
+
+
+ {name?.slice(0, 2)}
+
+
+
+
+ {isMe ? (
+ {t("wallet.transactions.you")}
+ ) : (
+ {name}
+ )}
+
+
+
+ )
+}
diff --git a/apps/renderer/src/pages/(main)/(layer)/(subview)/power/index.tsx b/apps/renderer/src/pages/(main)/(layer)/(subview)/power/index.tsx
index e0b33fae5e..d91affa3df 100644
--- a/apps/renderer/src/pages/(main)/(layer)/(subview)/power/index.tsx
+++ b/apps/renderer/src/pages/(main)/(layer)/(subview)/power/index.tsx
@@ -1,6 +1,8 @@
import { useTranslation } from "react-i18next"
+import { Divider } from "~/components/ui/divider"
import { MyWalletSection } from "~/modules/power/my-wallet-section"
+import { PowerRanking } from "~/modules/power/ranking"
import { TransactionsSection } from "~/modules/power/transaction-section"
import { useSubViewTitle } from "../hooks"
@@ -23,6 +25,10 @@ export function Component() {
+
+
+
+
)
diff --git a/apps/renderer/src/queries/wallet.tsx b/apps/renderer/src/queries/wallet.tsx
index 89d24f9b29..abc1b445d1 100644
--- a/apps/renderer/src/queries/wallet.tsx
+++ b/apps/renderer/src/queries/wallet.tsx
@@ -42,6 +42,20 @@ export const wallet = {
},
),
},
+
+ ranking: {
+ get: () =>
+ defineQuery(
+ ["wallet", "ranking"],
+ async () => {
+ const res = await apiClient.wallets.ranking.$get()
+ return res.data
+ },
+ {
+ rootKey: ["wallet", "ranking"],
+ },
+ ),
+ },
}
export const useWallet = () => useAuthQuery(wallet.get())
@@ -49,6 +63,8 @@ export const useWallet = () => useAuthQuery(wallet.get())
export const useWalletTransactions = (query: Parameters[0] = {}) =>
useAuthQuery(wallet.transactions.get(query))
+export const useWalletRanking = () => useAuthQuery(wallet.ranking.get())
+
export const useCreateWalletMutation = () =>
useMutation({
mutationKey: ["createWallet"],
diff --git a/locales/settings/en.json b/locales/settings/en.json
index c92397535e..ddc9fe6287 100644
--- a/locales/settings/en.json
+++ b/locales/settings/en.json
@@ -229,6 +229,11 @@
"wallet.create.description": "Create a free wallet to receive Power, which can be used to reward creators and also get rewarded for your content contributions.",
"wallet.power.dailyClaim": "You can claim {{amount}} free Power daily, which can be used to tip RSS entries on Follow.",
"wallet.power.description": "Power is an ERC-20 token on the {{blockchainName}} blockchain.",
+ "wallet.ranking.level": "Level",
+ "wallet.ranking.name": "Name",
+ "wallet.ranking.power": "Power",
+ "wallet.ranking.rank": "Rank",
+ "wallet.ranking.title": "Power Ranking",
"wallet.sidebar_title": "Power",
"wallet.transactions.amount": "Amount",
"wallet.transactions.date": "Date",
diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts
index aa6922d000..e985321a55 100644
--- a/packages/shared/src/hono.ts
+++ b/packages/shared/src/hono.ts
@@ -3923,6 +3923,22 @@ declare const wallets: drizzle_orm_pg_core.PgTableWithColumns<{
baseColumn: never;
generated: undefined;
}, {}, {}>;
+ powerToken: drizzle_orm_pg_core.PgColumn<{
+ name: "power_token";
+ tableName: "wallets";
+ dataType: "string";
+ columnType: "PgNumeric";
+ data: string;
+ driverParam: string;
+ notNull: true;
+ hasDefault: true;
+ isPrimaryKey: false;
+ isAutoincrement: false;
+ hasRuntimeDefault: false;
+ enumValues: undefined;
+ baseColumn: never;
+ generated: undefined;
+ }, {}, {}>;
dailyPowerToken: drizzle_orm_pg_core.PgColumn<{
name: "daily_power_token";
tableName: "wallets";
@@ -3955,6 +3971,86 @@ declare const wallets: drizzle_orm_pg_core.PgTableWithColumns<{
baseColumn: never;
generated: undefined;
}, {}, {}>;
+ rank: drizzle_orm_pg_core.PgColumn<{
+ name: "rank";
+ tableName: "wallets";
+ dataType: "number";
+ columnType: "PgInteger";
+ data: number;
+ driverParam: string | number;
+ notNull: false;
+ hasDefault: false;
+ isPrimaryKey: false;
+ isAutoincrement: false;
+ hasRuntimeDefault: false;
+ enumValues: undefined;
+ baseColumn: never;
+ generated: undefined;
+ }, {}, {}>;
+ level: drizzle_orm_pg_core.PgColumn<{
+ name: "level";
+ tableName: "wallets";
+ dataType: "number";
+ columnType: "PgInteger";
+ data: number;
+ driverParam: string | number;
+ notNull: false;
+ hasDefault: false;
+ isPrimaryKey: false;
+ isAutoincrement: false;
+ hasRuntimeDefault: false;
+ enumValues: undefined;
+ baseColumn: never;
+ generated: undefined;
+ }, {}, {}>;
+ preActivePoints: drizzle_orm_pg_core.PgColumn<{
+ name: "pre_active_points";
+ tableName: "wallets";
+ dataType: "number";
+ columnType: "PgInteger";
+ data: number;
+ driverParam: string | number;
+ notNull: false;
+ hasDefault: false;
+ isPrimaryKey: false;
+ isAutoincrement: false;
+ hasRuntimeDefault: false;
+ enumValues: undefined;
+ baseColumn: never;
+ generated: undefined;
+ }, {}, {}>;
+ activePoints: drizzle_orm_pg_core.PgColumn<{
+ name: "active_points";
+ tableName: "wallets";
+ dataType: "number";
+ columnType: "PgInteger";
+ data: number;
+ driverParam: string | number;
+ notNull: false;
+ hasDefault: false;
+ isPrimaryKey: false;
+ isAutoincrement: false;
+ hasRuntimeDefault: false;
+ enumValues: undefined;
+ baseColumn: never;
+ generated: undefined;
+ }, {}, {}>;
+ statusUpdatedAt: drizzle_orm_pg_core.PgColumn<{
+ name: "status_updated_at";
+ tableName: "wallets";
+ dataType: "date";
+ columnType: "PgTimestamp";
+ data: Date;
+ driverParam: string;
+ notNull: false;
+ hasDefault: false;
+ isPrimaryKey: false;
+ isAutoincrement: false;
+ hasRuntimeDefault: false;
+ enumValues: undefined;
+ baseColumn: never;
+ generated: undefined;
+ }, {}, {}>;
};
dialect: "pg";
}>;
@@ -3963,22 +4059,40 @@ declare const walletsOpenAPISchema: zod.ZodObject<{
address: zod.ZodNullable;
userId: zod.ZodString;
createdAt: zod.ZodString;
+ powerToken: zod.ZodString;
dailyPowerToken: zod.ZodString;
cashablePowerToken: zod.ZodString;
+ rank: zod.ZodNullable;
+ level: zod.ZodNullable;
+ preActivePoints: zod.ZodNullable;
+ activePoints: zod.ZodNullable;
+ statusUpdatedAt: zod.ZodNullable;
}, zod.UnknownKeysParam, zod.ZodTypeAny, {
createdAt: string;
userId: string;
addressIndex: number;
address: string | null;
+ powerToken: string;
dailyPowerToken: string;
cashablePowerToken: string;
+ rank: number | null;
+ level: number | null;
+ preActivePoints: number | null;
+ activePoints: number | null;
+ statusUpdatedAt: string | null;
}, {
createdAt: string;
userId: string;
addressIndex: number;
address: string | null;
+ powerToken: string;
dailyPowerToken: string;
cashablePowerToken: string;
+ rank: number | null;
+ level: number | null;
+ preActivePoints: number | null;
+ activePoints: number | null;
+ statusUpdatedAt: string | null;
}>;
declare const walletsRelations: drizzle_orm.Relations<"wallets", {
user: drizzle_orm.One<"user", true>;
@@ -4170,10 +4284,10 @@ declare const transactionsOpenAPISchema: zod.ZodObject<{
fromUserId: string | null;
toUserId: string | null;
hash: string;
+ powerToken: string;
toFeedId: string | null;
toListId: string | null;
toEntryId: string | null;
- powerToken: string;
comment: string | null;
}, {
type: "tip" | "mint" | "burn" | "withdraw" | "purchase";
@@ -4181,10 +4295,10 @@ declare const transactionsOpenAPISchema: zod.ZodObject<{
fromUserId: string | null;
toUserId: string | null;
hash: string;
+ powerToken: string;
toFeedId: string | null;
toListId: string | null;
toEntryId: string | null;
- powerToken: string;
comment: string | null;
}>;
declare const transactionsRelations: drizzle_orm.Relations<"transactions", {
@@ -4783,10 +4897,10 @@ declare const _routes: hono_hono_base.HonoBase