The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: premium trial gifts

Luna e0414cb5 43a2bd2f

+88 -9
+26
app/(home)/premium/api.ts
··· 1 + import { defaultFetchOptions } from "@/lib/api"; 2 + import type { ApiError } from "@/typings"; 3 + 4 + export interface ApiV1GiftGetResponse { 5 + id: string; 6 + 7 + creatorId: string; 8 + recipientId: string | null; 9 + 10 + days: number; 11 + 12 + createdAt: string; 13 + } 14 + 15 + export async function getGift(giftId: string): Promise<ApiV1GiftGetResponse | ApiError | null> { 16 + const res = await fetch( 17 + `${process.env.NEXT_PUBLIC_API}/gifts/${giftId}`, 18 + { 19 + ...defaultFetchOptions, 20 + next: { revalidate: 1 } 21 + } 22 + ); 23 + 24 + if (!res.ok) return null; 25 + return res.json(); 26 + }
+11 -4
app/(home)/premium/checkout/api.ts
··· 4 4 url: string; 5 5 } 6 6 7 - export async function createCheckout(session: string, donationQuantity: number, referer: string) { 8 - const response = await fetch(`${process.env.NEXT_PUBLIC_API}/users/@me/billing/checkout`, { 9 - method: "PUT", 7 + export async function createCheckout( 8 + session: string, 9 + body: { 10 + donationQuantity: number; 11 + giftId: string | null; 12 + referer: string | null; 13 + } 14 + ) { 15 + const response = await fetch(`${process.env.NEXT_PUBLIC_API}/billing/subscriptions`, { 16 + method: "POST", 10 17 headers: { 11 18 "Content-Type": "application/json", 12 19 Cookie: `session=${session}` 13 20 }, 14 - body: JSON.stringify({ donationQuantity, referer }) 21 + body: JSON.stringify(body) 15 22 }); 16 23 17 24 const res = await response.json() as CheckoutResponse | ApiError;
+8 -4
app/(home)/premium/checkout/route.ts
··· 14 14 redirect("/login?callback=" + encodeURIComponent("/" + request.url.split("/").slice(3).join("/"))); 15 15 } 16 16 17 - const donationQuantity = Number.parseInt(searchParams.get("donation") || "0", 10); 18 - const referer = request.headers.get("referer") || ""; 19 - 20 - const url = await createCheckout(session.value, donationQuantity, referer) 17 + const url = await createCheckout( 18 + session.value, 19 + { 20 + donationQuantity: Number.parseInt(searchParams.get("donation") || "0", 10), 21 + giftId: searchParams.get("gift"), 22 + referer: request.headers.get("referer") 23 + } 24 + ) 21 25 .catch((error) => error); 22 26 23 27 if (url instanceof Error) {
+36
app/(home)/premium/gfit-banner.tsx
··· 1 + "use client"; 2 + import Notice from "@/components/notice"; 3 + import { Skeleton } from "@/components/ui/skeleton"; 4 + import { useSearchParams } from "next/navigation"; 5 + import { Suspense } from "react"; 6 + 7 + import { getGift } from "./api"; 8 + 9 + export function GiftBanner() { 10 + const search = useSearchParams(); 11 + const giftId = search.get("gift"); 12 + 13 + if (!giftId) { 14 + return null; 15 + } 16 + 17 + return ( 18 + <Suspense 19 + fallback={<Skeleton className="h-12 w-full rounded-lg mb-4" />} 20 + > 21 + <InnerGiftBanner giftId={giftId} /> 22 + </Suspense> 23 + ); 24 + } 25 + 26 + async function InnerGiftBanner({ giftId }: { giftId: string; }) { 27 + const gift = await getGift(giftId); 28 + 29 + if (!gift || "message" in gift) { 30 + return <div>Gift not found</div>; 31 + } 32 + 33 + return ( 34 + <Notice message={`${gift.days} day trial will be applied at checkout`} /> 35 + ); 36 + }
+3
app/(home)/premium/page.tsx
··· 18 18 import { HiArrowRight, HiLightningBolt, HiOutlineCheck, HiOutlineInformationCircle, HiUser, HiUserGroup, HiX } from "react-icons/hi"; 19 19 import { IoMdInfinite } from "react-icons/io"; 20 20 21 + import { GiftBanner } from "./gfit-banner"; 21 22 import { Subscribe } from "./subscribe.component"; 22 23 23 24 const montserrat = Montserrat({ subsets: ["latin"] }); ··· 91 92 (˶˃ ᵕ ˂˶) 92 93 </span> 93 94 </div> 95 + 96 + <GiftBanner /> 94 97 95 98 <div className="dark:bg-wamellow bg-wamellow-100 dark:text-neutral-300 text-neutral-700 mt-2 w-full rounded-xl overflow-hidden"> 96 99
+4 -1
app/(home)/premium/subscribe.component.tsx
··· 6 6 import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; 7 7 import { cn } from "@/utils/cn"; 8 8 import Link from "next/link"; 9 + import { useSearchParams } from "next/navigation"; 9 10 import { type HTMLProps, useState } from "react"; 10 11 import { HiArrowDown, HiArrowUp, HiLightningBolt, HiOutlineInformationCircle } from "react-icons/hi"; 11 12 12 13 export function Subscribe({ header }: { header?: boolean; }) { 14 + const search = useSearchParams(); 15 + 13 16 const premium = userStore((u) => u?.premium || false); 14 17 const [donation, setDonation] = useState(0); 15 18 ··· 52 55 > 53 56 <Link 54 57 prefetch={false} 55 - href={`/premium/checkout?donation=${donation}`} 58 + href={`/premium/checkout?${new URLSearchParams({ donation: donation.toString(), gift: search.get("gift") || "" }).toString()}`} 56 59 > 57 60 <HiLightningBolt /> 58 61 Subscribe