From 9088aea6517b71fcd2ed387358fb9e8e97a962a9 Mon Sep 17 00:00:00 2001
From: DIYgod
Date: Fri, 13 Sep 2024 12:40:59 +0800
Subject: [PATCH] feat: 10x token (#354)
* feat: 10x token
* feat: power withdraw modal
* feat: update token text
---
src/hono.ts | 34 +++-
src/renderer/src/constants/app.tsx | 3 +
src/renderer/src/modules/feed-column/list.tsx | 1 -
src/renderer/src/modules/panel/cmdk.tsx | 2 +-
.../src/modules/settings/tabs/invitations.tsx | 7 +-
.../my-wallet-section/claim-daily-reward.tsx | 5 +-
.../tabs/wallet/my-wallet-section/index.tsx | 66 ++++----
.../wallet/my-wallet-section/withdraw.tsx | 147 ++++++++++++++++--
src/renderer/src/modules/wallet/balance.tsx | 8 +-
src/renderer/src/modules/wallet/tip-modal.tsx | 13 +-
src/renderer/src/providers/i18n-provider.tsx | 2 +-
src/renderer/src/store/search/index.ts | 6 +-
12 files changed, 229 insertions(+), 65 deletions(-)
diff --git a/src/hono.ts b/src/hono.ts
index db58f7b3d..ed8b269d1 100644
--- a/src/hono.ts
+++ b/src/hono.ts
@@ -4362,7 +4362,7 @@ declare const _routes: hono_hono_base.HonoBase<
input: {
json: {
entryId: string
- amount: "1000000000000000000" | "2000000000000000000"
+ amount: string
}
}
output: {
@@ -4465,7 +4465,7 @@ declare const _routes: hono_hono_base.HonoBase<
status: 200
}
}
- "/wallets/transactions/withdrawable": {
+ "/wallets/transactions/feed-withdrawable": {
$get: {
input: {
query: {
@@ -4484,7 +4484,7 @@ declare const _routes: hono_hono_base.HonoBase<
status: 200
}
}
- "/wallets/transactions/withdraw": {
+ "/wallets/transactions/feed-withdraw": {
$post: {
input: {
json: {
@@ -4501,6 +4501,24 @@ declare const _routes: hono_hono_base.HonoBase<
status: 200
}
}
+ "/wallets/transactions/withdraw": {
+ $post: {
+ input: {
+ json: {
+ address: string
+ amount: string
+ }
+ }
+ output: {
+ code: 0
+ data: {
+ transactionHash: string
+ }
+ }
+ outputFormat: "json" | "text"
+ status: 200
+ }
+ }
"/wallets/transactions/claim-check": {
$get: {
input: {}
@@ -4544,6 +4562,16 @@ declare const _routes: hono_hono_base.HonoBase<
status: 200
}
}
+ "/wallets/refresh": {
+ $post: {
+ input: {}
+ output: {
+ code: 0
+ }
+ outputFormat: "json" | "text"
+ status: 200
+ }
+ }
} & {
"/subscriptions": {
$get: {
diff --git a/src/renderer/src/constants/app.tsx b/src/renderer/src/constants/app.tsx
index 55886bf09..341eee264 100644
--- a/src/renderer/src/constants/app.tsx
+++ b/src/renderer/src/constants/app.tsx
@@ -9,3 +9,6 @@ export const QUERY_PERSIST_KEY = getStorageNS("REACT_QUERY_OFFLINE_CACHE")
export const ROUTE_FEED_PENDING = "all"
export const ROUTE_ENTRY_PENDING = "pending"
export const ROUTE_FEED_IN_FOLDER = "folder-"
+
+export const DAILY_CLAIM_AMOUNT = "20"
+export const INVITATION_PRICE = "100"
diff --git a/src/renderer/src/modules/feed-column/list.tsx b/src/renderer/src/modules/feed-column/list.tsx
index ec8915014..53fedddd9 100644
--- a/src/renderer/src/modules/feed-column/list.tsx
+++ b/src/renderer/src/modules/feed-column/list.tsx
@@ -1,4 +1,3 @@
-
import * as HoverCard from "@radix-ui/react-hover-card"
import { useUISettingKey } from "@renderer/atoms/settings/ui"
import { ScrollArea } from "@renderer/components/ui/scroll-area"
diff --git a/src/renderer/src/modules/panel/cmdk.tsx b/src/renderer/src/modules/panel/cmdk.tsx
index 1e1aaf181..5c5ba03d9 100644
--- a/src/renderer/src/modules/panel/cmdk.tsx
+++ b/src/renderer/src/modules/panel/cmdk.tsx
@@ -267,7 +267,7 @@ const SearchResultCount: FC<{
const searchInstance = React.useContext(SearchCmdKContext)
const hasKeyword = useSearchStore((s) => !!s.keyword)
const searchType = useSearchStore((s) => s.searchType)
- // eslint-disable-next-line react-compiler/react-compiler
+
const recordCountPromise = useMemo(async () => {
let count = 0
const counts = await searchInstance?.then((s) => s.counts)
diff --git a/src/renderer/src/modules/settings/tabs/invitations.tsx b/src/renderer/src/modules/settings/tabs/invitations.tsx
index 609e1fc44..669bae63d 100644
--- a/src/renderer/src/modules/settings/tabs/invitations.tsx
+++ b/src/renderer/src/modules/settings/tabs/invitations.tsx
@@ -19,6 +19,7 @@ import {
TooltipPortal,
TooltipTrigger,
} from "@renderer/components/ui/tooltip"
+import { INVITATION_PRICE } from "@renderer/constants"
import { useAuthQuery } from "@renderer/hooks/common"
import { apiClient } from "@renderer/lib/api-fetch"
import { toastFetchError } from "@renderer/lib/error-parser"
@@ -42,8 +43,8 @@ export const SettingInvitations = () => {
use.
- You can spend 10
-
+ You can spend {INVITATION_PRICE}
+
Power to generate an invitation code for your friends.
@@ -153,7 +154,7 @@ const ConfirmModalContent = ({ dismiss }: { dismiss: () => void }) => {
return (
<>
- Generating an invitation code will cost you 10
+ Generating an invitation code will cost you {INVITATION_PRICE}
Power. Do you want to continue?
diff --git a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx
index 77a66f558..b6bf7e3ba 100644
--- a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx
+++ b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/claim-daily-reward.tsx
@@ -1,6 +1,7 @@
import { TooltipTrigger } from "@radix-ui/react-tooltip"
import { Button } from "@renderer/components/ui/button"
import { Tooltip, TooltipContent } from "@renderer/components/ui/tooltip"
+import { DAILY_CLAIM_AMOUNT } from "@renderer/constants"
import { useClaimCheck, useClaimWalletDailyRewardMutation } from "@renderer/queries/wallet"
export const ClaimDailyReward = () => {
@@ -22,7 +23,9 @@ export const ClaimDailyReward = () => {
- {canClaim ? "Claim your 2 Daily Power now!" : `You have already claimed today.`}
+ {canClaim
+ ? `Claim your ${DAILY_CLAIM_AMOUNT} Daily Power now!`
+ : `You have already claimed today.`}
)
diff --git a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx
index f8be38142..ede5624ed 100644
--- a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx
+++ b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/index.tsx
@@ -1,4 +1,5 @@
import { useWhoami } from "@renderer/atoms/user"
+import { Button } from "@renderer/components/ui/button"
import { CopyButton } from "@renderer/components/ui/code-highlighter"
import { LoadingWithIcon } from "@renderer/components/ui/loading"
import {
@@ -7,9 +8,13 @@ import {
TooltipPortal,
TooltipTrigger,
} from "@renderer/components/ui/tooltip"
+import { DAILY_CLAIM_AMOUNT } from "@renderer/constants"
+import { apiClient } from "@renderer/lib/api-fetch"
+import { cn } from "@renderer/lib/utils"
import { SettingSectionTitle } from "@renderer/modules/settings/section"
import { Balance } from "@renderer/modules/wallet/balance"
-import { useWallet } from "@renderer/queries/wallet"
+import { useWallet, wallet as walletActions } from "@renderer/queries/wallet"
+import { useMutation } from "@tanstack/react-query"
import { ClaimDailyReward } from "./claim-daily-reward"
import { CreateWallet } from "./create-wallet"
@@ -20,6 +25,15 @@ export const MyWalletSection = () => {
const wallet = useWallet({ userId: user?.id })
const myWallet = wallet.data?.[0]
+ const refreshMutation = useMutation({
+ mutationFn: async () => {
+ await apiClient.wallets.refresh.$post()
+ },
+ onSuccess: () => {
+ walletActions.get().invalidate()
+ },
+ })
+
if (wallet.isPending) {
return (
@@ -51,7 +65,10 @@ export const MyWalletSection = () => {
.
-
You can claim 2 free Power daily, which can be used to tip RSS entries on Follow.
+
+ You can claim {DAILY_CLAIM_AMOUNT} free Power daily, which can be used to tip RSS
+ entries on Follow.
+
@@ -66,9 +83,23 @@ export const MyWalletSection = () => {
-
- {BigInt(myWallet.dailyPowerToken || 0n) + BigInt(myWallet.cashablePowerToken || 0n)}
-
+
+
+ {BigInt(myWallet.dailyPowerToken || 0n) + BigInt(myWallet.cashablePowerToken || 0n)}
+
+ refreshMutation.mutate()}
+ disabled={refreshMutation.isPending}
+ >
+
+
+
@@ -77,25 +108,8 @@ export const MyWalletSection = () => {
-
- Daily Power
-
-
- {myWallet.dailyPowerToken}
-
-
-
-
- 1. Daily Power can only be used for tipping others.
- 2. You can claim 2 free Daily Power daily.
-
-
-
-
-
-
-
- Cashable Power
+
+ Withdrawable
{myWallet.cashablePowerToken}
@@ -103,9 +117,9 @@ export const MyWalletSection = () => {
- 1. Cashable Power can be withdrawn to your wallet for trading.
- 2. Cashable Power is the Power you have recharged and the tips you have received.
+ Withdrawable Power includes both the tips you've received and the Power you've
+ recharged.
diff --git a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/withdraw.tsx b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/withdraw.tsx
index 979fde6a0..f38ca64a8 100644
--- a/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/withdraw.tsx
+++ b/src/renderer/src/modules/settings/tabs/wallet/my-wallet-section/withdraw.tsx
@@ -1,14 +1,137 @@
-import { TooltipTrigger } from "@radix-ui/react-tooltip"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useWhoami } from "@renderer/atoms/user"
import { Button } from "@renderer/components/ui/button"
-import { Tooltip, TooltipContent } from "@renderer/components/ui/tooltip"
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@renderer/components/ui/form"
+import { Input } from "@renderer/components/ui/input"
+import { useModalStack } from "@renderer/components/ui/modal"
+import { apiClient } from "@renderer/lib/api-fetch"
+import { cn } from "@renderer/lib/utils"
+import { Balance } from "@renderer/modules/wallet/balance"
+import { useWallet, wallet as walletActions } from "@renderer/queries/wallet"
+import { useMutation } from "@tanstack/react-query"
+import { from, toNumber } from "dnum"
+import { useEffect } from "react"
+import { useForm } from "react-hook-form"
+import { toast } from "sonner"
+import { z } from "zod"
-export const WithdrawButton = () => (
-
-
-
- Withdraw
-
-
- Comming soon...
-
-)
+export const WithdrawButton = () => {
+ const { present } = useModalStack()
+
+ const onClick = () => {
+ present({
+ title: "Withdraw Power",
+ content: ({ dismiss }) => ,
+ })
+ }
+
+ return (
+
+ Withdraw
+
+ )
+}
+
+const WithdrawModalContent = ({ dismiss }: { dismiss: () => void }) => {
+ const user = useWhoami()
+ const wallet = useWallet({ userId: user?.id })
+ const cashablePowerTokenBigInt = [BigInt(wallet.data?.[0].cashablePowerToken || 0n), 18] as const
+ const cashablePowerTokenNumber = toNumber(cashablePowerTokenBigInt)
+
+ const formSchema = z.object({
+ address: z.string().startsWith("0x").length(42),
+ amount: z.number().min(0).max(cashablePowerTokenNumber),
+ })
+
+ const form = useForm>({
+ resolver: zodResolver(formSchema),
+ })
+
+ const mutation = useMutation({
+ mutationFn: async ({ address, amount }: { address: string; amount: number }) => {
+ const amountBigInt = from(amount, 18)[0]
+ await apiClient.wallets.transactions.withdraw.$post({
+ json: {
+ address,
+ amount: amountBigInt.toString(),
+ },
+ })
+ },
+ })
+
+ const onSubmit = (values: z.infer) => {
+ mutation.mutate(values)
+ }
+
+ useEffect(() => {
+ if (mutation.isError) {
+ toast.error(`Withdrawal failed: ${mutation.error?.message}`)
+ }
+ }, [mutation.isError])
+
+ useEffect(() => {
+ if (mutation.isSuccess) {
+ toast.success("Withdrawal successful!")
+ walletActions.get().invalidate()
+ walletActions.transactions.get().invalidate()
+ dismiss()
+ }
+ }, [mutation.isSuccess])
+
+ return (
+ <>
+
+ You have{" "}
+ {wallet.data?.[0].cashablePowerToken || "0"} {" "}
+ withdrawable Power in your wallet.
+
+
+
+ >
+ )
+}
diff --git a/src/renderer/src/modules/wallet/balance.tsx b/src/renderer/src/modules/wallet/balance.tsx
index d74697325..3fbcd9684 100644
--- a/src/renderer/src/modules/wallet/balance.tsx
+++ b/src/renderer/src/modules/wallet/balance.tsx
@@ -22,8 +22,8 @@ export const Balance = ({
const formattedFull = format(n, { digits: 18, trailingZeros: true })
const Content = (
-
- {formatted} {withSuffix &&
POWER }
+
+ {formatted} {withSuffix && Power }
)
@@ -33,8 +33,8 @@ export const Balance = ({
{Content}
-
-
{formattedFull} POWER
+
+ {formattedFull} Power
diff --git a/src/renderer/src/modules/wallet/tip-modal.tsx b/src/renderer/src/modules/wallet/tip-modal.tsx
index 49a27b47e..d20fcf5f6 100644
--- a/src/renderer/src/modules/wallet/tip-modal.tsx
+++ b/src/renderer/src/modules/wallet/tip-modal.tsx
@@ -16,7 +16,7 @@ import { useFeedClaimModal } from "../claim"
import { useSettingModal } from "../settings/modal/hooks-hack"
import { Balance } from "./balance"
-const DEFAULT_RECOMMENDED_TIP = 1
+const DEFAULT_RECOMMENDED_TIP = 10
const useMyWallet = () => {
const user = useWhoami()
@@ -56,7 +56,7 @@ const TipModalContent_: FC<{
const tipMutation = useWalletTipMutation()
- const [amount, setAmount] = useState<1 | 2>(DEFAULT_RECOMMENDED_TIP)
+ const [amount, setAmount] = useState
(DEFAULT_RECOMMENDED_TIP)
const amountBigInt = from(amount, 18)[0]
@@ -146,13 +146,10 @@ const TipModalContent_: FC<{
Amount
- setAmount(Number(value) as 1 | 2)}
- >
+ setAmount(Number(value))}>
-
-
+
+
diff --git a/src/renderer/src/providers/i18n-provider.tsx b/src/renderer/src/providers/i18n-provider.tsx
index f8707e4a2..d02b24d16 100644
--- a/src/renderer/src/providers/i18n-provider.tsx
+++ b/src/renderer/src/providers/i18n-provider.tsx
@@ -83,7 +83,7 @@ export const I18nProvider: FC = ({ children }) => {
const [currentI18NInstance, update] = useAtom(i18nAtom)
if (import.meta.env.DEV)
- // eslint-disable-next-line react-compiler/react-compiler, react-hooks/rules-of-hooks
+ // eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(
() =>
EventBus.subscribe("I18N_UPDATE", () => {
diff --git a/src/renderer/src/store/search/index.ts b/src/renderer/src/store/search/index.ts
index 5d3a249a1..98dcd29b1 100644
--- a/src/renderer/src/store/search/index.ts
+++ b/src/renderer/src/store/search/index.ts
@@ -1,9 +1,5 @@
import type { EntryModel } from "@renderer/models"
-import {
- EntryService,
- FeedService,
- SubscriptionService,
-} from "@renderer/services"
+import { EntryService, FeedService, SubscriptionService } from "@renderer/services"
import type { IFuseOptions } from "fuse.js"
import Fuse from "fuse.js"