feat: tipping
This commit is contained in:
parent
be6968cf66
commit
e02d5ee735
|
|
@ -1,3 +1,3 @@
|
|||
<svg width="27" height="26" viewBox="0 0 27 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6336 1.69775C7.40594 1.69775 2.35742 6.74628 2.35742 12.9739C2.35742 19.2016 7.40594 24.2501 13.6336 24.2501C19.8613 24.2501 24.9098 19.2016 24.9098 12.9739C24.9098 6.74628 19.8613 1.69775 13.6336 1.69775ZM0.857422 12.9739C0.857422 5.91785 6.57752 0.197754 13.6336 0.197754C20.6897 0.197754 26.4098 5.91785 26.4098 12.9739C26.4098 20.03 20.6897 25.7501 13.6336 25.7501C6.57752 25.7501 0.857422 20.03 0.857422 12.9739ZM13.6447 6.28291C14.6115 5.17936 16.4248 5.87145 16.4248 7.32935V11.8061H17.918C19.2873 11.8061 20.0194 13.415 19.1126 14.4454L13.6186 20.6869C13.6184 20.6871 13.6188 20.6867 13.6186 20.6869C12.6519 21.7905 10.8378 21.0994 10.8378 19.6413V15.1645H9.34461C7.9761 15.1645 7.25819 13.5543 8.14626 12.5295L8.15005 12.5251L13.6439 6.28375C13.6442 6.28347 13.6444 6.28319 13.6447 6.28291ZM14.8136 7.24298C14.8069 7.24447 14.7934 7.24802 14.7728 7.27151L14.7713 7.27318L9.27869 13.5132C9.26099 13.5341 9.25731 13.5489 9.25607 13.559C9.25438 13.5729 9.25634 13.592 9.26553 13.6123C9.27472 13.6326 9.28741 13.6459 9.29777 13.6529C9.30511 13.6579 9.31748 13.6645 9.34461 13.6645H12.3378V19.6413C12.3378 19.672 12.3455 19.6838 12.35 19.69C12.3574 19.7 12.3722 19.7131 12.3951 19.7218C12.4181 19.7304 12.4374 19.7302 12.449 19.7277C12.4557 19.7262 12.4692 19.7226 12.4898 19.6991L12.4912 19.6975L17.9865 13.4544C17.9866 13.4544 17.9865 13.4544 17.9865 13.4544C18.0064 13.4318 18.01 13.4164 18.0112 13.4074C18.0128 13.3948 18.0111 13.3773 18.0025 13.3584C17.9939 13.3394 17.9818 13.3264 17.9709 13.3191C17.963 13.3137 17.9486 13.3061 17.918 13.3061H14.9248V7.32935C14.9248 7.29873 14.9171 7.28688 14.9126 7.28073C14.9052 7.27069 14.8904 7.25754 14.8674 7.24888C14.8445 7.24023 14.8251 7.24044 14.8136 7.24298Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.6336 1.69775C7.40594 1.69775 2.35742 6.74628 2.35742 12.9739C2.35742 19.2016 7.40594 24.2501 13.6336 24.2501C19.8613 24.2501 24.9098 19.2016 24.9098 12.9739C24.9098 6.74628 19.8613 1.69775 13.6336 1.69775ZM0.857422 12.9739C0.857422 5.91785 6.57752 0.197754 13.6336 0.197754C20.6897 0.197754 26.4098 5.91785 26.4098 12.9739C26.4098 20.03 20.6897 25.7501 13.6336 25.7501C6.57752 25.7501 0.857422 20.03 0.857422 12.9739ZM13.6447 6.28291C14.6115 5.17936 16.4248 5.87145 16.4248 7.32935V11.8061H17.918C19.2873 11.8061 20.0194 13.415 19.1126 14.4454L13.6186 20.6869C13.6184 20.6871 13.6188 20.6867 13.6186 20.6869C12.6519 21.7905 10.8378 21.0994 10.8378 19.6413V15.1645H9.34461C7.9761 15.1645 7.25819 13.5543 8.14626 12.5295L8.15005 12.5251L13.6439 6.28375C13.6442 6.28347 13.6444 6.28319 13.6447 6.28291ZM14.8136 7.24298C14.8069 7.24447 14.7934 7.24802 14.7728 7.27151L14.7713 7.27318L9.27869 13.5132C9.26099 13.5341 9.25731 13.5489 9.25607 13.559C9.25438 13.5729 9.25634 13.592 9.26553 13.6123C9.27472 13.6326 9.28741 13.6459 9.29777 13.6529C9.30511 13.6579 9.31748 13.6645 9.34461 13.6645H12.3378V19.6413C12.3378 19.672 12.3455 19.6838 12.35 19.69C12.3574 19.7 12.3722 19.7131 12.3951 19.7218C12.4181 19.7304 12.4374 19.7302 12.449 19.7277C12.4557 19.7262 12.4692 19.7226 12.4898 19.6991L12.4912 19.6975L17.9865 13.4544C17.9866 13.4544 17.9865 13.4544 17.9865 13.4544C18.0064 13.4318 18.01 13.4164 18.0112 13.4074C18.0128 13.3948 18.0111 13.3773 18.0025 13.3584C17.9939 13.3394 17.9818 13.3264 17.9709 13.3191C17.963 13.3137 17.9486 13.3061 17.918 13.3061H14.9248V7.32935C14.9248 7.29873 14.9171 7.28688 14.9126 7.28073C14.9052 7.27069 14.8904 7.25754 14.8674 7.24888C14.8445 7.24023 14.8251 7.24044 14.8136 7.24298Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
|
@ -8,30 +8,39 @@ export const Balance = ({
|
|||
className,
|
||||
precision = 2,
|
||||
withSuffix = false,
|
||||
withTooltip = true,
|
||||
}: {
|
||||
/** The token balance in wei. */
|
||||
children: bigint
|
||||
children: bigint | string
|
||||
className?: string
|
||||
precision?: number
|
||||
withSuffix?: boolean
|
||||
withTooltip?: boolean
|
||||
}) => {
|
||||
const from = [BigInt(children), 18] as const
|
||||
const formatted = format(from, { digits: precision, trailingZeros: true })
|
||||
const formattedFull = format(from, { digits: 18, trailingZeros: true })
|
||||
const n = [BigInt(children), 18] as const
|
||||
const formatted = format(n, { digits: precision, trailingZeros: true })
|
||||
const formattedFull = format(n, { digits: 18, trailingZeros: true })
|
||||
|
||||
const Content = (
|
||||
<div className={cn("font-mono tabular-nums", className)}>
|
||||
{formatted}
|
||||
{" "}
|
||||
{withSuffix && <span>POWER</span>}
|
||||
</div>
|
||||
)
|
||||
|
||||
if (!withTooltip) return Content
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<span className={cn("font-mono tabular-nums", className)}>
|
||||
{formatted}
|
||||
{" "}
|
||||
{withSuffix && <span>$POWER</span>}
|
||||
</span>
|
||||
<TooltipTrigger asChild>
|
||||
{Content}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="font-mono text-sm">
|
||||
<span className="font-bold tabular-nums">{formattedFull}</span>
|
||||
{" "}
|
||||
<span>$POWER</span>
|
||||
<span>POWER</span>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
|
|||
data: {
|
||||
type: "tip" | "mint" | "burn" | "withdraw";
|
||||
createdAt: string;
|
||||
powerToken: bigint;
|
||||
powerToken: string;
|
||||
hash: string;
|
||||
fromUserId: string | null;
|
||||
toUserId: string | null;
|
||||
|
|
@ -124,7 +124,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
|
|||
data: {
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
powerToken: bigint;
|
||||
powerToken: string;
|
||||
addressIndex: number;
|
||||
address: string | null;
|
||||
}[];
|
||||
|
|
@ -139,7 +139,7 @@ declare const routes: hono_hono_base.HonoBase<hono_types.BlankEnv, {
|
|||
data: {
|
||||
userId: string;
|
||||
createdAt: string;
|
||||
powerToken: bigint;
|
||||
powerToken: string;
|
||||
addressIndex: number;
|
||||
address: string | null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -108,8 +108,6 @@ export const useEntryActions = ({
|
|||
refetchOnWindowFocus: false,
|
||||
})
|
||||
|
||||
// const canTip = Boolean(entry?.feeds.ownerUserId)
|
||||
// comment out L119-122 to work ⬇️
|
||||
const openTipModal = useTipModal({
|
||||
userId: entry?.feeds.ownerUserId ?? undefined,
|
||||
feedId: entry?.feeds.id ?? undefined,
|
||||
|
|
@ -129,7 +127,6 @@ export const useEntryActions = ({
|
|||
shortcut: shortcuts.entry.tip.key,
|
||||
name: `Tip`,
|
||||
className: "i-mgc-power-outline",
|
||||
// disabled: !canTip,
|
||||
onClick: () => {
|
||||
nextFrame(openTipModal)
|
||||
},
|
||||
|
|
@ -239,6 +236,7 @@ export const useEntryActions = ({
|
|||
collect,
|
||||
entry,
|
||||
read,
|
||||
openTipModal,
|
||||
uncollect,
|
||||
unread,
|
||||
view,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const ClaimDailyReward = () => {
|
|||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{canClaim ?
|
||||
"Claim your daily reward (2.00 PowerTokens) now!" :
|
||||
"Claim your daily reward (2.00 POWER) now!" :
|
||||
`You can claim your daily reward in ${hour}:${minute}:${second}.`}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export const CreateWallet = () => {
|
|||
<p className="my-2 text-theme-inactive">
|
||||
Create a free wallet to receive
|
||||
{" "}
|
||||
<strong>PowerTokens</strong>
|
||||
<strong>$POWER</strong>
|
||||
, which can
|
||||
be used to reward creators and also get rewarded for your content
|
||||
contributions.
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ export const useTipModal = ({ userId, feedId }: { userId?: string, feedId?: stri
|
|||
const { present } = useModalStack()
|
||||
|
||||
return useCallback(() => {
|
||||
if (!userId) {
|
||||
if (!userId && !feedId) {
|
||||
// this should not happen unless there is a bug in the code
|
||||
toast.error("You must have a user to tip.")
|
||||
toast.error("Invalid user id or feed id")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,50 @@
|
|||
import { useUser } from "@renderer/atoms/user"
|
||||
import { StyledButton } from "@renderer/components/ui/button"
|
||||
import { Divider } from "@renderer/components/ui/divider"
|
||||
import { Input } from "@renderer/components/ui/input"
|
||||
import { LoadingCircle } from "@renderer/components/ui/loading"
|
||||
import { useWalletTipMutation, useWalletTransactions } from "@renderer/queries/wallet"
|
||||
import { useCurrentModal } from "@renderer/components/ui/modal"
|
||||
import { Balance } from "@renderer/components/ui/wallet/balance"
|
||||
import { nextFrame } from "@renderer/lib/dom"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { useWallet, useWalletTipMutation, useWalletTransactions } from "@renderer/queries/wallet"
|
||||
import { from, subtract, toNumber } from "dnum"
|
||||
import type { FC } from "react"
|
||||
import { useState } from "react"
|
||||
|
||||
import { useSettingModal } from "../settings/modal/hooks"
|
||||
|
||||
const DEFAULT_RECOMMENDED_TIP = 0.1
|
||||
|
||||
export const TipModalContent: FC<{
|
||||
userId: string
|
||||
feedId?: string
|
||||
}> = ({ userId, feedId }) => {
|
||||
const transationsQuery = useWalletTransactions({ toUserId: userId, toFeedId: feedId })
|
||||
const user = useUser()
|
||||
const myWallet = useWallet({ userId: user.id })
|
||||
const myWalletData = myWallet.data?.[0]
|
||||
|
||||
const balanceBigInt = BigInt(myWalletData?.powerToken ?? 0)
|
||||
const balanceNumber = toNumber(from(balanceBigInt, 18), { digits: 2, trailingZeros: true })
|
||||
|
||||
const transactionsQuery = useWalletTransactions({ toUserId: userId, toFeedId: feedId })
|
||||
|
||||
const tipMutation = useWalletTipMutation()
|
||||
|
||||
if (transationsQuery.isPending) {
|
||||
const [amount, setAmount] = useState(DEFAULT_RECOMMENDED_TIP > balanceNumber ? balanceNumber : DEFAULT_RECOMMENDED_TIP)
|
||||
|
||||
const amountBigInt = from(amount, 18)[0]
|
||||
const remainingBalance = subtract(balanceBigInt, amountBigInt)[0]
|
||||
|
||||
const wrongNumberRange = amountBigInt > balanceBigInt || amountBigInt <= BigInt(0)
|
||||
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
|
||||
const { dismiss } = useCurrentModal()
|
||||
|
||||
const settingModalPresent = useSettingModal()
|
||||
|
||||
if (transactionsQuery.isPending || myWallet.isPending) {
|
||||
return (
|
||||
<div className="center h-32 w-[650px]">
|
||||
<LoadingCircle size="large" />
|
||||
|
|
@ -19,24 +52,112 @@ export const TipModalContent: FC<{
|
|||
)
|
||||
}
|
||||
|
||||
if (transationsQuery.error) {
|
||||
return <div>Failed to load transactions history.</div>
|
||||
if (!myWalletData) {
|
||||
return (
|
||||
<div className="flex w-[80vw] max-w-[350px] flex-col gap-5">
|
||||
<p className="text-sm text-theme-foreground/80">
|
||||
You don't have a wallet yet. Please create a wallet to tip.
|
||||
</p>
|
||||
<div className="flex justify-end">
|
||||
<StyledButton
|
||||
variant="primary"
|
||||
onClick={() => nextFrame(() => settingModalPresent("wallet"))}
|
||||
>
|
||||
Create For Free
|
||||
</StyledButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// if (transactionsQuery.error) {
|
||||
// return <div>Failed to load transactions history.</div>
|
||||
// }
|
||||
|
||||
if (tipMutation.isSuccess) {
|
||||
return (
|
||||
<div className="flex w-[80vw] max-w-[350px] flex-col gap-5">
|
||||
<p className="text-sm text-theme-foreground/80">
|
||||
Tip sent successfully! Thank you for your support.
|
||||
</p>
|
||||
<p>
|
||||
<Balance withSuffix>{amountBigInt}</Balance>
|
||||
{" "}
|
||||
has been sent to the author.
|
||||
</p>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<StyledButton
|
||||
variant="primary"
|
||||
onClick={() => dismiss()}
|
||||
>
|
||||
OK
|
||||
</StyledButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-[650px] max-w-full">
|
||||
{/* // TODO: */}
|
||||
<div className="mt-3 flex justify-end">
|
||||
<div className="flex w-[80vw] max-w-[350px] flex-col gap-5">
|
||||
<p className="text-sm text-theme-foreground/80">
|
||||
⭐ Tip to show your support! Your tip will be added to the author's wallet.
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col justify-center gap-y-2">
|
||||
<div className="font-bold">Amount</div>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={balanceNumber}
|
||||
step={0.01}
|
||||
placeholder="Enter amount"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(Number(e.target.value))}
|
||||
/>
|
||||
|
||||
<Divider className="my-2" />
|
||||
|
||||
<div className="text-sm text-theme-foreground/80">
|
||||
<div className="flex flex-row gap-x-2">
|
||||
<div className="font-bold">Balance</div>
|
||||
<Balance withSuffix>{balanceBigInt}</Balance>
|
||||
</div>
|
||||
<div className={cn("flex flex-row gap-x-2", { "text-red-500": wrongNumberRange })}>
|
||||
<div className="font-bold">Tipping</div>
|
||||
<Balance withSuffix>{amountBigInt}</Balance>
|
||||
</div>
|
||||
<div className={cn("flex flex-row gap-x-2", { "text-red-500": wrongNumberRange })}>
|
||||
<div className="font-bold">Remaining</div>
|
||||
<Balance withSuffix>{remainingBalance}</Balance>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<StyledButton
|
||||
disabled={tipMutation.isSuccess || tipMutation.isPending}
|
||||
disabled={tipMutation.isSuccess || tipMutation.isPending || wrongNumberRange}
|
||||
isLoading={tipMutation.isPending}
|
||||
onClick={() => tipMutation.mutate({ userId, feedId, amount: "0" })}
|
||||
onClick={() => {
|
||||
if (tipMutation.isPending) return
|
||||
if (!showConfirm) {
|
||||
setShowConfirm(true)
|
||||
return
|
||||
}
|
||||
tipMutation.mutate({ userId, feedId, amount: amountBigInt.toString() })
|
||||
setShowConfirm(false)
|
||||
}}
|
||||
variant={tipMutation.isSuccess ? "plain" : "primary"}
|
||||
>
|
||||
{tipMutation.isSuccess && (
|
||||
<i className="i-mgc-check-circle-filled mr-2 bg-green-500" />
|
||||
)}
|
||||
Claim
|
||||
{showConfirm ? (
|
||||
<div className="flex flex-row justify-center gap-x-1">
|
||||
Confirm Tip
|
||||
<Balance withTooltip={false} withSuffix>{amountBigInt}</Balance>
|
||||
</div>
|
||||
) : "Tip Now"}
|
||||
</StyledButton>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
|
||||
.shadow-modal {
|
||||
@apply shadow-2xl shadow-stone-300 dark:shadow-stone-800;
|
||||
@apply shadow-2xl shadow-stone-300 dark:shadow-stone-900;
|
||||
}
|
||||
/* Utils */
|
||||
.no-animation {
|
||||
|
|
|
|||
Loading…
Reference in New Issue