feat: daily reward display

This commit is contained in:
DIYgod 2024-10-17 23:31:42 +08:00
parent b6cb042ed9
commit 78db8ad50f
No known key found for this signature in database
11 changed files with 106 additions and 84 deletions

View File

@ -4,10 +4,9 @@ import { Trans, useTranslation } from "react-i18next"
import { Button } from "~/components/ui/button"
import { Tooltip, TooltipContent } from "~/components/ui/tooltip"
import { DAILY_CLAIM_AMOUNT } from "~/constants"
import { Level } from "~/modules/wallet/level"
import { useClaimCheck, useClaimWalletDailyRewardMutation } from "~/queries/wallet"
export const ClaimDailyReward = ({ level }: { level: number }) => {
export const ClaimDailyReward = () => {
const mutation = useClaimWalletDailyRewardMutation()
const { t } = useTranslation("settings")
@ -24,7 +23,6 @@ export const ClaimDailyReward = ({ level }: { level: number }) => {
disabled={!canClaim}
>
{canClaim ? t("wallet.claim.button.claim") : t("wallet.claim.button.claimed")}
<Level className="ml-3" level={level} hideIcon />
</Button>
</TooltipTrigger>
<TooltipContent>

View File

@ -9,6 +9,7 @@ import { DAILY_CLAIM_AMOUNT } from "~/constants"
import { apiClient } from "~/lib/api-fetch"
import { cn } from "~/lib/utils"
import { SettingSectionTitle } from "~/modules/settings/section"
import { ActivityPoints } from "~/modules/wallet/activity-points"
import { Balance } from "~/modules/wallet/balance"
import { Level } from "~/modules/wallet/level"
import { useWallet, wallet as walletActions } from "~/queries/wallet"
@ -82,50 +83,79 @@ export const MyWalletSection = () => {
className="p-1 opacity-0 duration-200 group-hover:opacity-100 [&_i]:size-2.5"
/>
</div>
<SettingSectionTitle title={t("wallet.balance.level")} margin="compact" />
<Level level={myWallet.level?.level || 0} />
<SettingSectionTitle title={t("wallet.balance.title")} margin="compact" />
<div className="mb-2 flex items-end justify-between">
<div className="flex items-center gap-1">
<Balance className="text-xl font-bold text-accent">
{BigInt(myWallet.dailyPowerToken || 0n) + BigInt(myWallet.cashablePowerToken || 0n)}
</Balance>
<Button
variant="ghost"
onClick={() => refreshMutation.mutate()}
disabled={refreshMutation.isPending}
>
<i
className={cn(
"i-mgc-refresh-2-cute-re",
refreshMutation.isPending && "animate-spin",
)}
/>
</Button>
<div className="mb-2 flex items-center justify-between">
<div>
<div className="flex items-center gap-1">
<Balance className="text-xl font-bold text-accent">
{BigInt(myWallet.powerToken || 0n)}
</Balance>
<Button
variant="ghost"
onClick={() => refreshMutation.mutate()}
disabled={refreshMutation.isPending}
>
<i
className={cn(
"i-mgc-refresh-2-cute-re",
refreshMutation.isPending && "animate-spin",
)}
/>
</Button>
</div>
<Tooltip>
<TooltipTrigger className="mt-1 block">
<div className="flex flex-row items-center gap-x-2 text-xs">
<span className="flex items-center gap-1 text-left">
{t("wallet.balance.withdrawable")} <i className="i-mgc-question-cute-re" />
</span>
<Balance className="center text-[12px] font-medium">
{myWallet.cashablePowerToken}
</Balance>
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent align="start" className="z-[999]">
<p>{t("wallet.balance.withdrawableTooltip")}</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
<div className="flex gap-2">
<WithdrawButton />
<ClaimDailyReward level={myWallet.level?.level || 0} />
</div>
</div>
<Tooltip>
<TooltipTrigger className="block">
<div className="flex flex-row items-center gap-x-2 text-xs text-zinc-600 dark:text-neutral-400">
<span className="flex items-center gap-1 text-left">
{t("wallet.balance.withdrawable")} <i className="i-mingcute-question-line" />
</span>
<Balance className="center text-[12px] font-medium">
{myWallet.cashablePowerToken}
</Balance>
<SettingSectionTitle title={t("wallet.balance.dailyReward")} margin="compact" />
<div className="my-1 text-sm">
All active users on Follow are eligible for daily power rewards, which can be used for
purchases and tipping on Follow.
</div>
<div className="my-1 text-sm">
Based on your level and past activities, you can receive a{" "}
<Balance className="align-top" withSuffix>
{BigInt(myWallet.todayDailyPower || 0n)}
</Balance>{" "}
reward today.{" "}
<a
href="https://github.com/RSSNext/Follow/wiki/Power#daily-reward"
target="_blank"
className="underline"
rel="noreferrer noopener"
>
Learn more.
</a>
</div>
<div className="my-2 flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="space-y-1">
<Level level={myWallet.level?.level || 0} />
<ActivityPoints points={myWallet.level?.prevActivityPoints || 0} />
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent align="start" className="z-[999]">
<p>{t("wallet.balance.withdrawableTooltip")}</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
<i className="i-mgc-right-cute-li text-3xl" />
<Balance withSuffix>{BigInt(myWallet.todayDailyPower || 0n)}</Balance>
</div>
<ClaimDailyReward />
</div>
</div>
</>
)

View File

@ -74,8 +74,7 @@ export const PowerRanking: Component = ({ className }) => {
</TableCell>
<TableCell className="py-2">
<div className="flex items-center gap-1">
<i className="i-mgc-power align-text-bottom text-accent" />
<Balance>{row.powerToken}</Balance>
<Balance withSuffix>{row.powerToken}</Balance>
</div>
</TableCell>
<TableCell className="py-2">

View File

@ -10,14 +10,14 @@ import { cn } from "~/lib/utils"
import { SettingActionItem, SettingDescription, SettingSwitch } from "./control"
export const SettingSectionTitle: FC<{
title: string
title: string | ReactNode
margin?: "compact" | "normal"
}> = ({ title, margin }) => (
<div
className={cn(
"shrink-0 text-sm font-medium capitalize text-gray-400 first:mt-0 dark:text-neutral-500",
margin === "compact" ? "mb-2 mt-4" : "mb-4 mt-8",
margin === "compact" ? "mb-2 mt-8" : "mb-4 mt-8",
)}
>
{title}

View File

@ -0,0 +1,11 @@
import { cn } from "~/lib/utils"
export const ActivityPoints = ({ points, className }: { points: number; className?: string }) => {
return (
<div className={cn("flex items-center gap-1", className)}>
<i className="i-mgc-fire-cute-fi text-red-500" />
<span>{points}</span>
<sub className="-translate-y-px text-[0.6rem] font-normal">x{Math.floor(points / 10)}</sub>
</div>
)
}

View File

@ -25,13 +25,9 @@ export const Balance = ({
const formattedFull = format(n, { digits: 18, trailingZeros: true })
const Content = (
<div className={cn("tabular-nums", className)}>
{formatted}{" "}
{withSuffix && (
<span>
Power <i className="i-mgc-power align-text-bottom text-accent" />
</span>
)}
<div className={cn("inline-flex items-center gap-1 tabular-nums", className)}>
{withSuffix && <i className="i-mgc-power align-text-bottom text-accent" />}
<span>{formatted}</span>
</div>
)

View File

@ -93,7 +93,7 @@ const TipModalContent_: FC<{
<div className="flex w-[80vw] max-w-[350px] flex-col gap-5">
<p className="text-sm text-theme-foreground/80">{t("tip_modal.tip_sent")}</p>
<p>
<Balance className="mr-1 inline-block text-sm" withSuffix>
<Balance className="mr-1 text-sm" withSuffix>
{amountBigInt}
</Balance>{" "}
{t("tip_modal.tip_amount_sent")}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path fill="#10161F" fill-rule="evenodd" d="M10.938 2.785c.592-.227 1.333-.18 1.913.347a8.094 8.094 0 0 1 2.276 3.533c.226-.188.492-.324.783-.39a1.767 1.767 0 0 1 1.787.626 18.24 18.24 0 0 1 2.197 3.42C20.52 11.612 21 13.07 21 14.502c0 1.778-.36 3.454-1.369 4.793-1.023 1.357-2.607 2.228-4.77 2.595-1.103.188-1.87-.817-1.71-1.747.249-1.44.074-2.068-.148-2.497-.131-.253-.302-.481-.55-.807l-.013-.017c-.235-.31-.523-.688-.795-1.166a.286.286 0 0 0-.11-.108c-1.056.844-1.497 1.606-1.658 2.246-.164.655-.07 1.318.19 2.018.192.513.094 1.066-.213 1.469a1.43 1.43 0 0 1-1.51.52c-2.341-.613-4.517-2.394-5.25-4.847-.758-2.54.11-5.486 3.251-8.209.868-.751 2.557-2.532 3.337-4.67a2.186 2.186 0 0 1 1.256-1.289" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 858 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path stroke="#10161F" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.343 17.657a15.965 15.965 0 0 0 4.981-4.63c.334-.473.5-.71.5-1.027 0-.317-.166-.554-.5-1.028a15.962 15.962 0 0 0-4.98-4.629"/></svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@ -218,7 +218,8 @@
"titles.profile": "Profile",
"titles.shortcuts": "Shortcuts",
"wallet.address.title": "Your Address",
"wallet.balance.level": "Your Level",
"wallet.balance.activePoints": "Active Points",
"wallet.balance.dailyReward": "Your Daily Reward",
"wallet.balance.title": "Your Balance",
"wallet.balance.withdrawable": "Withdrawable",
"wallet.balance.withdrawableTooltip": "Withdrawable Power includes both the tips you've received and the Power you've recharged.",

View File

@ -4320,8 +4320,8 @@ declare const levels: drizzle_orm_pg_core.PgTableWithColumns<{
baseColumn: never;
generated: undefined;
}, {}, {}>;
preActivePoints: drizzle_orm_pg_core.PgColumn<{
name: "pre_active_points";
prevActivityPoints: drizzle_orm_pg_core.PgColumn<{
name: "prev_activity_points";
tableName: "levels";
dataType: "number";
columnType: "PgInteger";
@ -4336,8 +4336,8 @@ declare const levels: drizzle_orm_pg_core.PgTableWithColumns<{
baseColumn: never;
generated: undefined;
}, {}, {}>;
activePoints: drizzle_orm_pg_core.PgColumn<{
name: "active_points";
activityPoints: drizzle_orm_pg_core.PgColumn<{
name: "activity_points";
tableName: "levels";
dataType: "number";
columnType: "PgInteger";
@ -4407,8 +4407,8 @@ declare const levelsOpenAPISchema: zod.ZodObject<{
address: zod.ZodString;
rank: zod.ZodNullable<zod.ZodNumber>;
level: zod.ZodNullable<zod.ZodNumber>;
preActivePoints: zod.ZodNullable<zod.ZodNumber>;
activePoints: zod.ZodNullable<zod.ZodNumber>;
prevActivityPoints: zod.ZodNullable<zod.ZodNumber>;
activityPoints: zod.ZodNullable<zod.ZodNumber>;
statusUpdatedAt: zod.ZodNullable<zod.ZodString>;
powerToken: zod.ZodString;
userId: zod.ZodString;
@ -4418,8 +4418,8 @@ declare const levelsOpenAPISchema: zod.ZodObject<{
powerToken: string;
rank: number | null;
level: number | null;
preActivePoints: number | null;
activePoints: number | null;
prevActivityPoints: number | null;
activityPoints: number | null;
statusUpdatedAt: string | null;
}, {
userId: string;
@ -4427,8 +4427,8 @@ declare const levelsOpenAPISchema: zod.ZodObject<{
powerToken: string;
rank: number | null;
level: number | null;
preActivePoints: number | null;
activePoints: number | null;
prevActivityPoints: number | null;
activityPoints: number | null;
statusUpdatedAt: string | null;
}>;
declare const levelsRelations: drizzle_orm.Relations<"levels", {
@ -5040,19 +5040,6 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
status: 200;
};
};
"/wallets/transactions/claim_daily_ttl": {
$get: {
input: {};
output: {
code: 0;
data: {
ttl: number;
};
};
outputFormat: "json" | "text";
status: 200;
};
};
"/wallets/transactions/withdraw": {
$post: {
input: {
@ -5096,15 +5083,13 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
dailyPowerToken: string;
cashablePowerToken: string;
level: {
userId: string;
address: string;
powerToken: string;
rank: number | null;
level: number | null;
preActivePoints: number | null;
activePoints: number | null;
statusUpdatedAt: string | null;
prevActivityPoints: number | null;
activityPoints: number | null;
} | null;
todayDailyPower: string;
tomorrowDailyPower: string;
}[];
};
outputFormat: "json" | "text";
@ -5149,8 +5134,8 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
powerToken: string;
rank: number | null;
level: number | null;
preActivePoints: number | null;
activePoints: number | null;
prevActivityPoints: number | null;
activityPoints: number | null;
statusUpdatedAt: string | null;
}[];
};