tangled
alpha
login
or
join now
shi.gg
/
mellow-web
4
fork
atom
The weeb for the next gen discord boat - Wamellow
wamellow.com
bot
discord
4
fork
atom
overview
issues
pulls
pipelines
fix: billing payment method rendering
shi.gg
3 months ago
ef236a1d
efda0038
verified
This commit was signed with the committer's
known signature
.
shi.gg
SSH Key Fingerprint:
SHA256:f/mKQ+wsL4JFEMsfMBVIpFfwyYH0N5+n6IHwZA0Q+U0=
+33
-19
2 changed files
expand all
collapse all
unified
split
app
profile
billing
page.tsx
typings.ts
+20
-12
app/profile/billing/page.tsx
···
20
20
import { useRef, useState } from "react";
21
21
import { GrAmex } from "react-icons/gr";
22
22
import { HiCreditCard, HiLightningBolt } from "react-icons/hi";
23
23
-
import { SiDinersclub, SiDiscover, SiJcb, SiMastercard, SiStripe, SiVisa } from "react-icons/si";
23
23
+
import { SiDinersclub, SiDiscover, SiJcb, SiMastercard, SiPaypal, SiStripe, SiVisa } from "react-icons/si";
24
24
25
25
function isActive(status: ApiV1UsersMeBillingGetResponse["status"]): status is "active" | "trialing" | "past_due" {
26
26
return status === "active" || status === "trialing" || status === "past_due";
···
31
31
const [changeDonationModalOpen, setChangeDonationModalOpen] = useState(false);
32
32
33
33
const { data, isLoading, error, edit } = useApi<ApiV1UsersMeBillingGetResponse>("/users/@me/billing");
34
34
+
const [nowInSeconds] = useState(() => Date.now() / 1_000);
34
35
35
36
if ((isLoading && !user?.premium) || (!isLoading && !data) || (data && !isActive(data.status))) {
36
37
return (<>
···
56
57
</>);
57
58
}
58
59
59
59
-
const periodEndsInDays = Math.floor((((data?.currentPeriodEnd || 0) - Date.now() / 1_000) / (60 * 60 * 24)));
60
60
+
const periodEndsInDays = Math.floor(((data?.currentPeriodEnd || 0) - nowInSeconds) / (60 * 60 * 24));
60
61
const periodEndsInStr = `${periodEndsInDays > 1 ? "in " : ""}${periodEndsInDays === 0 ? "Today" : periodEndsInDays === 1 ? "Tomorrow" : periodEndsInDays} ${periodEndsInDays > 1 ? "days" : ""}`;
61
62
62
63
return (
···
126
127
<h2 className="font-semibold text-xl text-neutral-300 mb-2 lg:mb-0 lg:relative lg:bottom-2">Payment Method</h2>
127
128
{isLoading
128
129
? <Skeleton className="h-12 w-full" />
129
129
-
:
130
130
-
<div className="flex gap-2 items-center bg-wamellow-100 px-4 py-1 rounded-lg">
130
130
+
: <div className="flex gap-2 items-center bg-wamellow-100 px-4 py-1 rounded-lg">
131
131
<PaymentMethodIcon method={data!.paymentMethod} />
132
132
-
{typeof data?.paymentMethod === "string" ? data?.paymentMethod : "**** **** **** " + data?.paymentMethod?.last4}
132
132
+
{getPaymentMethodInfo(data!.paymentMethod)}
133
133
+
133
134
<Button
134
135
asChild
135
136
className="ml-auto"
···
184
185
return "subscriptions/" + data.subscriptionId + "/cancel";
185
186
}
186
187
187
187
-
function PaymentMethodIcon({ method }: { method: ApiV1UsersMeBillingGetResponse["paymentMethod"]; }) {
188
188
-
if (typeof method === "string") {
189
189
-
return <HiCreditCard className="size-6" />;
190
190
-
}
188
188
+
function PaymentMethodIcon({ method }: { method?: ApiV1UsersMeBillingGetResponse["paymentMethod"]; }) {
189
189
+
if (!method) return <HiCreditCard className="size-6" />;
191
190
192
192
-
switch (method?.brand) {
191
191
+
switch (method.brand) {
192
192
+
case "paypal": return <SiPaypal className="size-6" />;
193
193
case "amex": return <GrAmex className="size-6" />;
194
194
case "diners": return <SiDinersclub className="size-6" />;
195
195
case "discover": return <SiDiscover className="size-6" />;
196
196
case "jcb": return <SiJcb className="size-6" />;
197
197
case "link": return <SiStripe className="size-6" />;
198
198
case "mastercard": return <SiMastercard className="size-6" />;
199
199
-
case "visa": return <SiVisa className="size-6"/>;
199
199
+
case "visa": return <SiVisa className="size-6" />;
200
200
+
default: return <HiCreditCard className="size-6" />;
200
201
}
202
202
+
}
201
203
202
202
-
return <HiCreditCard className="size-6" />;
204
204
+
function getPaymentMethodInfo(method?: ApiV1UsersMeBillingGetResponse["paymentMethod"]) {
205
205
+
if (!method) return "Unknown";
206
206
+
207
207
+
if ("email" in method) return method.email ?? "PayPal";
208
208
+
if ("last4" in method) return method.last4 ? `•••• •••• •••• ${method.last4}` : "Card";
209
209
+
210
210
+
return "Unknown";
203
211
}
204
212
205
213
function PremiumGuildSelect({
+13
-7
typings.ts
···
330
330
type: ConnectionType;
331
331
}
332
332
333
333
+
interface PaymentMethodCard {
334
334
+
brand: string | null;
335
335
+
last4: string | null;
336
336
+
}
337
337
+
338
338
+
interface PaymentMethodPaypal {
339
339
+
brand: "paypal";
340
340
+
email: string | null;
341
341
+
}
342
342
+
343
343
+
type PaymentMethod = PaymentMethodCard | PaymentMethodPaypal;
344
344
+
333
345
export interface ApiV1UsersMeBillingGetResponse {
334
346
subscriptionId: string;
335
347
status:
···
347
359
currentPeriodStart: number;
348
360
cancelAtPeriodEnd: boolean;
349
361
donationQuantity: number;
350
350
-
paymentMethod:
351
351
-
| {
352
352
-
brand: string | null;
353
353
-
last4: string | null;
354
354
-
}
355
355
-
| string
356
356
-
| null;
362
362
+
paymentMethod: PaymentMethod | null;
357
363
portalUrl: string;
358
364
guildIds: string[];
359
365
}