diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json index 42b49087..57773408 100644 --- a/public/locales/zh/common.json +++ b/public/locales/zh/common.json @@ -55,11 +55,14 @@ "Patron": "赞助", "Become a patron of {{name}}" : "成为 {{name}} 的赞助者", "Latest patrons": "最近赞助者", + "Latest tipper": "最近赞赏者", "You are here to be the first patron.": "你将是第一个赞助者。", + "You are here to be the first tipper.": "你将是第一个赞赏者。", "Select a tier": "选择一个等级", "One-time": "一次性", "Monthly and NFT Rewards": "每月和 NFT 奖励", "Coming soon": "即将推出", "What is Mira? Where can I get some?" : "Mira 是什么?我在哪里可以获得?", - "Tip": "赞赏" + "Tip": "赞赏", + "Tip the post: {{name}}": "赞赏文章:{{name}}" } \ No newline at end of file diff --git a/src/components/common/PatronModal.tsx b/src/components/common/PatronModal.tsx index 3b64f5bf..d8235d22 100644 --- a/src/components/common/PatronModal.tsx +++ b/src/components/common/PatronModal.tsx @@ -1,5 +1,5 @@ import { Button } from "~/components/ui/Button" -import { Profile } from "~/lib/types" +import { Note, Profile } from "~/lib/types" import { useTranslation } from "next-i18next" import { HeartIcon } from "@heroicons/react/24/solid" import { Modal } from "~/components/ui/Modal" @@ -16,16 +16,19 @@ import { CharacterFloatCard } from "./CharacterFloatCard" import { UniLink } from "../ui/UniLink" import { getSiteLink } from "~/lib/helpers" import { CSB_SCAN } from "~/lib/env" +import { parsePageId } from "~/models/page.model" export const PatronModal: React.FC<{ site: Profile | undefined | null + page?: Note | null open: boolean setOpen: (open: boolean) => void -}> = ({ site, open, setOpen }) => { +}> = ({ site, page, open, setOpen }) => { const { t } = useTranslation("common") const tipCharacter = useTipCharacter() const tips = useGetTips({ toCharacterId: site?.metadata?.proof, + toNoteId: parsePageId(page?.id || "").noteId, }) const radios = [ @@ -66,6 +69,7 @@ export const PatronModal: React.FC<{ fromCharacterId: currentCharacterId, toCharacterId: site?.metadata?.proof, amount: parseInt(value), + noteId: parsePageId(page?.id || "").noteId, }) } } @@ -100,15 +104,20 @@ export const PatronModal: React.FC<{ } }, [tipCharacter.isError, t]) + const title = + (page + ? t("Tip the post: {{name}}", { + name: page.title, + }) + : t("Become a patron of {{name}}", { + name: site?.name, + })) || "" + return ( } size="lg" > @@ -131,12 +140,14 @@ export const PatronModal: React.FC<{ )}
-
{t("Latest patrons")}
+
+ {t(page ? "Latest tipper" : "Latest patrons")} +
- {tips.data?.list?.length ? ( + {tips.data?.pages?.[0]?.list?.length ? ( <> -
    - {tips.data.list?.map((tip, index) => ( +
      + {tips.data.pages[0].list?.map((tip, index) => (
    • @@ -167,17 +178,21 @@ export const PatronModal: React.FC<{
    • ))} - {tips.data.count > 10 && ( + {tips.data.pages?.[0]?.count > 8 && (
    • -
      - +{tips.data.count - 10} +
      + +{tips.data.pages?.[0]?.count - 8}
    • )}
    ) : ( - t("You are here to be the first patron.") + t( + page + ? "You are here to be the first tipper." + : "You are here to be the first patron.", + ) )}
@@ -219,9 +234,7 @@ export const PatronModal: React.FC<{ isLoading={tipCharacter.isLoading} ref={submitRef} > - {t("Become a patron of {{name}}", { - name: site?.name, - })} + {title} diff --git a/src/components/common/Reactions.tsx b/src/components/common/Reactions.tsx index 961271c0..630699bf 100644 --- a/src/components/common/Reactions.tsx +++ b/src/components/common/Reactions.tsx @@ -7,7 +7,7 @@ import { useToggleLikePage, useGetLikeCounts, } from "~/queries/page" -import { useAccountSites } from "~/queries/site" +import { useAccountSites, useGetTips } from "~/queries/site" import { useState, useEffect, useRef } from "react" import { Button } from "../ui/Button" import { CSB_SCAN, CSB_XCHAR } from "~/lib/env" @@ -21,20 +21,24 @@ import { cn } from "~/lib/utils" import { Tooltip } from "~/components/ui/Tooltip" import confetti from "canvas-confetti" import { Trans, useTranslation } from "next-i18next" -import { Profile } from "~/lib/types" +import { Note, Profile } from "~/lib/types" +import { HeartIcon } from "@heroicons/react/24/solid" +import { useAccountState } from "@crossbell/connect-kit" +import { PatronModal } from "~/components/common/PatronModal" export const Reactions: React.FC<{ className?: string size?: "sm" | "base" pageId?: string site?: Profile | null -}> = ({ className, size, pageId, site }) => { + page?: Note | null +}> = ({ className, size, pageId, site, page }) => { const toggleLikePage = useToggleLikePage() const mintPage = useMintPage() const { t } = useTranslation("common") // like - const userSite = useAccountSites() + const account = useAccountState((s) => s.computed.account) const [isLikeOpen, setIsLikeOpen] = useState(false) const [isLikeListOpen, setIsLikeListOpen] = useState(false) const likeRef = useRef(null) @@ -134,6 +138,27 @@ export const Reactions: React.FC<{ } }, [mintPage.isSuccess]) + // tip + const [isTipOpen, setIsTipOpen] = useState(false) + const tipRef = useRef(null) + + const { characterId, noteId } = parsePageId(pageId || "") + const tips = useGetTips({ + toCharacterId: characterId, + toNoteId: noteId, + }) + const isTip = useGetTips({ + toCharacterId: characterId, + toNoteId: noteId, + characterId: account?.characterId, + }) + + const tip = () => { + if (pageId) { + setIsTipOpen(true) + } + } + return ( <>
{(() => { @@ -260,6 +285,58 @@ export const Reactions: React.FC<{ )}
+ {size !== "sm" && ( +
+ + { +
    + {tips.data?.pages?.[0]?.list + ?.sort((a, b: any) => + b.character?.metadata?.content?.avatars?.[0] ? 1 : -1, + ) + .slice(0, 3) + .map((tip: any, index) => ( +
  • + +
  • + ))} + {(tips.data?.pages?.[0]?.count || 0) > 3 && ( +
  • +
    + +{tips.data!.pages?.[0]?.count - 3} +
    +
  • + )} +
+ } +
+ )} xChar {" "} @@ -311,6 +388,12 @@ export const Reactions: React.FC<{ + = ({ page, site }) => { return ( <> - + ) diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index dfc9f2ff..72da4594 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -15,7 +15,7 @@ export type Variant = | "collect" | "crossbell" | "outline" - | "patron" + | "tip" export type VariantColor = "green" | "red" | "gray" | "gradient" | "black" diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx index 0f0828ac..6f276db4 100644 --- a/src/components/ui/Modal.tsx +++ b/src/components/ui/Modal.tsx @@ -40,7 +40,7 @@ export const Modal: React.FC<{ {title && ( {titleIcon && {titleIcon}} - {title} + {title} )} {children} diff --git a/src/css/tailwind.css b/src/css/tailwind.css index 040b9ec6..0f401675 100644 --- a/src/css/tailwind.css +++ b/src/css/tailwind.css @@ -153,15 +153,15 @@ a { fill: #ffcf55; } -.button.is-patron { +.button.is-tip { @apply shadow-none p-0; @apply text-gray-500 fill-gray-500; } -.button.is-patron:hover { +.button.is-tip:hover { color: #f87171; fill: #f87171; } -.button.is-patron.active { +.button.is-tip.active { color: #f87171; fill: #f87171; } diff --git a/src/models/site.model.ts b/src/models/site.model.ts index 70705ccf..e610ae68 100644 --- a/src/models/site.model.ts +++ b/src/models/site.model.ts @@ -520,27 +520,45 @@ export async function tipCharacter( fromCharacterId: string | number toCharacterId: string | number amount: number + noteId?: string | number }, contract: Contract, ) { const decimals = await contract.getMiraTokenDecimals() - return await contract?.tipCharacter( - input.fromCharacterId, - input.toCharacterId, - BigInt(input.amount) * BigInt(10) ** BigInt(decimals?.data || 18), - ) + if (input.noteId) { + return await contract?.tipCharacterForNote( + input.fromCharacterId, + input.toCharacterId, + input.noteId, + BigInt(input.amount) * BigInt(10) ** BigInt(decimals?.data || 18), + ) + } else { + return await contract?.tipCharacter( + input.fromCharacterId, + input.toCharacterId, + BigInt(input.amount) * BigInt(10) ** BigInt(decimals?.data || 18), + ) + } } export async function getTips( - input: { toCharacterId: string | number }, + input: { + toCharacterId: string | number + characterId?: string | number + toNoteId?: string | number + cursor?: string + }, contract: Contract, ) { const address = await contract.getMiraTokenAddress() const tips = await indexer?.getTips({ + characterId: input.characterId, + toNoteId: input.toNoteId, toCharacterId: input.toCharacterId, tokenAddress: address?.data || "0xAfB95CC0BD320648B3E8Df6223d9CDD05EbeDC64", includeMetadata: true, limit: 8, + cursor: input.cursor, }) if (tips?.list?.length) { diff --git a/src/queries/site.ts b/src/queries/site.ts index 1b4da9e9..7e165f89 100644 --- a/src/queries/site.ts +++ b/src/queries/site.ts @@ -1,4 +1,9 @@ -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" +import { + useQuery, + useMutation, + useQueryClient, + useInfiniteQuery, +} from "@tanstack/react-query" import { useContract } from "@crossbell/contract" import { useAccountState, @@ -324,17 +329,29 @@ export function useTipCharacter() { return mutation } -export const useGetTips = (data: { toCharacterId?: string }) => { +export const useGetTips = ( + data: Partial[0]>, +) => { const contract = useContract() - return useQuery(["getTips", data], async () => { - if (!data.toCharacterId) { - return null - } - return siteModel.getTips( - { - toCharacterId: data.toCharacterId, - }, - contract, - ) + return useInfiniteQuery({ + queryKey: ["getTips", data], + queryFn: async ({ pageParam }) => { + if (!data.toCharacterId) { + return { + count: 0, + list: [], + cursor: undefined, + } + } + return siteModel.getTips( + { + ...data, + toCharacterId: data.toCharacterId, + cursor: pageParam, + }, + contract, + ) + }, + getNextPageParam: (lastPage) => lastPage.cursor || undefined, }) }