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)} + + +
@@ -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 = () => ( - - - - - Comming soon... - -) +export const WithdrawButton = () => { + const { present } = useModalStack() + + const onClick = () => { + present({ + title: "Withdraw Power", + content: ({ dismiss }) => , + }) + } + + return ( + + ) +} + +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. +
+
+ + ( + + Your Ethereum Address + + + + + + )} + /> + ( + + Amount + + field.onChange(value.target.valueAsNumber)} + /> + + + + )} + /> +
+ +
+ + + + ) +} 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"