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