Hey is a decentralized and permissionless social media app built with Lens Protocol 馃尶
1import {
2 ArrowsRightLeftIcon,
3 PlusIcon,
4 UsersIcon,
5 XCircleIcon
6} from "@heroicons/react/24/outline";
7import { ADDRESS_PLACEHOLDER } from "@hey/data/constants";
8import type { CollectActionType } from "@hey/types/hey";
9import { motion } from "motion/react";
10import { useState } from "react";
11import { isAddress } from "viem";
12import SearchAccounts from "@/components/Shared/Account/SearchAccounts";
13import ToggleWithHelper from "@/components/Shared/ToggleWithHelper";
14import { Button, H6, Input } from "@/components/Shared/UI";
15import splitNumber from "@/helpers/splitNumber";
16import { useCollectActionStore } from "@/store/non-persisted/post/useCollectActionStore";
17import { useAccountStore } from "@/store/persisted/useAccountStore";
18import { EXPANSION_EASE } from "@/variants";
19
20interface SplitConfigProps {
21 isRecipientsDuplicated: boolean;
22 setCollectType: (data: CollectActionType) => void;
23}
24
25const SplitConfig = ({
26 isRecipientsDuplicated,
27 setCollectType
28}: SplitConfigProps) => {
29 const { currentAccount } = useAccountStore();
30 const { collectAction } = useCollectActionStore((state) => state);
31
32 const currentAddress = currentAccount?.address || "";
33 const recipients = collectAction.payToCollect?.recipients || [];
34 const [isToggleOn, setIsToggleOn] = useState(
35 recipients.length > 1 ||
36 (recipients.length === 1 && recipients[0].address !== currentAddress)
37 );
38 const splitTotal = recipients.reduce((acc, curr) => acc + curr.percent, 0);
39
40 const handleSplitEvenly = () => {
41 const equalSplits = splitNumber(100, recipients.length);
42 const splits = recipients.map((recipient, i) => {
43 return {
44 address: recipient.address,
45 percent: equalSplits[i]
46 };
47 });
48 if (!collectAction.payToCollect) return;
49 setCollectType({
50 payToCollect: {
51 ...collectAction.payToCollect,
52 recipients: [...splits]
53 }
54 });
55 };
56
57 const onChangeRecipientOrPercent = (
58 index: number,
59 value: string,
60 type: "address" | "percent"
61 ) => {
62 const getRecipients = (value: string) => {
63 return recipients.map((recipient, i) => {
64 if (i === index) {
65 return {
66 ...recipient,
67 [type]: type === "address" ? value : Number.parseInt(value, 10)
68 };
69 }
70 return recipient;
71 });
72 };
73
74 if (!collectAction.payToCollect) return;
75 setCollectType({
76 payToCollect: {
77 ...collectAction.payToCollect,
78 recipients: getRecipients(value)
79 }
80 });
81 };
82
83 const updateRecipient = (index: number, value: string) => {
84 onChangeRecipientOrPercent(index, value, "address");
85 };
86
87 const handleRemoveRecipient = (index: number) => {
88 const updatedRecipients = recipients.filter((_, i) => i !== index);
89 if (updatedRecipients.length) {
90 if (!collectAction.payToCollect) return;
91 setCollectType({
92 payToCollect: {
93 ...collectAction.payToCollect,
94 recipients: updatedRecipients
95 }
96 });
97 } else {
98 if (!collectAction.payToCollect) return;
99 setCollectType({
100 payToCollect: {
101 ...collectAction.payToCollect,
102 recipients: [{ address: currentAddress, percent: 100 }]
103 }
104 });
105 setIsToggleOn(false);
106 }
107 };
108
109 const toggleSplit = () => {
110 if (!collectAction.payToCollect) return;
111 setCollectType({
112 payToCollect: {
113 ...collectAction.payToCollect,
114 recipients: [{ address: currentAddress, percent: 100 }]
115 }
116 });
117 setIsToggleOn(!isToggleOn);
118 };
119
120 return (
121 <div className="mt-5">
122 <ToggleWithHelper
123 description="Set multiple recipients for the collect fee"
124 heading="Split revenue"
125 icon={<UsersIcon className="size-5" />}
126 on={isToggleOn}
127 setOn={toggleSplit}
128 />
129 {isToggleOn ? (
130 <motion.div
131 animate="visible"
132 className="mt-4 ml-8 space-y-3"
133 initial="hidden"
134 transition={{ duration: 0.2, ease: EXPANSION_EASE }}
135 variants={{
136 hidden: { height: 0, opacity: 0, y: -20 },
137 visible: { height: "auto", opacity: 1, y: 0 }
138 }}
139 >
140 <div className="space-y-2">
141 {recipients.map((recipient, index) => (
142 <H6
143 className="flex items-center space-x-2 font-normal"
144 key={index}
145 >
146 <SearchAccounts
147 error={
148 recipient.address.length > 0 &&
149 !isAddress(recipient.address)
150 }
151 hideDropdown={isAddress(recipient.address)}
152 onAccountSelected={(account) =>
153 updateRecipient(index, account.owner)
154 }
155 onChange={(event) =>
156 updateRecipient(index, event.target.value)
157 }
158 placeholder={`${ADDRESS_PLACEHOLDER} or wagmi`}
159 value={recipient.address}
160 />
161 <div className="w-1/3">
162 <Input
163 iconRight="%"
164 max="100"
165 min="1"
166 onChange={(event) =>
167 onChangeRecipientOrPercent(
168 index,
169 event.target.value,
170 "percent"
171 )
172 }
173 placeholder="5"
174 type="number"
175 value={recipient.percent}
176 />
177 </div>
178 <button
179 onClick={() => handleRemoveRecipient(index)}
180 type="button"
181 >
182 <XCircleIcon className="size-5" />
183 </button>
184 </H6>
185 ))}
186 </div>
187 <div className="flex items-center justify-between">
188 {recipients.length >= 4 ? (
189 <div />
190 ) : (
191 <Button
192 icon={<PlusIcon className="size-3" />}
193 onClick={() => {
194 if (!collectAction.payToCollect) return;
195 setCollectType({
196 payToCollect: {
197 ...collectAction.payToCollect,
198 recipients: [...recipients, { address: "", percent: 0 }]
199 }
200 });
201 }}
202 outline
203 size="sm"
204 >
205 Add recipient
206 </Button>
207 )}
208 <Button
209 icon={<ArrowsRightLeftIcon className="size-3" />}
210 onClick={handleSplitEvenly}
211 outline
212 size="sm"
213 >
214 Split evenly
215 </Button>
216 </div>
217 {splitTotal > 100 ? (
218 <H6 className="text-red-500">
219 Splits cannot exceed 100%. Total: <span>{splitTotal}</span>%
220 </H6>
221 ) : null}
222 {splitTotal < 100 ? (
223 <H6 className="text-red-500">
224 Splits cannot be less than 100%. Total: <span>{splitTotal}</span>%
225 </H6>
226 ) : null}
227 {isRecipientsDuplicated ? (
228 <H6 className="text-red-500">Duplicate recipient address found</H6>
229 ) : null}
230 </motion.div>
231 ) : null}
232 </div>
233 );
234};
235
236export default SplitConfig;