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

Configure Feed

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

add passport public page

Luna a146751a b7a738a9

+334
+168
app/passport/[guildId]/VerifyComponent.tsx
··· 1 + "use client"; 2 + import Link from "next/link"; 3 + import { FunctionComponent, useEffect, useRef, useState } from "react"; 4 + import { BsDiscord } from "react-icons/bs"; 5 + import { HiExclamation, HiFingerPrint, HiLockClosed } from "react-icons/hi"; 6 + 7 + import { userStore } from "@/common/user"; 8 + import ImageReduceMotion from "@/components/ImageReduceMotion"; 9 + import LoginButton from "@/components/LoginButton"; 10 + import { GT4Init } from "@/lib/gt4"; 11 + import { ApiV1GuildsGetResponse } from "@/typings"; 12 + 13 + const VerifyComponent: FunctionComponent<{ guild: ApiV1GuildsGetResponse }> = ({ guild }) => { 14 + const user = userStore((s) => s); 15 + 16 + const btnRef = useRef<HTMLButtonElement>(null); 17 + 18 + const [state, setState] = useState<"LOADING" | "ERRORED" | "SUCCESS" | undefined>(); 19 + const [error, setError] = useState<string>(); 20 + 21 + useEffect(() => { 22 + if (!user?.id) return; 23 + const { init } = GT4Init(); 24 + 25 + init( 26 + { 27 + captchaId: process.env.NEXT_PUBLIC_CAPTCHA_ID, 28 + product: "bind", 29 + // riskType: "icon", 30 + hideSuccess: true, 31 + userInfo: user?.id 32 + }, 33 + handlerForBind 34 + ); 35 + 36 + }, [user]); 37 + 38 + // @ts-expect-error GeeTest types suck 39 + async function handlerForBind(c) { 40 + 41 + const button = btnRef.current; 42 + let isReady = false; 43 + 44 + c.onReady(() => { 45 + isReady = true; 46 + }); 47 + 48 + button?.addEventListener("click", function () { 49 + if (!isReady) return; 50 + if (state === "SUCCESS") return; 51 + setState(undefined); 52 + setError(undefined); 53 + 54 + c.showCaptcha(); 55 + }); 56 + 57 + c.onSuccess(async () => { 58 + setState("LOADING"); 59 + 60 + const result = c.getValidate(); 61 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guild.id}/passport-verification/captcha`, { 62 + method: "POST", 63 + headers: { 64 + authorization: localStorage.getItem("token") ?? "", 65 + "Content-Type": "application/json" 66 + }, 67 + body: JSON.stringify(result) 68 + }) 69 + .catch(() => null); 70 + 71 + switch (res?.status) { 72 + case 200: 73 + setState("SUCCESS"); 74 + break; 75 + default: 76 + setState("ERRORED"); 77 + setError((await res?.json())?.message || "Unknown server error"); 78 + break; 79 + } 80 + 81 + }); 82 + 83 + c.onError(() => { 84 + c.reset(); 85 + setState("ERRORED"); 86 + setError("Unknown captcha error"); 87 + }); 88 + 89 + c.onClose(() => { 90 + c.reset(); 91 + setState(undefined); 92 + }); 93 + 94 + c.onFail(async () => { 95 + setState("LOADING"); 96 + 97 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guild.id}/passport-verification/captcha?captcha-failed=true`, { 98 + method: "POST", 99 + headers: { 100 + authorization: localStorage.getItem("token") ?? "" 101 + } 102 + }) 103 + .catch(() => null); 104 + 105 + switch (res?.status) { 106 + case 400: 107 + setState(undefined); 108 + break; 109 + default: 110 + c.destroy(); 111 + setState("ERRORED"); 112 + setError((await res?.json())?.message || "Unknown server error"); 113 + break; 114 + } 115 + 116 + }); 117 + 118 + } 119 + 120 + return ( 121 + <div className="flex flex-col gap-3 w-full mt-4"> 122 + 123 + <div className="flex items-center dark:bg-wamellow-alpha bg-wamellow-100-alpha w-fit py-1 pl-2 pr-3 rounded-full"> 124 + <ImageReduceMotion url={user?.id ? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}` : "/discord.png"} size={20} alt="your avatar" className="rounded-full mr-1 h-5 w-5" /> 125 + <span className="pb-[2px] text-sm">{user?.global_name || `@${user?.username}`}</span> 126 + </div> 127 + 128 + {error && 129 + <div className="flex items-center gap-1 text-red-400 text-sm"> 130 + <HiExclamation className="h-5 w-5 relative top-[1px]" /> 131 + {error} 132 + </div> 133 + } 134 + 135 + {(user && !user.id) || error?.includes("email") ? 136 + <LoginButton 137 + className="w-full text-center border-2 border-wamellow-alpha rounded-md hover:border-blurple hover:bg-blurple duration-300" 138 + addClassName="justify-center" 139 + message="Login to verify" 140 + /> 141 + : 142 + <button 143 + ref={btnRef} 144 + className={`${state === "ERRORED" && "bg-red-500/80 hover:bg-red-500/60 opacity-80 cursor-not-allowed"} ${state === "SUCCESS" && "bg-green-500/80 hover:bg-green-500/60 cursor-not-allowed"} ${(!state || state === "LOADING") && "bg-violet-600 hover:bg-violet-600/80"} flex text-neutral-200 font-medium py-2 px-4 rounded-md duration-200 w-full justify-center`} 145 + disabled={state === "ERRORED" || state === "SUCCESS"} 146 + > 147 + <HiFingerPrint className="relative top-1" /> 148 + <span className="ml-2">Complete verification</span> 149 + </button> 150 + } 151 + 152 + <div className="flex w-full gap-2"> 153 + <Link href="/support" className="flex dark:bg-wamellow-alpha bg-wamellow-100-alpha hover:bg-blurple hover:text-white py-2 px-4 rounded-md duration-200 w-1/2 justify-center"> 154 + <BsDiscord className="relative top-1" /> 155 + <span className="ml-2">Support</span> 156 + </Link> 157 + <Link href="/privacy" className="flex dark:bg-wamellow-alpha bg-wamellow-100-alpha dark:hover:bg-wamellow-light hover:bg-wamellow-100-light dark:hover:text-white py-2 px-4 rounded-md duration-200 w-1/2 justify-center"> 158 + <HiLockClosed className="relative top-1" /> 159 + <span className="ml-1">Privacy</span> 160 + </Link> 161 + </div> 162 + 163 + </div> 164 + ); 165 + 166 + }; 167 + 168 + export default VerifyComponent;
+166
app/passport/[guildId]/page.tsx
··· 1 + import { Metadata } from "next"; 2 + import Image from "next/image"; 3 + import { BsDiscord } from "react-icons/bs"; 4 + import { HiChartBar, HiCheck, HiLightningBolt, HiLockClosed, HiStar, HiX } from "react-icons/hi"; 5 + 6 + import ErrorBanner from "@/components/Error"; 7 + import ImageReduceMotion from "@/components/ImageReduceMotion"; 8 + import { ListFeature } from "@/components/List"; 9 + import OverviewLinkComponent from "@/components/OverviewLinkComponent"; 10 + import { ApiV1GuildsGetResponse, ApiV1GuildsModulesPassportGetResponse } from "@/typings"; 11 + import decimalToRgb from "@/utils/decimalToRgb"; 12 + import { getCanonicalUrl } from "@/utils/urls"; 13 + 14 + import VerifyComponent from "./VerifyComponent"; 15 + 16 + interface PassportProps { params: { guildId: string }, searchParams: { page: string, type: string } } 17 + 18 + async function getGuild(guildId: string): Promise<ApiV1GuildsGetResponse> { 19 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}`, { 20 + headers: { Authorization: process.env.API_SECRET as string }, 21 + next: { revalidate: 60 * 60 } 22 + }); 23 + 24 + const guild = await res.json(); 25 + return guild; 26 + } 27 + 28 + async function getPassport(guildId: string): Promise<ApiV1GuildsModulesPassportGetResponse> { 29 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/passport-verification`, { 30 + headers: { Authorization: process.env.API_SECRET as string }, 31 + next: { revalidate: 60 } 32 + }); 33 + 34 + const passport = await res.json(); 35 + return passport; 36 + } 37 + 38 + export const generateMetadata = async ({ 39 + params 40 + }: PassportProps): Promise<Metadata> => { 41 + const guild = await getGuild(params.guildId); 42 + 43 + const title = `Verify in ${guild.name}`; 44 + const description = `Easily verify yourself in ${guild.name} with a simple and safe captcha in the web to gain access all channels.`; 45 + const url = getCanonicalUrl("passport", params.guildId); 46 + 47 + return { 48 + title, 49 + description, 50 + alternates: { 51 + canonical: url 52 + }, 53 + openGraph: { 54 + title, 55 + description, 56 + url, 57 + type: "website", 58 + images: guild?.icon ? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.webp?size=256` : "/discord.png" 59 + }, 60 + twitter: { 61 + card: "summary", 62 + title, 63 + description 64 + } 65 + }; 66 + }; 67 + 68 + export default async function Home({ params }: PassportProps) { 69 + const guildPromise = getGuild(params.guildId); 70 + const passportPromise = getPassport(params.guildId); 71 + 72 + const [guild, passport] = await Promise.all([guildPromise, passportPromise]); 73 + 74 + const backgroundRgb = decimalToRgb(passport.backgroundColor || 0); 75 + const intl = new Intl.NumberFormat("en", { notation: "standard" }); 76 + 77 + return ( 78 + <div className="w-full"> 79 + 80 + {passport.backgroundColor && 81 + <style> 82 + {` 83 + :root { 84 + --background-rgb: rgb(${backgroundRgb.r}, ${backgroundRgb.g}, ${backgroundRgb.b}); 85 + } 86 + `} 87 + </style> 88 + } 89 + 90 + {typeof passport === "object" && "statusCode" in passport && 91 + <ErrorBanner message={(passport as Record<string, string>).message} removeButton /> 92 + } 93 + 94 + <div className="grid md:flex gap-6"> 95 + 96 + <div className="w-full md:max-w-[384px] overflow-hidden rounded-xl dark:bg-wamellow bg-wamellow-100 relative"> 97 + 98 + <div className="mb-8 h-[216px]" style={{ background: "url(/paint.jpg)", backgroundRepeat: "no-repeat", backgroundSize: "cover" }} /> 99 + <div className="absolute top-0 w-full h-[216px]" style={{ background: "linear-gradient(180deg, rgba(0,0,0,0) 50%, var(--wamellow-rgb) 100%)" }} /> 100 + 101 + <div className="text-lg flex gap-2 items-center absolute top-[156px] ml-4"> 102 + <ImageReduceMotion url={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`} size={128} alt="Server icon" className="rounded-xl h-16 w-16" /> 103 + <div> 104 + <div className="text-xl dark:text-neutral-200 text-neutral-800 font-semibold">{guild?.name || "Unknown Server"}</div> 105 + <div className="text-sm flex items-center gap-2"> <Image src="https://cdn.discordapp.com/emojis/924058182626721802.webp" width={18} height={18} alt="member icon" />{intl.format(guild.memberCount)} members </div> 106 + <div className="text-sm flex items-center gap-2"> <Image src="https://cdn.discordapp.com/emojis/875797879401361408.webp" width={18} height={18} alt="boost icon" />Level {guild.premiumTier}</div> 107 + </div> 108 + </div> 109 + 110 + <div className="mx-4 mb-4"> 111 + 112 + <span className="text-sm font-semibold dark:text-neutral-400 text-neutral-600">GET ACCESS TO</span> 113 + <ul> 114 + {[ 115 + "Secure server", 116 + `${intl.format(guild.memberCount)} members`, 117 + `${guild.channelCount} channels` 118 + ].map((name) => ( 119 + <li key={name} className="flex gap-1 items-center"> 120 + <HiCheck className="text-violet-400" /> 121 + {name} 122 + </li> 123 + ))} 124 + <li className="flex gap-1 items-center" title="The cake is a lie"> 125 + <HiX className="text-red-400" /> 126 + Cakes 127 + </li> 128 + </ul> 129 + 130 + <VerifyComponent guild={guild} /> 131 + 132 + </div> 133 + 134 + </div> 135 + 136 + <div> 137 + 138 + <div className="w-full h-min overflow-hidden rounded-xl dark:bg-wamellow bg-wamellow-100 py-4 px-5"> 139 + 140 + <div className="mb-4 text-neutral-100 font-semibold text-xl">Modern, Simple, Wamellow 👋</div> 141 + <ListFeature 142 + items={[ 143 + { icon: <HiLockClosed />, title: "Secure", description: "Unrivaled user privacy with our top-notch verification systems.", color: 0xa84b56 }, 144 + { icon: <BsDiscord />, title: "Integration", description: "Unparalleled Discord integration, setting us apart from the rest.", color: 0x4752c4 }, 145 + { icon: <HiStar />, title: "Easy", description: "The most user-friendly and visually appealing verification process.", color: 0x7f43d8 }, 146 + { icon: <HiLightningBolt />, title: "Fast", description: "Welcome new members swiftly with the fastest verification method available.", color: 0xff9156 } 147 + ]} 148 + /> 149 + <div className="mt-4 text-sm text-neutral-500">*We actually have no idea what to put here</div> 150 + </div> 151 + 152 + <OverviewLinkComponent 153 + className="mt-6" 154 + title="View Leaderboard" 155 + message="Easily access and view the top chatters, voice timers, and inviters from this server." 156 + url={`/leaderboard/${params.guildId}`} 157 + icon={<HiChartBar />} 158 + /> 159 + 160 + </div> 161 + 162 + </div> 163 + 164 + </div> 165 + ); 166 + }