Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import { CheckCircleIcon } from "@heroicons/react/24/outline";
2import {
3 DEFAULT_COLLECT_TOKEN,
4 PERMISSIONS,
5 STATIC_IMAGES_URL,
6 SUBSCRIPTION_AMOUNT,
7 WRAPPED_NATIVE_TOKEN_SYMBOL
8} from "@hey/data/constants";
9import {
10 type AccountFragment,
11 useBalancesBulkQuery,
12 useJoinGroupMutation
13} from "@hey/indexer";
14import type { ApolloClientError } from "@hey/types/errors";
15import { useCallback, useState } from "react";
16import SingleAccount from "@/components/Shared/Account/SingleAccount";
17import TopUpButton from "@/components/Shared/Account/TopUp/Button";
18import { Button, Image, Spinner, Tooltip } from "@/components/Shared/UI";
19import errorToast from "@/helpers/errorToast";
20import getTokenImage from "@/helpers/getTokenImage";
21import useTransactionLifecycle from "@/hooks/useTransactionLifecycle";
22import useWaitForTransactionToComplete from "@/hooks/useWaitForTransactionToComplete";
23import { useAccountStore } from "@/store/persisted/useAccountStore";
24
25const Subscribe = () => {
26 const { currentAccount } = useAccountStore();
27 const [isSubmitting, setIsSubmitting] = useState(false);
28 const handleTransactionLifecycle = useTransactionLifecycle();
29 const waitForTransactionToComplete = useWaitForTransactionToComplete();
30
31 const { data: balance, loading: balanceLoading } = useBalancesBulkQuery({
32 fetchPolicy: "no-cache",
33 pollInterval: 3000,
34 skip: !currentAccount?.address,
35 variables: {
36 request: {
37 address: currentAccount?.address,
38 tokens: [DEFAULT_COLLECT_TOKEN]
39 }
40 }
41 });
42
43 const onCompleted = async (hash: string) => {
44 await waitForTransactionToComplete(hash);
45 location.reload();
46 };
47
48 const onError = useCallback((error: ApolloClientError) => {
49 setIsSubmitting(false);
50 errorToast(error);
51 }, []);
52
53 const tokenBalance =
54 balance?.balancesBulk[0].__typename === "Erc20Amount"
55 ? Number(balance.balancesBulk[0].value).toFixed(2)
56 : 0;
57
58 const canSubscribe = Number(tokenBalance) >= SUBSCRIPTION_AMOUNT;
59
60 const [joinGroup] = useJoinGroupMutation({
61 onCompleted: async ({ joinGroup }) => {
62 return await handleTransactionLifecycle({
63 onCompleted,
64 onError,
65 transactionData: joinGroup
66 });
67 },
68 onError
69 });
70
71 const handleSubscribe = async () => {
72 setIsSubmitting(true);
73
74 return await joinGroup({
75 variables: { request: { group: PERMISSIONS.SUBSCRIPTION } }
76 });
77 };
78
79 const hasSubscribed = currentAccount?.hasSubscribed;
80
81 return (
82 <div className="mx-5 my-10 flex flex-col items-center gap-y-8">
83 <Image
84 alt="Pro"
85 className="w-32"
86 src={`${STATIC_IMAGES_URL}/pro.png`}
87 width={128}
88 />
89 <div className="max-w-md text-center text-gray-500">
90 {hasSubscribed ? (
91 <div className="text-gray-500">
92 Thanks for being a valuable <b>Hey Pro</b> member!
93 </div>
94 ) : (
95 <>
96 Join Hey Pro for{" "}
97 <b className="inline-flex items-center gap-x-1">
98 {SUBSCRIPTION_AMOUNT}{" "}
99 <Tooltip content={WRAPPED_NATIVE_TOKEN_SYMBOL} placement="top">
100 <img
101 alt={WRAPPED_NATIVE_TOKEN_SYMBOL}
102 className="size-5"
103 src={getTokenImage(WRAPPED_NATIVE_TOKEN_SYMBOL)}
104 />
105 </Tooltip>
106 /year
107 </b>
108 .
109 </>
110 )}
111 </div>
112 <SingleAccount
113 account={currentAccount as AccountFragment}
114 isVerified
115 linkToAccount={false}
116 showUserPreview={false}
117 />
118 {hasSubscribed ? null : (
119 <>
120 <div className="flex flex-col items-center gap-y-2 text-gray-500">
121 <div className="flex items-center gap-x-1">
122 <CheckCircleIcon className="size-4.5" />
123 <span className="text-sm">Subscription Badge</span>
124 </div>
125 <div className="flex items-center gap-x-1">
126 <CheckCircleIcon className="size-4.5" />
127 <span className="text-sm">Exclusive Hey features</span>
128 </div>
129 <div className="flex items-center gap-x-1">
130 <CheckCircleIcon className="size-4.5" />
131 <span className="text-sm">Special NFT for early members</span>
132 </div>
133 <div className="flex items-center gap-x-1">
134 <CheckCircleIcon className="size-4.5" />
135 <span className="text-sm">Contribute to Hey's growth</span>
136 </div>
137 </div>
138 {balanceLoading ? (
139 <Button
140 className="w-sm"
141 disabled
142 icon={<Spinner className="my-1" size="xs" />}
143 />
144 ) : canSubscribe ? (
145 <Button
146 className="w-sm"
147 disabled={isSubmitting}
148 loading={isSubmitting}
149 onClick={handleSubscribe}
150 >
151 Subscribe for ${SUBSCRIPTION_AMOUNT}/year
152 </Button>
153 ) : (
154 <TopUpButton
155 amountToTopUp={
156 Math.ceil((SUBSCRIPTION_AMOUNT - Number(tokenBalance)) * 20) /
157 20
158 }
159 className="w-sm"
160 label={`Top-up ${SUBSCRIPTION_AMOUNT} ${WRAPPED_NATIVE_TOKEN_SYMBOL} to your account`}
161 outline
162 token={{
163 contractAddress: DEFAULT_COLLECT_TOKEN,
164 symbol: WRAPPED_NATIVE_TOKEN_SYMBOL
165 }}
166 />
167 )}
168 <div className="-mt-1 text-center text-gray-500 text-xs">
169 One-time payment. Manual renewal required next year.
170 </div>
171 </>
172 )}
173 </div>
174 );
175};
176
177export default Subscribe;