Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
mastodon
GitHub Repository: mastodon/joinmastodon
Path: blob/main/components/DonateTabs.tsx
1006 views
1
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/react"
2
import Image from "next/image"
3
import { FC, PropsWithChildren, ReactNode } from "react"
4
import { defineMessages, FormattedMessage, useIntl } from "react-intl"
5
6
import { DonatePopup } from "../donate/DonatePopup"
7
import ExternalLinkIcon from "../public/ui/external-link.svg?inline"
8
import deFlagIcon from "../public/german_flag_icon_round.svg"
9
import usFlagIcon from "../public/united_states_flag_icon_round.svg"
10
import gitHubLogo from "../public/logos/github.svg"
11
import patreonLogo from "../public/logos/patreon.svg"
12
import stripeLogo from "../public/logos/stripe.svg"
13
import benevityLogo from "../public/logos/benevity.svg"
14
import sponsorshipIcon from "../public/icons/corporate-sponsorship.svg"
15
16
import LinkButton from "./LinkButton"
17
import { useMediaQuery } from "usehooks-ts"
18
import classNames from "classnames"
19
20
const cardMessages = defineMessages({
21
giveButterTitle: {
22
id: "sponsors.donate_card.givebutter.title",
23
defaultMessage: "From the United States",
24
},
25
giveButterCopy: {
26
id: "sponsors.donate_card.givebutter.copy",
27
defaultMessage:
28
"Make a one-time or recurring donation to Mastodon Inc, our US 501c3 non-profit. Tax deductible for eligible US tax residents.*",
29
},
30
giveButterButton: {
31
id: "sponsors.donate_card.givebutter.button",
32
defaultMessage: "Donate through GiveButter",
33
},
34
giveButterImageAlt: {
35
id: "sponsors.donate_card.givebutter.image_alt",
36
defaultMessage: "USA Flag",
37
},
38
weAidTitle: {
39
id: "sponsors.donate_card.weaid.title",
40
defaultMessage: "From Germany",
41
},
42
weAidCopy: {
43
id: "sponsors.donate_card.weaid.copy",
44
defaultMessage:
45
"Make a one-time donation through WE AID gGmbH, our German fiscal host. Tax deductible for eligible German tax residents.*",
46
},
47
weAidButton: {
48
id: "sponsors.donate_card.weaid.button",
49
defaultMessage: "Donate through WE AID",
50
},
51
weAidImageAlt: {
52
id: "sponsors.donate_card.weaid.image_alt",
53
defaultMessage: "German Flag",
54
},
55
patreonTitle: {
56
id: "sponsors.donate_card.patreon.title",
57
defaultMessage: "Patreon",
58
},
59
patreonCopy: {
60
id: "sponsors.donate_card.patreon.copy",
61
defaultMessage:
62
"Patreon donors gain access to Mastodon’s Discord for developers, server admins, and social web supporters.",
63
},
64
patreonButton: {
65
id: "sponsors.donate_card.patreon.button",
66
defaultMessage: "Donate through Patreon",
67
},
68
gitHubTitle: {
69
id: "sponsors.donate_card.github.title",
70
defaultMessage: "GitHub",
71
},
72
gitHubCopy: {
73
id: "sponsors.donate_card.github.copy",
74
defaultMessage:
75
"GitHub Sponsors receive a Mastodon badge to their Org or Personal profile. Plus we don’t pay fees!",
76
},
77
gitHubButton: {
78
id: "sponsors.donate_card.github.button",
79
defaultMessage: "Donate through GitHub",
80
},
81
stripeTitle: {
82
id: "sponsors.donate_card.stripe.title",
83
defaultMessage: "Stripe",
84
},
85
stripeCopy: {
86
id: "sponsors.donate_card.stripe.copy",
87
defaultMessage:
88
"Make a one-time or recurring direct donation to Mastodon GmbH, from anywhere in the world.",
89
},
90
stripeButton: {
91
id: "sponsors.donate_card.stripe.button",
92
defaultMessage: "Donate through Stripe",
93
},
94
corpSponsorTitle: {
95
id: "sponsors.donate_card.corporate_sponsor.title",
96
defaultMessage: "Corporate sponsorship",
97
},
98
corpSponsorCopy: {
99
id: "sponsors.donate_card.corporate_sponsor.copy",
100
defaultMessage:
101
"We welcome corporate sponsors! We’ll feature your company’s logo and a link to your website.",
102
},
103
corpSponsorButton: {
104
id: "sponsors.donate_card.corporate_sponsor.button",
105
defaultMessage: "Become a sponsor",
106
},
107
corpMatchTitle: {
108
id: "sponsors.donate_card.corporate_matching.title",
109
defaultMessage: "Corporate matching",
110
},
111
corpMatchCopy: {
112
id: "sponsors.donate_card.corporate_matching.copy",
113
defaultMessage:
114
"Does your company provide corporate matching? If so, you can use Benevity to donate!",
115
},
116
corpMatchButton: {
117
id: "sponsors.donate_card.corporate_matching.button",
118
defaultMessage: "Donate on Benevity",
119
},
120
logoAlt: {
121
id: "sponsors.donate_card.logo.alt",
122
defaultMessage: "{name} Logo",
123
},
124
})
125
126
export const DonateTabs: FC<{ className?: string }> = ({ className }) => {
127
const isMobile = useMediaQuery("(max-width: 768px")
128
if (isMobile) {
129
return (
130
<div className={classNames(className, "flex flex-col gap-4")}>
131
<DonateCards plain />
132
</div>
133
)
134
}
135
return (
136
<TabGroup className={className}>
137
<TabList className="flex mb-4">
138
<StyledTab>
139
<FormattedMessage
140
id="sponsors.donate_header.tax_deductible"
141
defaultMessage="Tax-deductible donations"
142
/>
143
</StyledTab>
144
<StyledTab>
145
<FormattedMessage
146
id="sponsors.donate_header.individual"
147
defaultMessage="More individual donation options"
148
/>
149
</StyledTab>
150
<StyledTab>
151
<FormattedMessage
152
id="sponsors.donate_header.corporate"
153
defaultMessage="Corporate gifts & matching"
154
/>
155
</StyledTab>
156
</TabList>
157
<TabPanels className="border border-gray-3 p-8 rounded-2xl">
158
<DonateCards />
159
</TabPanels>
160
</TabGroup>
161
)
162
}
163
164
const StyledTab: FC<PropsWithChildren> = ({ children }) => (
165
<Tab className="grow py-2 data-[selected]:pb-[calc(0.5rem-3px)] transition-colors font-semibold box-border border-b border-gray-3 data-[selected]:border-b-4 data-[selected]:border-blurple-500">
166
{children}
167
</Tab>
168
)
169
170
const DonateCards: FC<{ plain?: boolean }> = ({ plain }) => {
171
const intl = useIntl()
172
return (
173
<>
174
<StyledTabPanel
175
title={
176
<FormattedMessage
177
id="sponsors.donate_header.tax_deductible"
178
defaultMessage="Tax-deductible donations"
179
/>
180
}
181
footer={
182
<FormattedMessage
183
id="sponsors.donate_footer.tax_disclaimer"
184
defaultMessage="*Tax exemptions vary according to the laws of each country. We strongly recommend that you contact a tax consultant in your country if you have any questions about tax exemptions or reductions."
185
/>
186
}
187
plain={plain}
188
>
189
<DonateCard
190
title={intl.formatMessage(cardMessages.giveButterTitle)}
191
copy={intl.formatMessage(cardMessages.giveButterCopy)}
192
cta={intl.formatMessage(cardMessages.giveButterButton)}
193
ctaAction="https://givebutter.com/nAk74p"
194
imageSrc={usFlagIcon}
195
imageAlt={intl.formatMessage(cardMessages.giveButterImageAlt)}
196
/>
197
<DonateCard
198
title={intl.formatMessage(cardMessages.weAidTitle)}
199
copy={intl.formatMessage(cardMessages.weAidCopy)}
200
cta={intl.formatMessage(cardMessages.weAidButton)}
201
ctaAction="https://donate.stripe.com/14A4gAfACaLg76zfKB1ZS07"
202
imageSrc={deFlagIcon}
203
imageAlt={intl.formatMessage(cardMessages.weAidImageAlt)}
204
/>
205
</StyledTabPanel>
206
<StyledTabPanel
207
title={
208
<FormattedMessage
209
id="sponsors.donate_header.individual_options"
210
defaultMessage="More individual donation options"
211
/>
212
}
213
plain={plain}
214
>
215
<DonateCard
216
title={intl.formatMessage(cardMessages.stripeTitle)}
217
copy={intl.formatMessage(cardMessages.stripeCopy)}
218
cta={intl.formatMessage(cardMessages.stripeButton)}
219
ctaAction="popup"
220
imageSrc={stripeLogo}
221
imageAlt={intl.formatMessage(cardMessages.logoAlt, {
222
name: "Stripe",
223
})}
224
/>
225
<DonateCard
226
title={intl.formatMessage(cardMessages.patreonTitle)}
227
copy={intl.formatMessage(cardMessages.patreonCopy)}
228
cta={intl.formatMessage(cardMessages.patreonButton)}
229
ctaAction="https://www.patreon.com/mastodon"
230
imageSrc={patreonLogo}
231
imageAlt={intl.formatMessage(cardMessages.logoAlt, {
232
name: "Patreon",
233
})}
234
/>
235
<DonateCard
236
title={intl.formatMessage(cardMessages.gitHubTitle)}
237
copy={intl.formatMessage(cardMessages.gitHubCopy)}
238
cta={intl.formatMessage(cardMessages.gitHubButton)}
239
ctaAction="https://github.com/sponsors/mastodon"
240
imageSrc={gitHubLogo}
241
imageAlt={intl.formatMessage(cardMessages.logoAlt, {
242
name: "GitHub",
243
})}
244
/>
245
</StyledTabPanel>
246
<StyledTabPanel
247
title={
248
<FormattedMessage
249
id="sponsors.donate_header.corporate"
250
defaultMessage="Corporate gifts & matching"
251
/>
252
}
253
plain={plain}
254
>
255
<DonateCard
256
title={intl.formatMessage(cardMessages.corpSponsorTitle)}
257
copy={intl.formatMessage(cardMessages.corpSponsorCopy)}
258
cta={intl.formatMessage(cardMessages.corpSponsorButton)}
259
ctaAction="https://sponsor.joinmastodon.org/"
260
imageSrc={sponsorshipIcon}
261
/>
262
<DonateCard
263
title={intl.formatMessage(cardMessages.corpMatchTitle)}
264
copy={intl.formatMessage(cardMessages.corpMatchCopy)}
265
cta={intl.formatMessage(cardMessages.corpMatchButton)}
266
ctaAction="https://causes.benevity.org/causes/276-5575947211653_d7e4"
267
imageSrc={benevityLogo}
268
imageAlt={intl.formatMessage(cardMessages.logoAlt, {
269
name: "Benevity",
270
})}
271
/>
272
</StyledTabPanel>
273
</>
274
)
275
}
276
277
const StyledTabPanel: FC<
278
PropsWithChildren<{ title: ReactNode; footer?: ReactNode; plain?: boolean }>
279
> = ({ children, title, footer, plain }) => {
280
const Wrapper = plain ? "div" : TabPanel
281
return (
282
<Wrapper
283
className={classNames(plain && "border border-gray-3 p-4 rounded-2xl")}
284
>
285
<h2 className="text-h5 font-semibold mb-8 text-center">{title}</h2>
286
<div className="flex flex-col sm:flex-row items-center gap-4 justify-center">
287
{children}
288
</div>
289
{footer && (
290
<div className="mt-8 p-4 bg-gray-4 text-gray-1 rounded-lg">
291
{footer}
292
</div>
293
)}
294
</Wrapper>
295
)
296
}
297
298
interface DonateCardProps {
299
title: string
300
imageSrc?: string
301
imageAlt?: string
302
copy: string
303
cta: string
304
ctaAction: string | "popup"
305
}
306
307
const DonateCard = ({
308
title,
309
imageSrc,
310
imageAlt = "",
311
copy,
312
cta,
313
ctaAction,
314
}: DonateCardProps) => (
315
<div className="flex flex-col items-center text-center max-w-80">
316
{imageSrc && (
317
<Image
318
src={imageSrc}
319
className="aspect-square"
320
width="40"
321
height="40"
322
alt={imageAlt}
323
/>
324
)}
325
<h3 className="text-b1 font-semibold mt-5 mb-2">{title}</h3>
326
<p className="b2 grow mb-8 text-gray-1">{copy}</p>
327
{ctaAction !== "popup" ? (
328
<LinkButton size="medium" href={ctaAction} light>
329
{cta}
330
<ExternalLinkIcon className="size-5 ml-2 fill-current" />
331
</LinkButton>
332
) : (
333
<DonatePopup size="medium" className="!font-semibold p-4">
334
{cta}
335
</DonatePopup>
336
)}
337
</div>
338
)
339
340