232 lines
6.0 KiB
TypeScript
232 lines
6.0 KiB
TypeScript
"use client"
|
|
|
|
import confetti from "canvas-confetti"
|
|
import { useTranslations } from "next-intl"
|
|
import { FC, useEffect, useMemo, useRef, useState } from "react"
|
|
|
|
import { useAccountState } from "@crossbell/connect-kit"
|
|
|
|
import { CharacterList } from "~/components/common/CharacterList"
|
|
import { Tooltip } from "~/components/ui/Tooltip"
|
|
import { UniLink } from "~/components/ui/UniLink"
|
|
import { CSB_SCAN, CSB_XCHAR } from "~/lib/env"
|
|
import { noopArr } from "~/lib/noop"
|
|
import { cn } from "~/lib/utils"
|
|
import { useCheckMint, useGetMints, useMintPage } from "~/queries/page"
|
|
|
|
import { AvatarStack } from "../ui/AvatarStack"
|
|
import { Button } from "../ui/Button"
|
|
import { ModalContentProps, useModalStack } from "../ui/ModalStack"
|
|
|
|
export const ReactionMint = ({
|
|
size,
|
|
noteId,
|
|
characterId,
|
|
vertical,
|
|
}: {
|
|
size?: "sm" | "base"
|
|
noteId?: number
|
|
characterId?: number
|
|
vertical?: boolean
|
|
}) => {
|
|
const mintPage = useMintPage()
|
|
const t = useTranslations()
|
|
|
|
const account = useAccountState((s) => s.computed.account)
|
|
|
|
const [isMintListOpen, setIsMintListOpen] = useState(false)
|
|
const mintRef = useRef<HTMLButtonElement>(null)
|
|
|
|
const mints = useGetMints({
|
|
characterId,
|
|
noteId,
|
|
includeCharacter: size !== "sm",
|
|
})
|
|
const isMint = useCheckMint({
|
|
characterId,
|
|
noteId,
|
|
})
|
|
const presentMintModal = usePresentMintModal({
|
|
handle: account?.character?.handle || "",
|
|
transactionHash: isMint.data?.list?.[0]?.transactionHash || "",
|
|
})
|
|
const mint = () => {
|
|
if (characterId && noteId) {
|
|
if (isMint.data?.count) {
|
|
presentMintModal()
|
|
} else {
|
|
mintPage.mutate({
|
|
characterId,
|
|
noteId,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (mintPage.isSuccess) {
|
|
if (mintRef.current?.getBoundingClientRect()) {
|
|
confetti({
|
|
particleCount: 150,
|
|
spread: 360,
|
|
ticks: 50,
|
|
gravity: 0,
|
|
decay: 0.94,
|
|
startVelocity: 30,
|
|
origin: {
|
|
x:
|
|
(mintRef.current.getBoundingClientRect().left +
|
|
mintRef.current.getBoundingClientRect().width / 2 || 0.5) /
|
|
window.innerWidth,
|
|
y:
|
|
(mintRef.current.getBoundingClientRect().top || 0.5) /
|
|
window.innerHeight,
|
|
},
|
|
shapes: ["star"],
|
|
// cspell:disable-next-line
|
|
colors: ["FFE400", "FFBD00", "E89400", "FFCA6C", "FDFFB8"],
|
|
})
|
|
}
|
|
}
|
|
}, [mintPage.isSuccess])
|
|
|
|
const showAvatarStack = size !== "sm" && !vertical
|
|
|
|
const avatars = useMemo(
|
|
() =>
|
|
mints.data?.pages?.[0]?.list
|
|
?.sort((a, b) =>
|
|
b.character?.metadata?.content?.avatars?.[0] ? 1 : -1,
|
|
)
|
|
.slice(0, 3)
|
|
.map((mint) => ({
|
|
images: mint.character?.metadata?.content?.avatars,
|
|
name: mint.character?.metadata?.content?.name,
|
|
cid: mint.character.characterId,
|
|
})) || noopArr,
|
|
[mints],
|
|
)
|
|
|
|
return (
|
|
<>
|
|
<div className="xlog-reactions-mint flex items-center">
|
|
<Button
|
|
variant="collect"
|
|
variantColor={vertical ? "light" : undefined}
|
|
className={cn(
|
|
"flex items-center",
|
|
{
|
|
active: isMint.isSuccess && isMint.data.count,
|
|
},
|
|
vertical ? "!h-auto flex-col" : "mr-2",
|
|
)}
|
|
isAutoWidth={true}
|
|
onClick={mint}
|
|
isLoading={mintPage.isLoading}
|
|
ref={mintRef}
|
|
>
|
|
{(() => {
|
|
const inner = (
|
|
<i
|
|
className={cn(
|
|
"i-mingcute-magic-1-fill",
|
|
size === "sm"
|
|
? "text-base"
|
|
: vertical
|
|
? "text-[33px]"
|
|
: "text-[38px]",
|
|
)}
|
|
></i>
|
|
)
|
|
return size !== "sm" ? (
|
|
<Tooltip
|
|
label={t("Mint to an NFT")}
|
|
placement={vertical ? "right" : "top"}
|
|
>
|
|
{inner}
|
|
</Tooltip>
|
|
) : (
|
|
inner
|
|
)
|
|
})()}
|
|
<span className={cn("leading-snug", vertical ? "" : "ml-2")}>
|
|
{!mints.isLoading ? mints.data?.pages?.[0]?.count || 0 : "-"}
|
|
</span>
|
|
</Button>
|
|
{showAvatarStack && (
|
|
<AvatarStack
|
|
avatars={avatars}
|
|
onClick={() => setIsMintListOpen(true)}
|
|
count={mints.data?.pages?.[0]?.count || 0}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{showAvatarStack && (
|
|
<CharacterList
|
|
open={isMintListOpen}
|
|
setOpen={setIsMintListOpen}
|
|
title={t("Mint List")}
|
|
loadMore={mints.fetchNextPage}
|
|
hasMore={!!mints.hasNextPage}
|
|
list={mints.data?.pages || []}
|
|
></CharacterList>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
const usePresentMintModal = (props: MintModalProps) => {
|
|
const { present } = useModalStack()
|
|
const t = useTranslations()
|
|
return () => {
|
|
present({
|
|
title: t("Mint successfully") || "",
|
|
content: (rest) => <MintModal {...rest} {...props} />,
|
|
})
|
|
}
|
|
}
|
|
|
|
interface MintModalProps {
|
|
handle: string
|
|
transactionHash: string
|
|
}
|
|
|
|
const MintModal: FC<ModalContentProps<MintModalProps>> = ({
|
|
handle,
|
|
dismiss,
|
|
transactionHash,
|
|
}) => {
|
|
const t = useTranslations()
|
|
|
|
return (
|
|
<div>
|
|
<div className="p-5">
|
|
{t.rich("mint stored", {
|
|
link1: (chunks) => (
|
|
<UniLink
|
|
className="text-accent"
|
|
href={`${CSB_XCHAR}/${handle}/collections`}
|
|
>
|
|
{chunks}
|
|
</UniLink>
|
|
),
|
|
link2: (chunks) => (
|
|
<UniLink
|
|
className="text-accent"
|
|
href={`${CSB_SCAN}/tx/${transactionHash}`}
|
|
>
|
|
Crossbell Scan
|
|
</UniLink>
|
|
),
|
|
})}
|
|
</div>
|
|
<div className="h-16 border-t flex items-center px-5">
|
|
<Button isBlock onClick={dismiss}>
|
|
{t("Got it, thanks!")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|