From 7df4b22e19d4a7c0a29c058bf20d0f01e595a503 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:54:50 +0800 Subject: [PATCH 1/5] feat(boost): add Boost modal and radio card selection for boosting feeds --- apps/renderer/src/modules/boost/hooks.tsx | 21 +++ apps/renderer/src/modules/boost/modal.tsx | 132 ++++++++++++++++++ .../src/modules/boost/radio-cards.tsx | 52 +++++++ 3 files changed, 205 insertions(+) create mode 100644 apps/renderer/src/modules/boost/hooks.tsx create mode 100644 apps/renderer/src/modules/boost/modal.tsx create mode 100644 apps/renderer/src/modules/boost/radio-cards.tsx diff --git a/apps/renderer/src/modules/boost/hooks.tsx b/apps/renderer/src/modules/boost/hooks.tsx new file mode 100644 index 0000000000..fa4a4ce1b3 --- /dev/null +++ b/apps/renderer/src/modules/boost/hooks.tsx @@ -0,0 +1,21 @@ +import { useCallback } from "react" + +import { useModalStack } from "~/components/ui/modal" + +import { BoostModalContent } from "./modal" + +export const useBoostModal = () => { + const { present } = useModalStack() + + return useCallback( + (feedId: string) => { + present({ + id: "boost", + title: "Boost", + content: () => , + overlay: true, + }) + }, + [present], + ) +} diff --git a/apps/renderer/src/modules/boost/modal.tsx b/apps/renderer/src/modules/boost/modal.tsx new file mode 100644 index 0000000000..22a7285bbd --- /dev/null +++ b/apps/renderer/src/modules/boost/modal.tsx @@ -0,0 +1,132 @@ +import { from } from "dnum" +import { useCallback, useState } from "react" + +import { Button } from "~/components/ui/button" +import { LoadingWithIcon } from "~/components/ui/loading" +import { useCurrentModal } from "~/components/ui/modal" +import { useAuthQuery, useI18n } from "~/hooks/common" +import { boosts, useBoostFeedMutation } from "~/queries/boosts" +import { useWallet } from "~/queries/wallet" + +import { Balance } from "../wallet/balance" +import { RadioCards } from "./radio-cards" + +export const BoostModalContent = ({ feedId }: { feedId: string }) => { + const t = useI18n() + const myWallet = useWallet() + const myWalletData = myWallet.data?.[0] + + const dPowerBigInt = BigInt(myWalletData?.dailyPowerToken ?? 0) + const cPowerBigInt = BigInt(myWalletData?.cashablePowerToken ?? 0) + const balanceBigInt = cPowerBigInt + dPowerBigInt + const [amount, setAmount] = useState(80) + const amountBigInt = from(amount, 18)[0] + const wrongNumberRange = amountBigInt > balanceBigInt || amountBigInt <= BigInt(0) + + const { data: boostStatus, isLoading } = useAuthQuery(boosts.getStatus({ feedId })) + const boostFeedMutation = useBoostFeedMutation() + const { dismiss } = useCurrentModal() + + const handleBoost = useCallback(() => { + if (boostFeedMutation.isPending) return + boostFeedMutation.mutate({ feedId, amount: amountBigInt.toString() }) + }, [amountBigInt, boostFeedMutation, feedId]) + + if (isLoading || !boostStatus) { + return ( +
+ } size="large" /> +
+ ) + } + + if (boostFeedMutation.isSuccess) { + return ( +
+

{t("tip_modal.tip_sent")}

+

+ + {amountBigInt} + {" "} + {t("tip_modal.tip_amount_sent")} +

+ +
+ +
+
+ ) + } + + return ( +
+
+
🚀 Boost Feed
+ + + Boost feed to get more privilege, everyone subscribed to this feed will thank you. + + + + +
+ + +
+ ) +} + +const BoostProgress = ({ + level, + boostCount, + remainingBoostsToLevelUp, +}: { + level: number + boostCount: number + remainingBoostsToLevelUp: number +}) => { + const percentage = (boostCount / (boostCount + remainingBoostsToLevelUp)) * 100 + const nextLevel = level + 1 + return ( +
+
+ + ⚡️ {boostCount} + +
+
+
+
+ +
+ Lv. {level} + Lv. {nextLevel} +
+ + {remainingBoostsToLevelUp} more boost will unlock the next level of privilege. + +
+ ) +} diff --git a/apps/renderer/src/modules/boost/radio-cards.tsx b/apps/renderer/src/modules/boost/radio-cards.tsx new file mode 100644 index 0000000000..c1341971d4 --- /dev/null +++ b/apps/renderer/src/modules/boost/radio-cards.tsx @@ -0,0 +1,52 @@ +import { RadioGroup } from "~/components/ui/radio-group" +import { RadioCard } from "~/components/ui/radio-group/RadioCard" + +const radios = [ + { + name: "1 Month", + amount: 80, + }, + { + name: "3 Months", + amount: 240, + }, + { + name: "6 Months", + amount: 480, + }, + { + name: "1 Year", + amount: 960, + }, +] + +export const RadioCards = ({ + value, + onValueChange, +}: { + value: number + onValueChange: (value: number) => void +}) => { + return ( + onValueChange(+value)}> +
+ {radios.map((item) => ( + +

{item.name}

+

+ {item.amount} + +

+
+ } + /> + ))} +
+ + ) +} From fe6a2416215a8d588f65aa81bf35d6f451933368 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:56:26 +0800 Subject: [PATCH 2/5] feat: integrate Boost modal into feed actions --- apps/renderer/src/hooks/biz/useFeedActions.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/renderer/src/hooks/biz/useFeedActions.tsx b/apps/renderer/src/hooks/biz/useFeedActions.tsx index d66e65ef0d..ef9e24dbd5 100644 --- a/apps/renderer/src/hooks/biz/useFeedActions.tsx +++ b/apps/renderer/src/hooks/biz/useFeedActions.tsx @@ -9,6 +9,7 @@ import type { FeedViewType } from "~/lib/enum" import type { NativeMenuItem, NullableNativeMenuItem } from "~/lib/native-menu" import { UrlBuilder } from "~/lib/url-builder" import { isBizId } from "~/lib/utils" +import { useBoostModal } from "~/modules/boost/hooks" import { useFeedClaimModal } from "~/modules/claim" import { FeedForm } from "~/modules/discover/feed-form" import { InboxForm } from "~/modules/discover/inbox-form" @@ -52,6 +53,7 @@ export const useFeedActions = ({ const { mutateAsync: addFeedToListMutation } = useAddFeedToFeedList() const { mutateAsync: removeFeedFromListMutation } = useRemoveFeedFromFeedList() + const openBoostModal = useBoostModal() const listByView = useOwnedList(view!) @@ -86,6 +88,13 @@ export const useFeedActions = ({ }, ] : []), + { + type: "text" as const, + label: "Boost", + click: () => { + openBoostModal(feedId) + }, + }, { type: "separator" as const, disabled: isEntryList, From 79a744e432d3f9b899b277e35b7b696c90b32dfb Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Sun, 20 Oct 2024 16:56:43 +0800 Subject: [PATCH 3/5] feat: add Boost feed status query and mutation --- apps/renderer/src/queries/boosts.tsx | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 apps/renderer/src/queries/boosts.tsx diff --git a/apps/renderer/src/queries/boosts.tsx b/apps/renderer/src/queries/boosts.tsx new file mode 100644 index 0000000000..74c54ed83a --- /dev/null +++ b/apps/renderer/src/queries/boosts.tsx @@ -0,0 +1,36 @@ +import { useMutation } from "@tanstack/react-query" +import { toast } from "sonner" + +import { apiClient } from "~/lib/api-fetch" +import { defineQuery } from "~/lib/defineQuery" +import { toastFetchError } from "~/lib/error-parser" + +export const boosts = { + getStatus: ({ feedId }: { feedId: string }) => + defineQuery(["boostFeed", feedId], async () => { + const res = await apiClient.boosts.$get({ + query: { + feedId, + }, + }) + return res.data + }), +} + +export const useBoostFeedMutation = () => + useMutation({ + mutationKey: ["boostFeed"], + mutationFn: (data: Parameters[0]["json"]) => + apiClient.boosts.$post({ json: data }), + onError(err) { + toastFetchError(err) + }, + onSuccess(_, variables) { + boosts.getStatus({ feedId: variables.feedId }).invalidate() + window.analytics?.capture("boost_sent", { + amount: variables.amount, + feedId: variables.feedId, + }) + toast("🎉 Boosted.") + }, + }) From b9f89aa17f5aadfa3a1b67d1d0e74a3292de4e69 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Mon, 21 Oct 2024 02:51:26 +0800 Subject: [PATCH 4/5] chore: enhance BoostProgress modal styling and transitions --- apps/renderer/src/modules/boost/modal.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/renderer/src/modules/boost/modal.tsx b/apps/renderer/src/modules/boost/modal.tsx index 22a7285bbd..13143ef86f 100644 --- a/apps/renderer/src/modules/boost/modal.tsx +++ b/apps/renderer/src/modules/boost/modal.tsx @@ -44,6 +44,7 @@ export const BoostModalContent = ({ feedId }: { feedId: string }) => { return (

{t("tip_modal.tip_sent")}

+

{amountBigInt} @@ -100,31 +101,31 @@ const BoostProgress = ({ const percentage = (boostCount / (boostCount + remainingBoostsToLevelUp)) * 100 const nextLevel = level + 1 return ( -

+
⚡️ {boostCount} -
+
- Lv. {level} - Lv. {nextLevel} + Lv. {level} + Lv. {nextLevel}
- + {remainingBoostsToLevelUp} more boost will unlock the next level of privilege.
From 952e006ddc6e2a215f954f73d6650ebe96b6d57f Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Mon, 21 Oct 2024 03:43:47 +0800 Subject: [PATCH 5/5] feat: update RadioCards to use monthlyBoostCost for dynamic pricing --- apps/renderer/src/modules/boost/modal.tsx | 8 ++++++-- apps/renderer/src/modules/boost/radio-cards.tsx | 14 ++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/renderer/src/modules/boost/modal.tsx b/apps/renderer/src/modules/boost/modal.tsx index 13143ef86f..b6a104f70b 100644 --- a/apps/renderer/src/modules/boost/modal.tsx +++ b/apps/renderer/src/modules/boost/modal.tsx @@ -19,7 +19,7 @@ export const BoostModalContent = ({ feedId }: { feedId: string }) => { const dPowerBigInt = BigInt(myWalletData?.dailyPowerToken ?? 0) const cPowerBigInt = BigInt(myWalletData?.cashablePowerToken ?? 0) const balanceBigInt = cPowerBigInt + dPowerBigInt - const [amount, setAmount] = useState(80) + const [amount, setAmount] = useState(0) const amountBigInt = from(amount, 18)[0] const wrongNumberRange = amountBigInt > balanceBigInt || amountBigInt <= BigInt(0) @@ -71,7 +71,11 @@ export const BoostModalContent = ({ feedId }: { feedId: string }) => { - +