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

replace nextui buttons & tooltips with custom

shi.gg 4ab589c9 291b5732

verified
+570 -586
+1 -1
app/(home)/premium/subscribe.component.tsx
··· 97 97 value={donation} 98 98 /> 99 99 </InputBaseControl> 100 - <Tooltip delayDuration={0}> 100 + <Tooltip> 101 101 <InputBaseAdornment> 102 102 <InputBaseAdornmentButton asChild> 103 103 <TooltipTrigger>
+4 -6
app/(home)/status/side.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Accordion, AccordionItem, Chip } from "@nextui-org/react"; 3 + import { Accordion, AccordionItem } from "@nextui-org/react"; 4 4 import { useCookies } from "next-client-cookies"; 5 5 import { type ReactNode, useEffect, useMemo, useState } from "react"; 6 6 7 7 import DumbTextInput from "@/components/inputs/dumb-text-input"; 8 + import { Badge } from "@/components/ui/badge"; 8 9 import { intl } from "@/utils/numbers"; 9 10 10 11 import type { ApiV1StatusGetResponse } from "./api"; ··· 78 79 return ( 79 80 <div className="flex items-center justify-between"> 80 81 {name} 81 - <Chip 82 - className="select-none" 83 - radius="sm" 84 - > 82 + <Badge> 85 83 {children} 86 - </Chip> 84 + </Badge> 87 85 </div> 88 86 ); 89 87 }
+47 -52
app/dashboard/[guildId]/custom-commands/create.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Chip } from "@nextui-org/react"; 4 3 import { useState } from "react"; 5 4 import { HiPencil } from "react-icons/hi"; 6 5 7 6 import DumbTextInput from "@/components/inputs/dumb-text-input"; 8 7 import Modal from "@/components/modal"; 8 + import { Button } from "@/components/ui/button"; 9 9 import type { ApiV1GuildsModulesTagsGetResponse } from "@/typings"; 10 10 11 11 export enum Style { ··· 26 26 const [open, setOpen] = useState(false); 27 27 const [name, setName] = useState(""); 28 28 29 - return ( 30 - <> 31 - {style === Style.Compact 32 - ? 33 - <Chip 34 - as={Button} 35 - className="default" 36 - variant="faded" 37 - onClick={() => setOpen(true)} 38 - startContent={<HiPencil className="relative left-1 ml-1" />} 39 - > 40 - Create 41 - </Chip> 42 - : 43 - <Button 44 - color="secondary" 45 - onClick={() => setOpen(true)} 46 - startContent={<HiPencil />} 47 - > 48 - Create new tag 49 - </Button> 50 - } 51 - 52 - <Modal<ApiV1GuildsModulesTagsGetResponse> 53 - title="Create new tag" 54 - isOpen={open} 55 - onClose={() => setOpen(false)} 56 - onSubmit={() => { 57 - return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/modules/tags`, { 58 - method: "POST", 59 - credentials: "include", 60 - headers: { 61 - "Content-Type": "application/json" 62 - }, 63 - body: JSON.stringify({ name: name || "new-tag" }) 64 - }); 65 - }} 66 - onSuccess={(tag) => { 67 - addTag(tag); 68 - setTagId(tag.id); 69 - }} 29 + return (<> 30 + {style === Style.Compact 31 + ? 32 + <Button 33 + className="rounded-full h-8" 34 + onClick={() => setOpen(true)} 35 + > 36 + <HiPencil /> 37 + Create 38 + </Button> 39 + : 40 + <Button 41 + variant="secondary" 42 + onClick={() => setOpen(true)} 70 43 > 71 - <DumbTextInput 72 - name="Name" 73 - placeholder="new-tag" 74 - value={name} 75 - setValue={setName} 76 - max={32} 77 - /> 78 - </Modal> 79 - </> 80 - ); 44 + <HiPencil /> 45 + Create a new Tag 46 + </Button> 47 + } 81 48 49 + <Modal<ApiV1GuildsModulesTagsGetResponse> 50 + title="Create new tag" 51 + isOpen={open} 52 + onClose={() => setOpen(false)} 53 + onSubmit={() => { 54 + return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/modules/tags`, { 55 + method: "POST", 56 + credentials: "include", 57 + headers: { 58 + "Content-Type": "application/json" 59 + }, 60 + body: JSON.stringify({ name: name || "new-tag" }) 61 + }); 62 + }} 63 + onSuccess={(tag) => { 64 + addTag(tag); 65 + setTagId(tag.id); 66 + }} 67 + > 68 + <DumbTextInput 69 + name="Name" 70 + placeholder="new-tag" 71 + value={name} 72 + setValue={setName} 73 + max={32} 74 + /> 75 + </Modal> 76 + </>); 82 77 }
+33 -31
app/dashboard/[guildId]/custom-commands/delete.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Tooltip } from "@nextui-org/react"; 4 3 import { useState } from "react"; 5 4 import { HiTrash } from "react-icons/hi"; 6 5 7 6 import Modal from "@/components/modal"; 7 + import { Button } from "@/components/ui/button"; 8 + import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; 8 9 9 10 interface Props { 10 11 guildId: string; ··· 18 19 19 20 const [open, setOpen] = useState(false); 20 21 21 - return ( 22 - <> 23 - <Tooltip content="Delete Tag" closeDelay={0}> 22 + return (<> 23 + <Tooltip> 24 + <TooltipTrigger asChild> 24 25 <Button 25 - isIconOnly 26 - color="danger" 26 + className="size-9 p-1.5" 27 + variant="destructive" 27 28 onClick={() => setOpen(true)} 28 - isDisabled={!id} 29 + disabled={!id} 29 30 > 30 - <HiTrash className="h-5 w-5" /> 31 - <span className="sr-only">Delete selected tag</span> 31 + <HiTrash /> 32 32 </Button> 33 - </Tooltip> 34 - 35 - <Modal 36 - buttonName="Delete" 37 - variant="destructive" 38 - title={"Delete Tag: " + name} 39 - isOpen={open} 40 - onClose={() => setOpen(false)} 41 - onSubmit={() => { 42 - return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/modules/tags/${id}`, { 43 - method: "DELETE", 44 - credentials: "include" 45 - }); 46 - }} 47 - onSuccess={() => { 48 - if (id) removeTag(id); 49 - }} 50 - > 51 - Are you sure you want to delete the {"\""}{name}{"\""} tag? It will be gone forever, probably, who knows. 52 - </Modal> 53 - </> 54 - ); 33 + </TooltipTrigger> 34 + <TooltipContent> 35 + <p>Delete Tag</p> 36 + </TooltipContent> 37 + </Tooltip> 55 38 39 + <Modal 40 + buttonName="Delete" 41 + variant="destructive" 42 + title={"Delete Tag: " + name} 43 + isOpen={open} 44 + onClose={() => setOpen(false)} 45 + onSubmit={() => { 46 + return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/modules/tags/${id}`, { 47 + method: "DELETE", 48 + credentials: "include" 49 + }); 50 + }} 51 + onSuccess={() => { 52 + if (id) removeTag(id); 53 + }} 54 + > 55 + Are you sure you want to delete the {"\""}{name}{"\""} tag? It will be gone forever, probably, who knows. 56 + </Modal> 57 + </>); 56 58 }
+21 -18
app/dashboard/[guildId]/custom-commands/page.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Chip, Tooltip } from "@nextui-org/react"; 4 - import Image from "next/image"; 5 3 import { useParams, usePathname, useRouter, useSearchParams } from "next/navigation"; 6 4 import { useCallback, useEffect } from "react"; 7 5 import { HiViewGridAdd } from "react-icons/hi"; ··· 13 11 import SelectInput from "@/components/inputs/select-menu"; 14 12 import TextInput from "@/components/inputs/text-input"; 15 13 import { ScreenMessage } from "@/components/screen-message"; 14 + import { Button } from "@/components/ui/button"; 15 + import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; 16 16 import { cacheOptions, getData } from "@/lib/api"; 17 17 import { Permissions } from "@/lib/discord/enum/permissions"; 18 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 19 18 import type { ApiV1GuildsModulesTagsGetResponse } from "@/typings"; 20 19 21 20 import CreateTag, { Style } from "./create.component"; ··· 66 65 href={`/dashboard/${guild?.id}`} 67 66 button="Go back to overview" 68 67 icon={<HiViewGridAdd />} 69 - > 70 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 71 - </ScreenMessage> 68 + /> 72 69 ); 73 70 } 74 71 ··· 95 92 }; 96 93 97 94 const removeTag = (id: string) => { 98 - queryClient.setQueryData<ApiV1GuildsModulesTagsGetResponse[]>(url, () => 95 + queryClient.setQueryData<ApiV1GuildsModulesTagsGetResponse[]>(url, () => ( 99 96 data?.filter((t) => t.id !== id) || [] 100 - ); 97 + )); 101 98 }; 102 99 103 100 return (<> ··· 106 103 {data 107 104 .sort((a, b) => a.name.localeCompare(b.name)) 108 105 .map((tag) => ( 109 - <Chip 106 + <Button 110 107 key={"guildTags-" + tag.id} 111 - as={Button} 112 - className="default border-0" 113 - variant={id === tag.id ? "flat" : "faded"} 114 - color={id === tag.id ? "secondary" : undefined} 115 - startContent={<span className="opacity-50 relative left-2">{tag.applicationCommandId ? "/" : "wm -"}</span>} 108 + className="rounded-full h-8" 116 109 onClick={() => setTagId(tag.id)} 117 110 > 118 - {tag.name + " "} 119 - </Chip> 111 + <span className="opacity-50 text-sm"> 112 + {tag.applicationCommandId ? "/" : "wm -"} 113 + </span> 114 + {tag.name} 115 + </Button> 120 116 )) 121 117 } 122 118 ··· 128 124 /> 129 125 130 126 <div className="ml-auto flex items-center gap-4"> 131 - <Tooltip content="Created tags / Limit" closeDelay={0}> 132 - <span className="dark:text-neutral-600 text-neutral-400 cursor-default">{data.length}/{30}</span> 127 + <Tooltip> 128 + <TooltipTrigger> 129 + <span className="dark:text-neutral-600 text-neutral-400 cursor-default"> 130 + {data.length}/{30} 131 + </span> 132 + </TooltipTrigger> 133 + <TooltipContent> 134 + <p>{data.length} created tags / {30} limit</p> 135 + </TooltipContent> 133 136 </Tooltip> 134 137 135 138 <DeleteTag
+8 -9
app/dashboard/[guildId]/dailyposts/create.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Chip } from "@nextui-org/react"; 4 3 import { useMemo, useState } from "react"; 5 4 import { HiPencil } from "react-icons/hi"; 6 5 ··· 8 7 import MultiSelectMenu from "@/components/inputs/multi-select-menu"; 9 8 import SelectMenu from "@/components/inputs/select-menu"; 10 9 import Modal from "@/components/modal"; 10 + import { Button } from "@/components/ui/button"; 11 11 import { type ApiV1GuildsModulesDailypostsGetResponse, DailypostType } from "@/typings"; 12 12 import { createSelectableItems } from "@/utils/create-selectable-items"; 13 13 ··· 43 43 return (<> 44 44 {style === Style.Compact 45 45 ? 46 - <Chip 47 - as={Button} 48 - className="default" 46 + <Button 47 + className="rounded-full h-8" 49 48 onClick={() => setOpen(true)} 50 - startContent={<HiPencil className="relative left-1 ml-1" />} 51 49 > 52 - Add Dailypost 53 - </Chip> 50 + <HiPencil /> 51 + Create 52 + </Button> 54 53 : 55 54 <Button 56 - color="secondary" 55 + variant="secondary" 57 56 onClick={() => setOpen(true)} 58 - startContent={<HiPencil />} 59 57 > 58 + <HiPencil /> 60 59 Create a new Dailypost 61 60 </Button> 62 61 }
+15 -16
app/dashboard/[guildId]/dailyposts/delete.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Tooltip } from "@nextui-org/react"; 4 3 import { useState } from "react"; 5 4 import { HiTrash } from "react-icons/hi"; 6 5 7 6 import { guildStore } from "@/common/guilds"; 8 7 import Modal from "@/components/modal"; 8 + import { Button } from "@/components/ui/button"; 9 + import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; 9 10 10 11 interface Props { 11 12 id: string | null; ··· 24 25 const [open, setOpen] = useState(false); 25 26 26 27 return (<> 27 - <Tooltip 28 - content="Delete Dailypost" 29 - closeDelay={0} 30 - > 31 - <Button 32 - isIconOnly 33 - color="danger" 34 - variant="flat" 35 - onClick={() => setOpen(true)} 36 - isDisabled={!id} 37 - > 38 - <span> 28 + <Tooltip> 29 + <TooltipTrigger asChild> 30 + <Button 31 + className="size-9 p-1.5" 32 + variant="destructive" 33 + onClick={() => setOpen(true)} 34 + disabled={!id} 35 + > 39 36 <HiTrash /> 40 - </span> 41 - <span className="sr-only">Delete selected dailypost</span> 42 - </Button> 37 + </Button> 38 + </TooltipTrigger> 39 + <TooltipContent> 40 + <p>Delete Dailypost</p> 41 + </TooltipContent> 43 42 </Tooltip> 44 43 45 44 <Modal
+1 -4
app/dashboard/[guildId]/dailyposts/page.tsx
··· 13 13 import MultiSelectMenu from "@/components/inputs/multi-select-menu"; 14 14 import SelectMenu from "@/components/inputs/select-menu"; 15 15 import { ScreenMessage } from "@/components/screen-message"; 16 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 17 16 import type { ApiV1GuildsModulesDailypostsGetResponse } from "@/typings"; 18 17 import { createSelectableItems } from "@/utils/create-selectable-items"; 19 18 ··· 49 48 href={`/dashboard/${guild?.id}`} 50 49 button="Go back to overview" 51 50 icon={<HiViewGridAdd />} 52 - > 53 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 54 - </ScreenMessage> 51 + /> 55 52 ); 56 53 } 57 54
+23 -21
app/dashboard/[guildId]/layout.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Skeleton } from "@nextui-org/react"; 4 3 import Head from "next/head"; 5 - import Image from "next/image"; 6 4 import Link from "next/link"; 7 5 import { redirect, useParams } from "next/navigation"; 8 6 import { useCookies } from "next-client-cookies"; ··· 11 9 import { useQuery } from "react-query"; 12 10 13 11 import { guildStore } from "@/common/guilds"; 14 - import { ClientButton } from "@/components/client"; 15 12 import ImageReduceMotion from "@/components/image-reduce-motion"; 16 13 import { ListTab } from "@/components/list"; 17 14 import { ScreenMessage, SupportButton } from "@/components/screen-message"; 15 + import { Button } from "@/components/ui/button"; 16 + import { Skeleton } from "@/components/ui/skeleton"; 18 17 import { cacheOptions, getData } from "@/lib/api"; 19 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 20 18 import type { ApiV1GuildsChannelsGetResponse, ApiV1GuildsEmojisGetResponse, ApiV1GuildsGetResponse, ApiV1GuildsRolesGetResponse } from "@/typings"; 21 19 import { intl } from "@/utils/numbers"; 22 20 ··· 104 102 <div className="flex flex-col w-full"> 105 103 {guild?.name && ( 106 104 <Head> 107 - <title>{`${guild?.name}'s Dashboard`}</title> 105 + <title>{guild.name}{"'"}s Dashboard</title> 108 106 </Head> 109 107 )} 110 108 111 109 <div className="flex flex-col gap-5 mb-3"> 112 110 <Button 113 - as={Link} 111 + asChild 114 112 className="w-fit" 115 - href="/profile" 116 - startContent={<HiArrowNarrowLeft />} 117 113 > 118 - Serverlist 114 + <Link href="/profile"> 115 + <HiArrowNarrowLeft /> 116 + Serverlist 117 + </Link> 119 118 </Button> 120 119 121 120 <div className="text-lg flex gap-5"> 122 - <Skeleton isLoaded={!isLoading} className="rounded-full h-14 w-14 ring-offset-[var(--background-rgb)] ring-2 ring-offset-2 ring-violet-400/40 shrink-0"> 121 + <Skeleton 122 + isLoading={!guild?.id} 123 + className="rounded-full h-14 w-14 ring-offset-[var(--background-rgb)] ring-2 ring-offset-2 ring-violet-400/40 shrink-0 relative top-1" 124 + > 123 125 <ImageReduceMotion 124 - alt="this server" 126 + alt="this server's icon" 125 127 className="rounded-full" 126 128 url={`https://cdn.discordapp.com/icons/${guild?.id}/${guild?.icon}`} 127 129 size={128} ··· 197 199 <ScreenMessage 198 200 title={error.includes("permssions") 199 201 ? "You cannot access this page.." 200 - : "Something went wrong on this page.." 202 + : undefined 201 203 } 202 204 description={error} 203 205 buttons={<> 204 - <ClientButton 205 - as={Link} 206 - href="/profile" 207 - startContent={<HiViewGridAdd />} 206 + <Button 207 + asChild 208 + variant="secondary" 208 209 > 209 - Go back to Dashboard 210 - </ClientButton> 210 + <Link href="/profile"> 211 + <HiViewGridAdd /> 212 + Go back to Dashboard 213 + </Link> 214 + </Button> 211 215 <SupportButton /> 212 216 </>} 213 - > 214 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 215 - </ScreenMessage> 217 + /> 216 218 : 217 219 (guild && loaded.length === 3) ? children : <></> 218 220 }
+6 -22
app/dashboard/[guildId]/leaderboards/page.tsx
··· 1 1 "use client"; 2 2 3 3 import { ChannelType } from "discord-api-types/v10"; 4 - import Image from "next/image"; 5 4 import { useParams } from "next/navigation"; 6 5 import { HiChartBar, HiViewGridAdd } from "react-icons/hi"; 7 - import { useQuery } from "react-query"; 8 6 9 7 import { type Guild, guildStore } from "@/common/guilds"; 10 8 import ImageUrlInput from "@/components/inputs/image-url-input"; 11 9 import MultiSelectMenu from "@/components/inputs/multi-select-menu"; 12 10 import { ScreenMessage } from "@/components/screen-message"; 13 11 import { Section, SubSection } from "@/components/section"; 14 - import { cacheOptions, getData } from "@/lib/api"; 15 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 12 + import { useApi } from "@/lib/api/hook"; 16 13 import type { ApiV1GuildsModulesLeaderboardGetResponse } from "@/typings"; 17 14 import { createSelectableItems } from "@/utils/create-selectable-items"; 18 15 ··· 27 24 const params = useParams(); 28 25 29 26 const url = `/guilds/${params.guildId}/modules/leaderboard` as const; 27 + const { data, isLoading, error } = useApi<ApiV1GuildsModulesLeaderboardGetResponse>(url); 30 28 31 - const { data, isLoading, error } = useQuery( 32 - url, 33 - () => getData<ApiV1GuildsModulesLeaderboardGetResponse>(url), 34 - { 35 - enabled: !!params.guildId, 36 - ...cacheOptions, 37 - refetchOnMount: true 38 - } 39 - ); 29 + if (isLoading) return <></>; 40 30 41 - if (error || (data && "message" in data)) { 31 + if (!data || error) { 42 32 return ( 43 33 <ScreenMessage 44 34 top="0rem" 45 - title="Something went wrong on this page.." 46 - description={ 47 - (data && "message" in data ? data.message : `${error}`) 48 - || "An unknown error occurred."} 35 + description={error} 49 36 href={`/dashboard/${guild?.id}`} 50 37 button="Go back to overview" 51 38 icon={<HiViewGridAdd />} 52 - > 53 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 54 - </ScreenMessage> 39 + /> 55 40 ); 56 41 } 57 42 58 - if (isLoading || !data) return <></>; 59 43 60 44 return (<> 61 45 <div className="flex flex-col-reverse md:flex-row gap-6">
+20 -16
app/dashboard/[guildId]/leaderboards/permissions.component.tsx
··· 1 - import { Tooltip } from "@nextui-org/react"; 2 1 import { PermissionFlagsBits } from "discord-api-types/v10"; 3 2 import { useMemo } from "react"; 4 3 import { HiExclamation } from "react-icons/hi"; 5 4 6 5 import type { Guild } from "@/common/guilds"; 7 6 import DiscordChannel from "@/components/discord/channel"; 7 + import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; 8 8 import type { ApiV1GuildsChannelsGetResponse } from "@/typings"; 9 9 import { cn } from "@/utils/cn"; 10 10 ··· 46 46 }) { 47 47 return (<> 48 48 <p className="text-sm opacity-75 mb-3"> 49 - Wammellow cannot track acivity in {channels.length} channels as it is missing permissions. 49 + Wammellow cannot track activity in {channels.length} channels as it is missing permissions. 50 50 </p> 51 51 52 52 <div className="flex flex-col gap-1"> 53 - 54 53 {channels.map((c, i) => { 55 54 if (i >= MAX_CHANNELS) return null; 56 55 return ( ··· 63 62 name={c.name} 64 63 isTruncated 65 64 /> 66 - <Tooltip 67 - content={"Missing View Channel"} 68 - placement="right" 69 - className="bg-[#030206]" 70 - closeDelay={0} 71 - > 72 - <span> 65 + <Tooltip> 66 + <TooltipTrigger asChild> 73 67 <HiExclamation className="text-red-500" /> 74 - </span> 68 + </TooltipTrigger> 69 + <TooltipContent> 70 + <p>Missing View Channel</p> 71 + </TooltipContent> 75 72 </Tooltip> 76 73 </div> 77 74 ); 78 75 })} 79 76 </div> 80 77 81 - {channels.length > MAX_CHANNELS && 82 - <p className="text-sm opacity-75 relative bottom-1 mt-2"> 83 - +{channels.length - MAX_CHANNELS} more 84 - </p> 85 - } 78 + {channels.length > MAX_CHANNELS && ( 79 + <Tooltip> 80 + <TooltipTrigger> 81 + <p className="text-sm opacity-75 relative bottom-1 mt-2"> 82 + +{channels.length - MAX_CHANNELS} more 83 + </p> 84 + </TooltipTrigger> 85 + <TooltipContent> 86 + {channels.slice(MAX_CHANNELS).map((channel) => "#" + channel.name).join(", ")} 87 + </TooltipContent> 88 + </Tooltip> 89 + )} 86 90 </>); 87 91 }
+39 -42
app/dashboard/[guildId]/leaderboards/reset.component.tsx
··· 1 - import { Button } from "@nextui-org/react"; 2 1 import { useState } from "react"; 3 2 import { HiTrash, HiUsers } from "react-icons/hi"; 4 3 ··· 6 5 import ImageReduceMotion from "@/components/image-reduce-motion"; 7 6 import Modal from "@/components/modal"; 8 7 import Notice, { NoticeType } from "@/components/notice"; 8 + import { Button } from "@/components/ui/button"; 9 9 import { intl } from "@/utils/numbers"; 10 10 11 11 interface Props { ··· 20 20 21 21 const [modal, setModal] = useState<ModalType>(); 22 22 23 - return ( 24 - <> 25 - <Button 26 - onClick={() => setModal(ModalType.Delete)} 27 - color="danger" 28 - variant="flat" 29 - startContent={<HiTrash />} 30 - > 31 - Reset Leaderboard 32 - </Button> 23 + return (<> 24 + <Button 25 + onClick={() => setModal(ModalType.Delete)} 26 + variant="destructive" 27 + > 28 + <HiTrash /> 29 + Reset Leaderboard 30 + </Button> 31 + 32 + <Modal 33 + title="Reset @everyone's stats" 34 + buttonName="Reset" 35 + variant="destructive" 36 + isOpen={modal === ModalType.Delete} 37 + onClose={() => setModal(undefined)} 38 + onSubmit={() => { 39 + return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guild.id}/top-members`, { 40 + method: "DELETE", 41 + credentials: "include" 42 + }); 43 + }} 44 + > 45 + <Notice 46 + type={NoticeType.Info} 47 + message="Takes a few seconds to apply" 48 + /> 33 49 34 - <Modal 35 - title="Reset @everyone's stats" 36 - buttonName="Reset" 37 - variant="destructive" 38 - isOpen={modal === ModalType.Delete} 39 - onClose={() => setModal(undefined)} 40 - onSubmit={() => { 41 - return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guild.id}/top-members`, { 42 - method: "DELETE", 43 - credentials: "include" 44 - }); 45 - }} 46 - > 47 - <Notice 48 - type={NoticeType.Info} 49 - message="Takes a few seconds to apply" 50 + <div className="flex items-center gap-3"> 51 + <ImageReduceMotion 52 + alt="Guild Icon" 53 + className="rounded-full" 54 + url={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`} 55 + size={56} 50 56 /> 51 57 52 - <div className="flex items-center gap-3"> 53 - <ImageReduceMotion 54 - alt="Guild Icon" 55 - className="rounded-full" 56 - url={`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}`} 57 - size={56} 58 - /> 59 - 60 - <div className="flex flex-col gap-1"> 61 - <div className="text-xl dark:text-neutral-200 text-neutral-800 font-medium">{guild?.name || "Unknown Server"}</div> 62 - <div className="text-sm font-semibold flex items-center gap-1"> <HiUsers /> {intl.format(guild?.memberCount || 0)}</div> 63 - </div> 58 + <div className="flex flex-col gap-1"> 59 + <div className="text-xl dark:text-neutral-200 text-neutral-800 font-medium">{guild?.name || "Unknown Server"}</div> 60 + <div className="text-sm font-semibold flex items-center gap-1"> <HiUsers /> {intl.format(guild?.memberCount || 0)}</div> 64 61 </div> 62 + </div> 65 63 66 - </Modal> 67 - </> 68 - ); 64 + </Modal> 65 + </>); 69 66 }
+30 -31
app/dashboard/[guildId]/leaderboards/widget-button.component.tsx
··· 1 - import { Button } from "@nextui-org/react"; 2 1 import React, { useState } from "react"; 3 2 import { HiEmojiHappy, HiLockClosed } from "react-icons/hi"; 3 + 4 + import { Button } from "@/components/ui/button"; 4 5 5 6 enum State { 6 7 Idle = 0, ··· 54 55 setError(res.status + ": " + res.statusText); 55 56 }; 56 57 57 - return ( 58 - <> 59 - <Button 60 - className="w-fit" 61 - onClick={handle} 62 - color={ 63 - isEnabled 64 - ? "danger" 65 - : "secondary" 66 - } 67 - startContent={ 68 - isEnabled 69 - ? <HiLockClosed /> 70 - : <HiEmojiHappy /> 71 - } 72 - variant="flat" 73 - isLoading={state === State.Loading} 74 - isDisabled={state === State.Ratelimited} 75 - > 76 - {isEnabled 77 - ? "Disable invite widget" 78 - : "Enable invite widget" 79 - } 80 - </Button> 58 + return (<> 59 + <Button 60 + className="w-fit" 61 + onClick={handle} 62 + variant={ 63 + isEnabled 64 + ? "destructive" 65 + : "secondary" 66 + } 67 + icon={ 68 + isEnabled 69 + ? <HiLockClosed /> 70 + : <HiEmojiHappy /> 81 71 82 - {error && 83 - <div className="text-red-500 text-sm mt-1"> 84 - {error} 85 - </div> 72 + } 73 + loading={state === State.Loading} 74 + disabled={state === State.Ratelimited} 75 + > 76 + {isEnabled 77 + ? "Disable invite widget" 78 + : "Enable invite widget" 86 79 } 87 - </> 88 - ); 80 + </Button> 81 + 82 + {error && ( 83 + <div className="text-red-500 text-sm mt-1"> 84 + {error} 85 + </div> 86 + )} 87 + </>); 89 88 }
+24 -26
app/dashboard/[guildId]/leaderboards/widget.component.tsx
··· 1 - import { Skeleton } from "@nextui-org/react"; 2 1 import type { RESTError, RESTGetAPIGuildWidgetJSONResult } from "discord-api-types/v10"; 3 2 import { useState } from "react"; 4 3 import { HiEmojiHappy, HiLockClosed } from "react-icons/hi"; ··· 6 5 7 6 import type { Guild } from "@/common/guilds"; 8 7 import Notice from "@/components/notice"; 8 + import { Skeleton } from "@/components/ui/skeleton"; 9 9 import { cacheOptions } from "@/lib/api"; 10 10 import { cn } from "@/utils/cn"; 11 11 ··· 47 47 if (isLoading || !data) { 48 48 return ( 49 49 <div className="pt-1"> 50 - <Skeleton className="h-4 w-96 mb-2.5 rounded-lg" /> 50 + <Skeleton className="h-5 w-96 mb-2.5 rounded-lg" /> 51 51 <Skeleton className="h-10 w-52 rounded-lg" /> 52 52 </div> 53 53 ); 54 54 } 55 55 56 - return ( 57 - <> 58 - <div className={cn( 59 - "flex items-center gap-1 mb-2 font-medium", 60 - isEnabled 61 - ? "text-violet-400" 62 - : "text-red-500" 63 - )}> 64 - {isEnabled 65 - ? <HiEmojiHappy /> 66 - : <HiLockClosed /> 67 - } 68 - {isEnabled 69 - ? "Invite widget is enabled — people can join this server" 70 - : "Invite widget is disabled — this server is private" 71 - } 72 - </div> 56 + return (<> 57 + <div className={cn( 58 + "flex items-center gap-1 mb-2 font-medium", 59 + isEnabled 60 + ? "text-violet-400" 61 + : "text-red-500" 62 + )}> 63 + {isEnabled 64 + ? <HiEmojiHappy /> 65 + : <HiLockClosed /> 66 + } 67 + {isEnabled 68 + ? "Invite widget is enabled — people can join this server" 69 + : "Invite widget is disabled — this server is private" 70 + } 71 + </div> 73 72 74 - <DiscordWidgetButton 75 - guildId={guild.id} 76 - isEnabled={isEnabled} 77 - setEnabled={setEnabled} 78 - /> 79 - </> 80 - ); 73 + <DiscordWidgetButton 74 + guildId={guild.id} 75 + isEnabled={isEnabled} 76 + setEnabled={setEnabled} 77 + /> 78 + </>); 81 79 }
+16 -16
app/dashboard/[guildId]/notifications/delete.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Button, Tooltip } from "@nextui-org/react"; 3 + 4 4 import { useState } from "react"; 5 5 import { HiTrash } from "react-icons/hi"; 6 6 7 7 import { guildStore } from "@/common/guilds"; 8 8 import Modal from "@/components/modal"; 9 + import { Button } from "@/components/ui/button"; 10 + import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; 9 11 10 12 interface Props { 11 13 id: string | null; ··· 24 26 const [open, setOpen] = useState(false); 25 27 26 28 return (<> 27 - <Tooltip 28 - content="Delete Notification" 29 - closeDelay={0} 30 - > 31 - <Button 32 - isIconOnly 33 - color="danger" 34 - variant="flat" 35 - onClick={() => setOpen(true)} 36 - isDisabled={!id} 37 - > 38 - <span> 29 + <Tooltip> 30 + <TooltipTrigger asChild> 31 + <Button 32 + className="size-9 p-1.5" 33 + variant="destructive" 34 + onClick={() => setOpen(true)} 35 + disabled={!id} 36 + > 39 37 <HiTrash /> 40 - </span> 41 - <span className="sr-only">Delete selected notification</span> 42 - </Button> 38 + </Button> 39 + </TooltipTrigger> 40 + <TooltipContent> 41 + <p>Delete Notification</p> 42 + </TooltipContent> 43 43 </Tooltip> 44 44 45 45 <Modal
+1 -5
app/dashboard/[guildId]/notifications/page.tsx
··· 21 21 import { ScreenMessage } from "@/components/screen-message"; 22 22 import { Button } from "@/components/ui/button"; 23 23 import { cacheOptions } from "@/lib/api"; 24 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 25 24 import { type ApiV1GuildsModulesNotificationsGetResponse, NotificationFlags, NotificationType } from "@/typings"; 26 25 import { BitfieldManager, bitfieldToArray } from "@/utils/bitfields"; 27 26 import { createSelectableItems } from "@/utils/create-selectable-items"; ··· 53 52 return ( 54 53 <ScreenMessage 55 54 top="20vh" 56 - title="Something went wrong on this page.." 57 55 description={error} 58 56 href={`/dashboard/${guild?.id}`} 59 57 button="Go back to overview" 60 58 icon={<HiViewGridAdd />} 61 - > 62 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 63 - </ScreenMessage> 59 + /> 64 60 ); 65 61 } 66 62
+9 -7
app/dashboard/[guildId]/starboard/page.tsx
··· 1 1 "use client"; 2 2 3 - import { Button } from "@nextui-org/react"; 4 3 import Image from "next/image"; 5 4 import Link from "next/link"; 6 5 import { useParams } from "next/navigation"; ··· 16 15 import Switch from "@/components/inputs/switch"; 17 16 import TextInput from "@/components/inputs/text-input"; 18 17 import Notice from "@/components/notice"; 18 + import { Button } from "@/components/ui/button"; 19 19 import { useApi } from "@/lib/api/hook"; 20 20 import { type ApiV1GuildsModulesStarboardGetResponse, StarboardStyle } from "@/typings"; 21 21 import { createSelectableItems } from "@/utils/create-selectable-items"; ··· 45 45 return (<> 46 46 <div className="flex justify-between relative bottom-2 mb-3"> 47 47 <Button 48 - className="ml-auto" 49 - as={Link} 50 - href="/docs/starboard" 51 - target="_blank" 52 - endContent={<HiExternalLink />} 48 + asChild 53 49 size="sm" 54 50 > 55 - Read docs 51 + <Link 52 + href="/docs/starboard" 53 + target="_blank" 54 + > 55 + <HiExternalLink /> 56 + Read docs 57 + </Link> 56 58 </Button> 57 59 </div> 58 60
+2 -10
app/docs/[...pathname]/page.tsx
··· 1 1 import { Code } from "@nextui-org/react"; 2 2 import { readFile } from "fs/promises"; 3 - import Image from "next/image"; 4 3 5 4 import { Faq } from "@/app/(home)/faq.component"; 6 5 import BeautifyMarkdown from "@/components/markdown"; 7 6 import Notice, { NoticeType } from "@/components/notice"; 8 - import { HomeButton, ScreenMessage, SupportButton } from "@/components/screen-message"; 7 + import { ScreenMessage } from "@/components/screen-message"; 9 8 import metadata from "@/public/docs/meta.json"; 10 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 11 9 12 10 interface Props { 13 11 params: Promise<{ pathname: string[]; }>; ··· 26 24 top="6rem" 27 25 title="Sadly, this page can not be found.." 28 26 description="Seems like you got a little lost here? Here's wumpus for now!" 29 - buttons={<> 30 - <HomeButton /> 31 - <SupportButton /> 32 - </>} 33 - > 34 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 35 - </ScreenMessage> 27 + /> 36 28 ); 37 29 } 38 30
+1
app/globals.css
··· 42 42 --border: 260 3% 16%; 43 43 --input: 260 3% 16%; 44 44 --ring: 258 89% 66%; 45 + --separator: 0 0% 100%; 45 46 46 47 --chart-1: 220 70% 50%; 47 48 --chart-2: 160 60% 45%;
+2 -2
app/layout.tsx
··· 1 1 import "./globals.css"; 2 2 3 - import { Divider } from "@nextui-org/react"; 4 3 import type { Metadata, Viewport } from "next"; 5 4 import { Lexend, Noto_Sans_JP, Outfit } from "next/font/google"; 6 5 import { cookies } from "next/headers"; ··· 11 10 12 11 import { Header } from "@/components/header"; 13 12 import { LoginButton } from "@/components/login-button"; 13 + import { Separator } from "@/components/ui/separator"; 14 14 import { cn } from "@/utils/cn"; 15 15 import { getBaseUrl } from "@/utils/urls"; 16 16 ··· 182 182 <span className="text-xl dark:text-neutral-100 text-neutral-900 hidden sm:block">Wamellow</span> 183 183 </Link> 184 184 185 - <Divider 185 + <Separator 186 186 className="h-10 rotate-6 mx-3" 187 187 orientation="vertical" 188 188 />
+4 -17
app/leaderboard/[guildId]/page.tsx
··· 1 1 import { cookies } from "next/headers"; 2 - import Image from "next/image"; 3 2 import { redirect } from "next/navigation"; 4 3 5 - import { AddButton, HomeButton, ScreenMessage, SupportButton } from "@/components/screen-message"; 4 + import { ScreenMessage } from "@/components/screen-message"; 6 5 import { getGuild } from "@/lib/api"; 7 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 8 6 9 7 import { getPagination, getTopMembers } from "./api"; 10 8 import Member from "./member.component"; ··· 47 45 if (error || !guild || !members || !pagination || "message" in pagination) { 48 46 return ( 49 47 <ScreenMessage 50 - top="0rem" 51 - title="Something went wrong on this page.." 48 + top="14rem" 52 49 description={error} 53 - buttons={<> 54 - <HomeButton /> 55 - <SupportButton /> 56 - </>} 57 - > 58 - <Image src={SadWumpusPic} alt="" height={141 * 1.5} width={124 * 1.5} /> 59 - </ScreenMessage> 50 + /> 60 51 ); 61 52 } 62 53 63 54 if (!Array.isArray(members) || !members.length) { 64 55 return ( 65 56 <ScreenMessage 66 - top="0rem" 57 + top="14rem" 67 58 title="No members to see here.." 68 59 description="No members could be found on this page" 69 - buttons={<> 70 - <HomeButton /> 71 - <AddButton /> 72 - </>} 73 60 /> 74 61 ); 75 62 }
+22 -15
app/leaderboard/[guildId]/side.component.tsx
··· 1 1 "use client"; 2 2 3 - import { Accordion, AccordionItem, Button, Code } from "@nextui-org/react"; 3 + import { Accordion, AccordionItem, Code } from "@nextui-org/react"; 4 4 import Link from "next/link"; 5 5 import { useRouter } from "next/navigation"; 6 6 import { useCookies } from "next-client-cookies"; ··· 12 12 import Modal from "@/components/modal"; 13 13 import Notice, { NoticeType } from "@/components/notice"; 14 14 import { Share } from "@/components/share"; 15 + import { Button } from "@/components/ui/button"; 15 16 import type { ApiError, ApiV1GuildsGetResponse, ApiV1GuildsTopmembersPaginationGetResponse } from "@/typings"; 16 17 import { intl } from "@/utils/numbers"; 17 18 import { getCanonicalUrl } from "@/utils/urls"; ··· 41 42 42 43 {guild && "inviteUrl" in guild && guild.inviteUrl && 43 44 <Button 44 - as={Link} 45 - className="w-full !justify-start" 46 - color="secondary" 47 - href={guild.inviteUrl} 48 - target="_blank" 49 - startContent={<BsDiscord />} 45 + asChild 46 + className="justify-start" 47 + variant="secondary" 50 48 > 51 - Join {guild.name} 49 + <Link 50 + href={guild.inviteUrl} 51 + target="_blank" 52 + > 53 + <BsDiscord /> 54 + Join {guild.name} 55 + </Link> 52 56 </Button> 53 57 } 54 58 ··· 68 72 classNames={{ content: "mb-2" }} 69 73 > 70 74 <Button 71 - className="w-full !justify-start" 75 + className="w-full justify-start" 72 76 onClick={() => setModal(true)} 73 - startContent={<HiTrash />} 74 77 > 78 + <HiTrash /> 75 79 Reset member stats 76 80 </Button> 77 81 <Button 78 - as={Link} 79 - className="w-full !justify-start mt-2" 80 - href={getCanonicalUrl("dashboard", guild.id as string)} 81 - startContent={<HiViewGridAdd />} 82 + asChild 83 + className="w-full justify-start mt-2" 82 84 > 83 - Dashboard 85 + <Link 86 + href={getCanonicalUrl("dashboard", guild.id as string)} 87 + > 88 + <HiViewGridAdd /> 89 + Dashboard 90 + </Link> 84 91 </Button> 85 92 </AccordionItem> 86 93 :
+2 -11
app/not-found.tsx
··· 1 - import Image from "next/image"; 2 - 3 - import { AddButton, HomeButton, ScreenMessage } from "@/components/screen-message"; 4 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 1 + import { ScreenMessage } from "@/components/screen-message"; 5 2 6 3 export default function NotFound() { 7 4 return ( 8 5 <ScreenMessage 9 6 title="Sadly, this page can not be found.." 10 7 description="Seems like you got a little lost here? Here's wumpus for now!" 11 - buttons={<> 12 - <HomeButton /> 13 - <AddButton /> 14 - </>} 15 - > 16 - <Image src={SadWumpusPic} alt="" height={141 * 1.5} width={124 * 1.5} /> 17 - </ScreenMessage> 8 + /> 18 9 ); 19 10 }
+2 -2
app/profile/billing/page.tsx
··· 1 1 "use client"; 2 2 3 3 import { Turnstile, type TurnstileInstance } from "@marsidev/react-turnstile"; 4 - import { Link } from "@nextui-org/react"; 4 + import Link from "next/link"; 5 5 import { useRef, useState } from "react"; 6 6 import { GrAmex } from "react-icons/gr"; 7 7 import { HiCreditCard, HiLightningBolt } from "react-icons/hi"; ··· 115 115 className="ml-auto" 116 116 variant="link" 117 117 > 118 - <Link href={data?.portalUrl}> 118 + <Link href={data!.portalUrl}> 119 119 Change 120 120 </Link> 121 121 </Button>
+2 -14
app/profile/connections/page.tsx
··· 10 10 import DumbTextInput from "@/components/inputs/dumb-text-input"; 11 11 import Modal from "@/components/modal"; 12 12 import Notice, { NoticeType } from "@/components/notice"; 13 - import { HomeButton, ScreenMessage, SupportButton } from "@/components/screen-message"; 13 + import { ScreenMessage } from "@/components/screen-message"; 14 14 import { Button } from "@/components/ui/button"; 15 15 import { useApi } from "@/lib/api/hook"; 16 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 17 16 import { type ApiV1UsersMeConnectionsGetResponse, ConnectionType } from "@/typings"; 18 17 import { cn } from "@/utils/cn"; 19 18 ··· 27 26 const { isLoading, data, error } = useApi<ApiV1UsersMeConnectionsGetResponse[]>(url); 28 27 29 28 if (error) { 30 - return ( 31 - <ScreenMessage 32 - title="Something went wrong on this page.." 33 - description={error} 34 - buttons={<> 35 - <HomeButton /> 36 - <SupportButton /> 37 - </>} 38 - > 39 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 40 - </ScreenMessage> 41 - ); 29 + return <ScreenMessage description={error} />; 42 30 } 43 31 44 32 if (isLoading || !data) return <></>;
+16 -24
app/profile/layout.tsx
··· 1 1 "use client"; 2 2 3 - import { Chip } from "@nextui-org/react"; 4 - import Image from "next/image"; 5 3 import Link from "next/link"; 6 4 import { redirect } from "next/navigation"; 7 5 import { useCookies } from "next-client-cookies"; ··· 14 12 import ImageReduceMotion from "@/components/image-reduce-motion"; 15 13 import { ListTab } from "@/components/list"; 16 14 import { MetricCard, Metrics } from "@/components/metric-card"; 17 - import { HomeButton, ScreenMessage, SupportButton } from "@/components/screen-message"; 15 + import { ScreenMessage } from "@/components/screen-message"; 16 + import { Badge } from "@/components/ui/badge"; 18 17 import { Skeleton } from "@/components/ui/skeleton"; 19 18 import { cacheOptions, getData } from "@/lib/api"; 20 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 21 19 import type { ApiV1UsersMeGetResponse } from "@/typings"; 22 20 23 21 export default function RootLayout({ ··· 50 48 if (error || (data && "message" in data)) { 51 49 return ( 52 50 <ScreenMessage 53 - title="Something went wrong on this page.." 54 51 description={ 55 52 (data && "message" in data ? data.message : `${error}`) 56 - || "An unknown error occurred."} 57 - buttons={<> 58 - <HomeButton /> 59 - <SupportButton /> 60 - </>} 61 - > 62 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 63 - </ScreenMessage> 53 + || "An unknown error occurred." 54 + } 55 + /> 64 56 ); 65 57 } 66 58 ··· 87 79 {!user?.id ? 88 80 <div className="flex flex-col mt-2"> 89 81 <Skeleton className="rounded-xl w-32 h-5 mb-2" /> 90 - <Skeleton className="rounded-md w-24 h-7" /> 82 + <Skeleton className="rounded-md w-[90px] h-7" /> 91 83 </div> 92 84 : 93 85 <div className="flex flex-col gap-1"> 94 86 <div className="text-2xl dark:text-neutral-200 text-neutral-800 font-medium"> 95 87 {user.globalName || user.username} 96 88 </div> 97 - <Chip 98 - as={Link} 89 + <Link 90 + className="font-bold" 99 91 href="/vote" 100 92 target="_blank" 101 - color="secondary" 102 - startContent={<HiFire className="ml-1" />} 103 - variant="flat" 104 - radius="sm" 105 93 > 106 - <span className="font-bold uppercase"> 107 - {user.extended?.voteCount} votes 108 - </span> 109 - </Chip> 94 + <Badge 95 + className="h-7 font-semibold" 96 + variant="flat" 97 + > 98 + <HiFire/> 99 + {user.extended?.voteCount} VOTE{user.extended?.voteCount === 1 ? "" : "S"} 100 + </Badge> 101 + </Link> 110 102 </div> 111 103 } 112 104 </div>
+51 -43
app/profile/page.tsx
··· 1 1 "use client"; 2 2 3 - import { Button } from "@nextui-org/react"; 4 3 import { motion } from "framer-motion"; 5 - import Image from "next/image"; 6 4 import Link from "next/link"; 7 5 import { useSearchParams } from "next/navigation"; 8 6 import { useCookies } from "next-client-cookies"; ··· 11 9 12 10 import ImageReduceMotion from "@/components/image-reduce-motion"; 13 11 import DumbTextInput from "@/components/inputs/dumb-text-input"; 14 - import { HomeButton, ScreenMessage, SupportButton } from "@/components/screen-message"; 12 + import { ScreenMessage } from "@/components/screen-message"; 13 + import { Button } from "@/components/ui/button"; 15 14 import { useApi } from "@/lib/api/hook"; 16 - import SadWumpusPic from "@/public/sad-wumpus.gif"; 17 15 import type { ApiV1UsersMeGuildsGetResponse } from "@/typings"; 18 16 import { cn } from "@/utils/cn"; 19 17 ··· 48 46 return ( 49 47 <ScreenMessage 50 48 top="10rem" 51 - title="Something went wrong on this page.." 52 49 description={`${error}`} 53 - buttons={<> 54 - <HomeButton /> 55 - <SupportButton /> 56 - </>} 57 - > 58 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 59 - </ScreenMessage> 50 + /> 60 51 ); 61 52 } 62 53 ··· 64 55 65 56 return (<div className="flex flex-col w-full"> 66 57 67 - <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4"> 58 + <div className="flex flex-col md:flex-row md:items-center md:justify-between gap-2"> 68 59 <div className="relative top-2 w-full"> 69 60 <DumbTextInput 70 61 value={search} ··· 76 67 77 68 <div className="flex gap-2 md:mt-0"> 78 69 <Button 79 - as={Link} 70 + asChild 80 71 className="w-1/2 md:w-min" 81 - href="/login?invite=true" 82 - prefetch={false} 83 - startContent={<HiUserAdd />} 84 72 > 85 - Add to Server 73 + <Link 74 + href="/login?invite=true" 75 + prefetch={false} 76 + > 77 + <HiUserAdd /> 78 + Add to Server 79 + </Link> 86 80 </Button> 87 81 <Button 88 - as={Link} 89 - className="button-primary w-1/2 md:w-min" 90 - href="/login" 91 - prefetch={false} 92 - startContent={<HiRefresh />} 82 + asChild 83 + className="w-1/2 md:w-min" 84 + variant="secondary" 93 85 > 94 - Reload 86 + <Link 87 + href="/login" 88 + prefetch={false} 89 + > 90 + <HiRefresh /> 91 + Reload 92 + </Link> 95 93 </Button> 96 94 </div> 97 95 </div> ··· 121 119 <ScreenMessage 122 120 title="There are too many servers.." 123 121 description={`To save some performance, use the search to find a guild. Showing ${MAX_GUILDS} out of ~${guilds.length < 1000 ? length : Math.round(length / 1000) * 1000}.`} 124 - > 125 - <Image src={SadWumpusPic} alt="" height={141} width={124} /> 126 - </ScreenMessage> 122 + /> 127 123 } 128 124 129 125 </div>); ··· 191 187 function InviteButton({ guildId }: { guildId: string; }) { 192 188 return ( 193 189 <Button 194 - as={Link} 195 - className="default dark:bg-neutral-500/40 hover:dark:bg-neutral-500/20 bg-neutral-400/40 hover:bg-neutral-400/20 text-sm h-9" 196 - href={`/login?invite=true&guild_id=${guildId}`} 197 - prefetch={false} 198 - startContent={<HiUserAdd />} 190 + asChild 191 + className="h-8" 199 192 > 200 - Add Wamellow 193 + <Link 194 + href={`/login?invite=true&guild_id=${guildId}`} 195 + prefetch={false} 196 + > 197 + <HiUserAdd /> 198 + Add Wamellow 199 + </Link> 201 200 </Button> 202 201 ); 203 202 } 204 203 205 204 function ManageButton({ guildId }: { guildId: string; }) { 206 205 const searchParams = useSearchParams(); 206 + const to = searchParams.get("to"); 207 207 208 208 return ( 209 209 <Button 210 - as={Link} 211 - className="default dark:bg-neutral-500/40 hover:dark:bg-neutral-500/20 bg-neutral-400/40 hover:bg-neutral-400/20 text-sm h-9" 212 - href={`/dashboard/${guildId}${searchParams.get("to") ? `/${searchParams.get("to")}` : ""}`} 213 - startContent={<HiViewGridAdd />} 210 + asChild 211 + className="h-8" 214 212 > 215 - Manage 213 + <Link 214 + href={`/dashboard/${guildId}${to ? `/${to}` : ""}`} 215 + prefetch={false} 216 + > 217 + <HiViewGridAdd /> 218 + Manage 219 + </Link> 216 220 </Button> 217 221 ); 218 222 } ··· 220 224 function LeaderboardButton({ guildId }: { guildId: string; }) { 221 225 return ( 222 226 <Button 223 - as={Link} 224 - className="default dark:bg-neutral-500/40 hover:dark:bg-neutral-500/20 bg-neutral-400/40 hover:bg-neutral-400/20 text-sm h-9 md:opacity-0 group-hover/card:opacity-100" 225 - href={`/leaderboard/${guildId}`} 226 - startContent={<HiChartBar />} 227 + asChild 228 + className="h-8" 227 229 > 228 - Leaderboard 230 + <Link 231 + href={`/leaderboard/${guildId}`} 232 + prefetch={false} 233 + > 234 + <HiChartBar /> 235 + Leaderboard 236 + </Link> 229 237 </Button> 230 238 ); 231 239 }
+8 -8
app/profile/rank/card-style.component.tsx
··· 1 - import { Button } from "@nextui-org/react"; 2 1 import type { ApiError } from "next/dist/server/api-utils"; 3 2 import Image from "next/image"; 4 3 import { type ChangeEvent, useRef, useState } from "react"; ··· 7 6 import { type User, userStore } from "@/common/user"; 8 7 import Box from "@/components/box"; 9 8 import { Shiggy } from "@/components/shiggy"; 9 + import { Button } from "@/components/ui/button"; 10 10 import type { ApiV1UsersMeRankEmojiDeleteResponse, ApiV1UsersMeRankEmojiPutResponse } from "@/typings"; 11 11 import { cn } from "@/utils/cn"; 12 12 import { deepMerge } from "@/utils/deepMerge"; ··· 123 123 <div className="flex flex-col"> 124 124 <Button 125 125 className={cn(state === State.Loading && "shake")} 126 - color="secondary" 127 - startContent={state !== State.Loading && <HiUpload />} 126 + variant="secondary" 128 127 onClick={() => ref.current?.click()} 129 - isLoading={state === State.Loading} 128 + icon={<HiUpload />} 129 + loading={state === State.Loading} 130 130 > 131 131 {state === State.Success 132 132 ? "Looking good!" 133 133 : "Upload Emoji" 134 134 } 135 135 </Button> 136 - {user?.extended?.rank?.emoji && 136 + {user?.extended?.rank?.emoji && ( 137 137 <button 138 138 onClick={() => remove()} 139 139 className="text-red-400 hover:underline md:text-sm w-fit mt-1" 140 140 > 141 141 Remove 142 142 </button> 143 - } 143 + ) } 144 144 </div> 145 145 </div> 146 146 ··· 157 157 </Box> 158 158 159 159 <div className="flex"> 160 - {error && 160 + {error && ( 161 161 <div className="ml-auto text-red-500 text-sm"> 162 162 {error} 163 163 </div> 164 - } 164 + )} 165 165 </div> 166 166 </div> 167 167 );
+11 -7
components/ad.tsx
··· 1 1 "use client"; 2 2 3 - import { Button } from "@nextui-org/react"; 4 3 import { Poppins } from "next/font/google"; 5 4 import Link from "next/link"; 6 5 import type { FunctionComponent } from "react"; 7 6 import { HiArrowNarrowRight } from "react-icons/hi"; 8 7 9 8 import { cn } from "@/utils/cn"; 9 + 10 + import { Button } from "./ui/button"; 10 11 11 12 const poppins = Poppins({ subsets: ["latin"], weight: "700" }); 12 13 ··· 48 49 </div> 49 50 50 51 <Button 51 - as={Link} 52 + asChild 52 53 className="mt-3 font-medium" 53 - href={url} 54 - target="_blank" 55 - prefetch={false} 56 - endContent={<HiArrowNarrowRight />} 57 54 > 58 - {button} 55 + <Link 56 + href={url} 57 + target="_blank" 58 + prefetch={false} 59 + > 60 + {button} 61 + <HiArrowNarrowRight /> 62 + </Link> 59 63 </Button> 60 64 </div> 61 65 );
+6 -9
components/embed-creator.tsx
··· 1 - import { Button } from "@nextui-org/react"; 2 1 import React, { useState } from "react"; 3 2 import { BiMoon, BiSun } from "react-icons/bi"; 4 3 import { FaFloppyDisk } from "react-icons/fa6"; ··· 12 11 import DiscordMessageEmbed from "./discord/message-embed"; 13 12 import DumbColorInput from "./inputs/dumb-color-input"; 14 13 import DumbTextInput from "./inputs/dumb-text-input"; 14 + import { Button } from "./ui/button"; 15 15 16 16 enum State { 17 17 Idle = 0, ··· 194 194 </div> 195 195 196 196 <Button 197 - className={cn( 198 - "mt-1 w-full", 199 - disabled && "cursor-not-allowed opacity-50" 200 - )} 201 - color="secondary" 202 - isDisabled={disabled} 203 - isLoading={state === State.Loading} 197 + className="mt-1 w-full" 204 198 onClick={() => save()} 205 - startContent={state !== State.Loading && <FaFloppyDisk />} 199 + icon={<FaFloppyDisk />} 200 + disabled={disabled} 201 + loading={state === State.Loading} 202 + variant="secondary" 206 203 > 207 204 Save Changes 208 205 </Button>
+6 -10
components/header.tsx
··· 1 1 "use client"; 2 2 3 - import { Switch } from "@nextui-org/react"; 4 3 import { AnimatePresence, motion, MotionConfig } from "framer-motion"; 5 4 import Link from "next/link"; 6 5 import { useRouter } from "next/navigation"; ··· 17 16 import ImageReduceMotion from "./image-reduce-motion"; 18 17 import { Button } from "./ui/button"; 19 18 import { Skeleton } from "./ui/skeleton"; 19 + import { Switch } from "./ui/switch"; 20 20 21 21 enum State { 22 22 Idle = 0, ··· 221 221 <Switch 222 222 key={"headerButton-" + button.name} 223 223 className="ml-auto" 224 - isSelected={button.value} 225 - onValueChange={button.onChange} 226 - aria-label={button.name} 227 - color="secondary" 228 - size="sm" 224 + checked={button.value} 225 + onChange={button.onChange} 229 226 /> 230 227 </div> 231 228 ); ··· 240 237 } 241 238 242 239 <MotionConfig 243 - transition={reduceMotions ? 244 - { duration: 0 } 245 - : 246 - { type: "spring", bounce: 0.4, duration: menu ? 0.7 : 0.4 } 240 + transition={reduceMotions 241 + ? { duration: 0 } 242 + : { type: "spring", bounce: 0.4, duration: menu ? 0.7 : 0.4 } 247 243 } 248 244 > 249 245 <AnimatePresence initial={false}>
+9
components/loading-circle.tsx
··· 1 + import { LoaderCircleIcon } from "lucide-react"; 2 + 3 + import { cn } from "@/utils/cn"; 4 + 5 + export function LoadingCircle({ className = "" }: { className?: string; }) { 6 + return ( 7 + <LoaderCircleIcon className={cn("animate-spin size-8", className)} strokeWidth={2.5} /> 8 + ); 9 + }
+63 -64
components/screen-message.tsx
··· 1 - import type { Button } from "@nextui-org/react"; 1 + import Image from "next/image"; 2 2 import Link from "next/link"; 3 3 import { BsDiscord } from "react-icons/bs"; 4 4 import { HiHome } from "react-icons/hi"; 5 5 6 + import SadWumpusPic from "@/public/sad-wumpus.gif"; 6 7 import { cn } from "@/utils/cn"; 7 8 8 - import { ClientButton } from "./client"; 9 + import { Button } from "./ui/button"; 9 10 10 - type Props = { 11 - title: string; 12 - description: string; 11 + interface Props { 12 + title?: string; 13 + description?: string; 13 14 top?: string; 14 15 15 - /** 16 - * @deprecated 17 - */ 18 16 icon?: React.ReactNode; 19 - /** 20 - * @deprecated 21 - */ 22 17 button?: string; 18 + href?: string; 23 19 24 20 buttons?: React.ReactNode; 25 21 children?: React.ReactNode; 26 - } & React.ComponentProps<typeof Button>; 22 + } 27 23 28 24 export function ScreenMessage({ 29 25 title, 30 26 description, 31 - top = "16vh", 27 + top = "30vh", 32 28 33 29 icon, 34 30 button, 31 + href, 35 32 36 - buttons, 37 - children, 38 - ...props 33 + buttons = (<> 34 + <HomeButton /> 35 + <SupportButton /> 36 + </>), 37 + children = <Image src={SadWumpusPic} alt="" height={141 * 1.5} width={124 * 1.5} /> 39 38 }: Props) { 40 39 41 40 return ( 42 - <div className="w-full h-full flex flex-col items-center justify-center md:my-0 my-40"> 43 - 44 - <div style={{ marginTop: top }} /> 41 + <div 42 + className="w-full h-full flex justify-center gap-8" 43 + style={{ marginTop: top }} 44 + > 45 45 46 - {children && 46 + {children && ( 47 47 <div className={cn("relative bottom-8", buttons ? "ml-8" : "ml-4")}> 48 48 {children} 49 49 </div> 50 - } 50 + )} 51 51 52 52 <div> 53 - 54 - <div className="mb-8 flex flex-col items-center text-center"> 55 - <span className="text-4xl dark:text-neutral-100 text-neutral-900 font-semibold">{title}</span> <br /> 56 - <span className="text-lg dark:text-neutral-400 text-neutral-600 font-semibold max-w-xl">{description}</span> 53 + <div className="mb-8"> 54 + <h2 className="text-4xl dark:text-neutral-100 text-neutral-900 font-semibold">{title || "Something strange happened..."}</h2> 55 + <h3 className="text-lg dark:text-neutral-400 text-neutral-600 font-semibold max-w-xl mt-1">{description || "Some error has occurred, but no worries, we're fixing it!"}</h3> 57 56 </div> 58 57 59 - {(button && props.href) && 58 + {button && href && 60 59 <div className="w-full flex flex-col items-center"> 61 - <ClientButton 62 - as={Link} 63 - {...props} 64 - className={cn("px-20", props.className)} 65 - startContent={icon} 60 + <Button 61 + asChild 62 + variant="secondary" 66 63 > 67 - {button} 68 - </ClientButton> 64 + <Link href={href}> 65 + {icon} 66 + {button} 67 + </Link> 68 + </Button> 69 69 </div> 70 70 } 71 71 72 - {buttons && 73 - <div className="w-full flex flex-col items-center"> 74 - <div className="flex flex-wrap gap-2"> 75 - {buttons} 76 - </div> 72 + {buttons && ( 73 + <div className="flex flex-wrap gap-2"> 74 + {buttons} 77 75 </div> 78 - } 79 - 76 + )} 80 77 </div> 81 - 82 78 </div> 83 79 ); 84 80 } 85 81 86 82 export function HomeButton() { 87 83 return ( 88 - <ClientButton 89 - as={Link} 90 - href="/" 91 - startContent={<HiHome />} 84 + <Button 85 + asChild 86 + variant="secondary" 92 87 > 93 - Go back to Home 94 - </ClientButton> 88 + <Link href="/"> 89 + <HiHome /> 90 + Go back to Home 91 + </Link> 92 + </Button> 95 93 ); 96 94 } 97 95 98 96 export function AddButton() { 99 97 return ( 100 - <ClientButton 101 - as={Link} 102 - className="button-primary" 103 - href="/login?invite=true" 104 - prefetch={false} 105 - startContent={<BsDiscord />} 106 - > 107 - Add Wamellow to your server 108 - </ClientButton> 98 + <Button asChild> 99 + <Link 100 + href="/login?invite=true" 101 + prefetch={false} 102 + > 103 + <BsDiscord /> 104 + Add Wamellow to your server 105 + </Link> 106 + </Button> 109 107 ); 110 108 } 111 109 112 110 export function SupportButton() { 113 111 return ( 114 - <ClientButton 115 - as={Link} 116 - className="button-primary" 117 - href="/support" 118 - startContent={<BsDiscord />} 119 - > 120 - Join support server 121 - </ClientButton> 112 + <Button asChild> 113 + <Link 114 + href="/support" 115 + prefetch={false} 116 + > 117 + <BsDiscord /> 118 + Join support server 119 + </Link> 120 + </Button> 122 121 ); 123 122 }
+18 -20
components/section.tsx
··· 1 - import { Divider } from "@nextui-org/react"; 2 - 3 1 import { cn } from "@/utils/cn"; 2 + 3 + import { Separator } from "./ui/separator"; 4 4 5 5 export function Section({ 6 6 title, ··· 14 14 showDivider?: boolean; 15 15 children?: React.ReactNode; 16 16 } & React.HTMLAttributes<HTMLDivElement>) { 17 - return ( 18 - <> 19 - {showDivider && <Divider className="mt-12 mb-4" />} 17 + return (<> 18 + {showDivider && <Separator className="mt-12 mb-4" />} 20 19 21 - <div 22 - className={cn("mb-3", className)} 23 - {...props} 24 - > 25 - <h3 className="text-xl text-neutral-200">{title}</h3> 26 - {children && 27 - <p className="dark:text-neutral-500 text-neutral-400"> 28 - {children} 29 - </p> 30 - } 31 - </div> 32 - </> 33 - ); 20 + <div 21 + className={cn("mb-3", className)} 22 + {...props} 23 + > 24 + <h3 className="text-xl text-neutral-200">{title}</h3> 25 + {children && ( 26 + <p className="dark:text-neutral-500 text-neutral-400"> 27 + {children} 28 + </p> 29 + )} 30 + </div> 31 + </>); 34 32 } 35 33 36 34 export function SubSection({ ··· 47 45 <div {...props}> 48 46 <h3 className="text-medium font-medium text-neutral-300 mt-5">{title}</h3> 49 47 <div className="dark:text-neutral-500 text-neutral-400 mb-3"> 50 - {description && 48 + {description && ( 51 49 <div className="mb-3"> 52 50 {description} 53 51 </div> 54 - } 52 + )} 55 53 56 54 {children} 57 55 </div>
+1 -1
components/ui/badge.tsx
··· 4 4 import { cn } from "@/utils/cn"; 5 5 6 6 const badgeVariants = cva( 7 - "w-fit inline-flex items-center rounded-full border font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 [&>svg]:relative [&>svg]:right-1", 7 + "w-fit inline-flex items-center border font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 [&>svg]:relative [&>svg]:right-1", 8 8 { 9 9 variants: { 10 10 variant: {
+35 -2
components/ui/button.tsx
··· 5 5 6 6 import { cn } from "@/utils/cn"; 7 7 8 + import { LoadingCircle } from "../loading-circle"; 9 + 8 10 const buttonVariants = cva( 9 11 "inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&>svg]:size-5", 10 12 { ··· 38 40 extends React.ButtonHTMLAttributes<HTMLButtonElement>, 39 41 VariantProps<typeof buttonVariants> { 40 42 asChild?: boolean; 43 + loading?: boolean; 44 + icon?: React.ReactNode; 41 45 } 42 46 43 47 const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 44 - ({ className, variant, size, asChild = false, ...props }, ref) => { 48 + ({ className, variant, size, asChild = false, children, disabled = false, loading = false, icon, ...props }, ref) => { 45 49 const Comp = asChild ? Slot : "button"; 46 50 51 + const content = ( 52 + <> 53 + {loading 54 + ? <LoadingCircle /> 55 + : icon 56 + } 57 + {children} 58 + </> 59 + ); 60 + 61 + if (asChild) { 62 + return ( 63 + <Comp 64 + className={cn(buttonVariants({ variant, size, className }))} 65 + data-disabled={disabled || loading} 66 + ref={ref} 67 + {...props} 68 + > 69 + {icon 70 + ? React.cloneElement(children as React.ReactElement, {}, content) 71 + : children 72 + } 73 + </Comp> 74 + ); 75 + } 76 + 47 77 return ( 48 78 <Comp 49 79 className={cn(buttonVariants({ variant, size, className }))} 80 + disabled={disabled || loading} 50 81 ref={ref} 51 82 {...props} 52 - /> 83 + > 84 + {content} 85 + </Comp> 53 86 ); 54 87 } 55 88 );
+1 -1
components/ui/separator.tsx
··· 18 18 decorative={decorative} 19 19 orientation={orientation} 20 20 className={cn( 21 - "shrink-0 bg-border", 21 + "shrink-0 bg-separator/15", 22 22 orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]", 23 23 className 24 24 )}
+8 -2
components/ui/tooltip.tsx
··· 7 7 8 8 const TooltipProvider = TooltipPrimitive.Provider; 9 9 10 - const Tooltip = TooltipPrimitive.Root; 10 + const Tooltip = ({ delayDuration, ...props }: React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>) => ( 11 + <TooltipPrimitive.Root 12 + delayDuration={delayDuration ?? 0} 13 + {...props} 14 + /> 15 + ); 16 + Tooltip.displayName = TooltipPrimitive.Root.displayName; 11 17 12 18 const TooltipTrigger = TooltipPrimitive.Trigger; 13 19 ··· 19 25 ref={ref} 20 26 sideOffset={sideOffset} 21 27 className={cn( 22 - "z-50 overflow-hidden rounded-md bg-popover/30 px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", 28 + "z-50 overflow-hidden rounded-md bg-popover/30 backdrop-blur-md px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 max-w-md data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", 23 29 className 24 30 )} 25 31 {...props}
+1 -1
lib/api/hook.ts
··· 33 33 } 34 34 35 35 return { 36 - data, 36 + data: data as T, 37 37 isLoading, 38 38 error: error ? `${error}` : undefined, 39 39 edit
+1
tailwind.config.js
··· 62 62 border: 'hsl(var(--border))', 63 63 input: 'hsl(var(--input))', 64 64 ring: 'hsl(var(--ring))', 65 + separator: 'hsl(var(--separator))', 65 66 chart: { 66 67 '1': 'hsl(var(--chart-1))', 67 68 '2': 'hsl(var(--chart-2))',