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.

update components of passport

Luna d7e8ef22 55012758

+234 -205
+3 -2
app/(home)/page.tsx
··· 69 69 as={Link} 70 70 startContent={<HiUserAdd />} 71 71 href="/login?invite=true" 72 - className="button-primary" 72 + color="secondary" 73 73 > 74 74 <span className="block sm:hidden">Invite</span> 75 75 <span className="hidden sm:block">Invite Wamellow</span> ··· 106 106 <ServerButton 107 107 as={Link} 108 108 startContent={<HiUserAdd />} 109 - className="button-primary w-1/2 lg:w-fit !text-xl !font-medium" 109 + className="w-1/2 lg:w-fit !text-xl !font-medium" 110 + color="secondary" 110 111 href="/login?invite=true" 111 112 size="lg" 112 113 >
+184 -167
app/passport/[guildId]/VerifyComponent.tsx app/passport/[guildId]/verify.component.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/image-reduce-motion"; 9 - import LoginButton from "@/components/login-button"; 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"} 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 - 1 + "use client"; 2 + import { Button } from "@nextui-org/react"; 3 + import Link from "next/link"; 4 + import { FunctionComponent, useEffect, useRef, useState } from "react"; 5 + import { BsDiscord } from "react-icons/bs"; 6 + import { HiExclamation, HiFingerPrint, HiLockClosed } from "react-icons/hi"; 7 + 8 + import { userStore } from "@/common/user"; 9 + import ImageReduceMotion from "@/components/image-reduce-motion"; 10 + import { GT4Init } from "@/lib/gt4"; 11 + import { ApiV1GuildsGetResponse } from "@/typings"; 12 + import cn from "@/utils/cn"; 13 + 14 + const VerifyComponent: FunctionComponent<{ guild: ApiV1GuildsGetResponse }> = ({ guild }) => { 15 + const user = userStore((s) => s); 16 + 17 + const btnRef = useRef<HTMLButtonElement>(null); 18 + 19 + const [state, setState] = useState<"LOADING" | "ERRORED" | "SUCCESS" | undefined>(); 20 + const [error, setError] = useState<string>(); 21 + 22 + useEffect(() => { 23 + if (!user?.id) return; 24 + const { init } = GT4Init(); 25 + 26 + init( 27 + { 28 + captchaId: process.env.NEXT_PUBLIC_CAPTCHA_ID, 29 + product: "bind", 30 + // riskType: "icon", 31 + hideSuccess: true, 32 + userInfo: user?.id 33 + }, 34 + handlerForBind 35 + ); 36 + 37 + }, [user]); 38 + 39 + // @ts-expect-error GeeTest types suck 40 + async function handlerForBind(c) { 41 + 42 + const button = btnRef.current; 43 + let isReady = false; 44 + 45 + c.onReady(() => { 46 + isReady = true; 47 + }); 48 + 49 + button?.addEventListener("click", function () { 50 + if (!isReady) return; 51 + if (state === "SUCCESS") return; 52 + setState(undefined); 53 + setError(undefined); 54 + 55 + c.showCaptcha(); 56 + }); 57 + 58 + c.onSuccess(async () => { 59 + setState("LOADING"); 60 + 61 + const result = c.getValidate(); 62 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guild.id}/passport-verification/captcha`, { 63 + method: "POST", 64 + headers: { 65 + authorization: localStorage.getItem("token") ?? "", 66 + "Content-Type": "application/json" 67 + }, 68 + body: JSON.stringify(result) 69 + }) 70 + .catch(() => null); 71 + 72 + switch (res?.status) { 73 + case 200: 74 + setState("SUCCESS"); 75 + break; 76 + default: 77 + setState("ERRORED"); 78 + setError((await res?.json())?.message || "Unknown server error"); 79 + break; 80 + } 81 + 82 + }); 83 + 84 + c.onError(() => { 85 + c.reset(); 86 + setState("ERRORED"); 87 + setError("Unknown captcha error"); 88 + }); 89 + 90 + c.onClose(() => { 91 + c.reset(); 92 + setState(undefined); 93 + }); 94 + 95 + c.onFail(async () => { 96 + setState("LOADING"); 97 + 98 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guild.id}/passport-verification/captcha?captcha-failed=true`, { 99 + method: "POST", 100 + headers: { 101 + authorization: localStorage.getItem("token") ?? "" 102 + } 103 + }) 104 + .catch(() => null); 105 + 106 + switch (res?.status) { 107 + case 400: 108 + setState(undefined); 109 + break; 110 + default: 111 + c.destroy(); 112 + setState("ERRORED"); 113 + setError((await res?.json())?.message || "Unknown server error"); 114 + break; 115 + } 116 + 117 + }); 118 + 119 + } 120 + 121 + return ( 122 + <div className="flex flex-col gap-3 w-full mt-4"> 123 + 124 + <div className="flex items-center gap-1.5 dark:bg-wamellow-alpha bg-wamellow-100-alpha w-fit py-1 pl-2 pr-3 rounded-full"> 125 + <ImageReduceMotion url={`https://cdn.discordapp.com/avatars/${user?.id}/${user?.avatar}`} size={20} alt="your avatar" className="rounded-full h-5 w-5" /> 126 + <span className="text-sm">@{user?.username}</span> 127 + </div> 128 + 129 + {error && 130 + <div className="flex items-center gap-1 text-red-400 text-sm"> 131 + <HiExclamation className="h-5 w-5 relative top-[1px]" /> 132 + {error} 133 + </div> 134 + } 135 + 136 + { 137 + (user && !user.id) || error?.includes("email") ? 138 + <Button 139 + className="button-blurple font-medium w-full" 140 + startContent={<BsDiscord />} 141 + > 142 + Login to Verify 143 + </Button> 144 + : 145 + <Button 146 + ref={btnRef} 147 + color={state === "ERRORED" ? "danger" : state === "SUCCESS" ? "success" : "secondary"} 148 + className={cn(state === "ERRORED" && "cursor-not-allowed", state === "SUCCESS" && "cursor-not-allowed", "font-medium w-full")} 149 + isDisabled={state === "ERRORED" || state === "SUCCESS"} 150 + isLoading={state === "LOADING"} 151 + startContent={<HiFingerPrint />} 152 + > 153 + Complete verification 154 + </Button> 155 + } 156 + 157 + <div className="flex w-full gap-2"> 158 + <Button 159 + as={Link} 160 + href="/support" 161 + target="_blank" 162 + variant="faded" 163 + className="w-1/2" 164 + startContent={<BsDiscord />} 165 + > 166 + Support 167 + </Button> 168 + <Button 169 + as={Link} 170 + href="/privacy" 171 + target="_blank" 172 + variant="faded" 173 + className="w-1/2" 174 + startContent={<HiLockClosed />} 175 + > 176 + Privacy 177 + </Button> 178 + </div> 179 + 180 + </div > 181 + ); 182 + 183 + }; 184 + 168 185 export default VerifyComponent;
+21
app/passport/[guildId]/api.ts
··· 1 + import { ApiV1GuildsGetResponse, ApiV1GuildsModulesPassportGetResponse } from "@/typings"; 2 + 3 + export async function getGuild(guildId: string): Promise<ApiV1GuildsGetResponse> { 4 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}`, { 5 + headers: { Authorization: process.env.API_SECRET as string }, 6 + next: { revalidate: 60 * 60 } 7 + }); 8 + 9 + const guild = await res.json(); 10 + return guild; 11 + } 12 + 13 + export async function getPassport(guildId: string): Promise<ApiV1GuildsModulesPassportGetResponse> { 14 + const res = await fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/passport-verification`, { 15 + headers: { Authorization: process.env.API_SECRET as string }, 16 + next: { revalidate: 60 } 17 + }); 18 + 19 + const passport = await res.json(); 20 + return passport; 21 + }
+25 -35
app/passport/[guildId]/page.tsx
··· 1 1 import { Metadata } from "next"; 2 2 import Image from "next/image"; 3 3 import { BsDiscord } from "react-icons/bs"; 4 - import { HiChartBar, HiCheck, HiLightningBolt, HiLockClosed, HiStar, HiX } from "react-icons/hi"; 4 + import { HiChartBar, HiCheck, HiLightningBolt, HiLockClosed, HiStar, HiUsers, HiX } from "react-icons/hi"; 5 5 6 6 import ErrorBanner from "@/components/Error"; 7 7 import ImageReduceMotion from "@/components/image-reduce-motion"; 8 8 import { ListFeature } from "@/components/list"; 9 9 import OverviewLinkComponent from "@/components/OverviewLinkComponent"; 10 - import { ApiV1GuildsGetResponse, ApiV1GuildsModulesPassportGetResponse } from "@/typings"; 10 + import paintPic from "@/public/paint.webp"; 11 11 import decimalToRgb from "@/utils/decimalToRgb"; 12 12 import { getCanonicalUrl } from "@/utils/urls"; 13 13 14 - import VerifyComponent from "./VerifyComponent"; 14 + import { getGuild, getPassport } from "./api"; 15 + import Verify from "./verify.component"; 15 16 16 17 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 18 38 19 export const generateMetadata = async ({ 39 20 params ··· 95 76 96 77 <div className="w-full md:max-w-[384px] overflow-hidden rounded-xl dark:bg-wamellow bg-wamellow-100 relative"> 97 78 98 - <div className="mb-8 h-[216px]" style={{ background: "url(/paint.jpg)", backgroundRepeat: "no-repeat", backgroundSize: "cover" }} /> 79 + <Image 80 + alt="" 81 + className="w-full object-cover h-[216px]" 82 + src={guild.banner ? `https://cdn.discordapp.com/banners/${guild?.id}/${guild?.banner}?size=512` : paintPic.src} 83 + width={3840 / 10} 84 + height={2160 / 10} 85 + /> 99 86 <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 87 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> 88 + <div 89 + className="text-lg flex gap-5 items-center absolute top-[146px] rounded-3xl z-20 left-[4px] md:left-2 py-4 px-5 backdrop-blur-3xl backdrop-brightness-90 shadow-md" 90 + > 91 + <ImageReduceMotion url={`https://cdn.discordapp.com/icons/${guild?.id}/${guild?.icon}`} size={128} alt="Server icon" className="rounded-full h-14 w-14 ring-offset-[var(--background-rgb)] ring-2 ring-offset-2 ring-violet-400/40" /> 92 + <div className="flex flex-col gap-1"> 93 + <div className="text-2xl dark:text-neutral-200 text-neutral-800 font-medium">{guild?.name || "Unknown Server"}</div> 94 + <div className="text-sm font-semibold flex items-center gap-1"> 95 + <HiUsers /> {intl.format(guild?.memberCount || 0)} 96 + <Image src="https://cdn.discordapp.com/emojis/875797879401361408.webp" width={18} height={18} alt="boost icon" className="ml-2" /> Level {guild.premiumTier} 97 + </div> 107 98 </div> 108 99 </div> 109 100 110 - <div className="mx-4 mb-4"> 101 + <div className="mx-4 mb-4 mt-10 font-medium"> 111 102 112 - <span className="text-sm font-semibold dark:text-neutral-400 text-neutral-600">GET ACCESS TO</span> 103 + <span className="text-sm font-bold dark:text-neutral-400 text-neutral-600">GET ACCESS TO</span> 113 104 <ul> 114 105 {[ 115 106 "Secure server", 116 - `${intl.format(guild.memberCount)} members`, 117 - `${guild.channelCount} channels` 107 + `${intl.format(guild.memberCount)} members` 118 108 ].map((name) => ( 119 109 <li key={name} className="flex gap-1 items-center"> 120 110 <HiCheck className="text-violet-400" /> ··· 127 117 </li> 128 118 </ul> 129 119 130 - <VerifyComponent guild={guild} /> 120 + <Verify guild={guild} /> 131 121 132 122 </div> 133 123
+1 -1
typings.ts
··· 34 34 id: string; 35 35 name: string; 36 36 icon: string | null; 37 + banner: string | null; 37 38 memberCount: number; 38 - channelCount: number; 39 39 premiumTier: number; 40 40 follownewsChannel?: { 41 41 id?: string;