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

Configure Feed

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

add tags (custom commands) editor

Luna 746ba160 7f1d5019

+447 -18
-16
app/dashboard/[guildId]/actions/page.tsx
··· 1 - "use client"; 2 - 3 - export default function Home() { 4 - 5 - // if (undefined) return ( 6 - // <div> 7 - // {error && <ErrorBanner message={error} />} 8 - // </div> 9 - // ); 10 - 11 - return ( 12 - <div> 13 - idk how to design this 14 - </div> 15 - ); 16 - }
+83
app/dashboard/[guildId]/custom-commands/create.component.tsx
··· 1 + "use client"; 2 + import { Button, Chip } from "@nextui-org/react"; 3 + import { Dispatch, SetStateAction, useState } from "react"; 4 + import { HiPencil } from "react-icons/hi"; 5 + 6 + import DumbTextInput from "@/components/inputs/Dumb_TextInput"; 7 + import Modal from "@/components/modal"; 8 + import { ApiV1GuildsModulesTagsGetResponse } from "@/typings"; 9 + 10 + export enum Style { 11 + Compact = 1, 12 + Big = 2 13 + } 14 + 15 + interface Props { 16 + guildId: string; 17 + style: Style; 18 + 19 + setTags: Dispatch<SetStateAction<ApiV1GuildsModulesTagsGetResponse[]>>; 20 + setTagId: (id: string) => void; 21 + } 22 + 23 + export default function CreateTag({ guildId, style, setTags, setTagId }: Props) { 24 + 25 + const [open, setOpen] = useState(false); 26 + const [name, setName] = useState(""); 27 + 28 + return ( 29 + <> 30 + {style === Style.Compact 31 + ? 32 + <Chip 33 + as={Button} 34 + className="default" 35 + variant="faded" 36 + onClick={() => setOpen(true)} 37 + startContent={<HiPencil className="relative left-1 ml-1" />} 38 + > 39 + Create 40 + </Chip> 41 + : 42 + <Button 43 + color="secondary" 44 + onClick={() => setOpen(true)} 45 + startContent={<HiPencil />} 46 + > 47 + Create new tag 48 + </Button> 49 + } 50 + 51 + <Modal<ApiV1GuildsModulesTagsGetResponse> 52 + title="Create new tag" 53 + show={open} 54 + onClose={() => setOpen(false)} 55 + onSubmit={() => { 56 + return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/modules/tags`, { 57 + method: "POST", 58 + headers: { 59 + "Content-Type": "application/json", 60 + authorization: localStorage.getItem("token") as string 61 + }, 62 + body: JSON.stringify({ name: name || "new-tag" }) 63 + }); 64 + }} 65 + onSuccess={(tag) => { 66 + setTags((tags) => { 67 + return [tag, ...tags]; 68 + }); 69 + setTagId(tag.tagId); 70 + }} 71 + > 72 + <DumbTextInput 73 + name="Name" 74 + placeholder="new-tag" 75 + value={name} 76 + setValue={setName} 77 + max={32} 78 + /> 79 + </Modal> 80 + </> 81 + ); 82 + 83 + }
+62
app/dashboard/[guildId]/custom-commands/delete.component.tsx
··· 1 + "use client"; 2 + import { Button, Tooltip } from "@nextui-org/react"; 3 + import { Dispatch, SetStateAction, useState } from "react"; 4 + import { HiTrash } from "react-icons/hi"; 5 + 6 + import Modal from "@/components/modal"; 7 + import { ApiV1GuildsModulesTagsGetResponse } from "@/typings"; 8 + 9 + interface Props { 10 + guildId: string; 11 + tagId: string | null; 12 + name?: string; 13 + 14 + setTags: Dispatch<SetStateAction<ApiV1GuildsModulesTagsGetResponse[]>>; 15 + } 16 + 17 + export default function DeleteTag({ guildId, tagId, name, setTags }: Props) { 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={!tagId} 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="danger" 38 + title={"Delete tag: " + name} 39 + show={open} 40 + onClose={() => setOpen(false)} 41 + onSubmit={() => { 42 + return fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${guildId}/modules/tags/${tagId}`, { 43 + method: "DELETE", 44 + headers: { 45 + "Content-Type": "application/json", 46 + authorization: localStorage.getItem("token") as string 47 + } 48 + }); 49 + }} 50 + onSuccess={() => { 51 + setTags((tags) => { 52 + const newtags = tags?.filter((t) => t.tagId !== tagId) || []; 53 + return newtags; 54 + }); 55 + }} 56 + > 57 + Are you sure you want to delete the {'"'}{name}{'"'} tag? It will be gone forever, probably, who knows. 58 + </Modal> 59 + </> 60 + ); 61 + 62 + }
+229
app/dashboard/[guildId]/custom-commands/page.tsx
··· 1 + "use client"; 2 + 3 + import { Button, Chip, Tooltip } from "@nextui-org/react"; 4 + import { useParams, usePathname, useRouter, useSearchParams } from "next/navigation"; 5 + import { useCallback, useEffect, useState } from "react"; 6 + import { HiViewGridAdd } from "react-icons/hi"; 7 + 8 + import { guildStore } from "@/common/guilds"; 9 + import { StatsBar } from "@/components/counter"; 10 + import MessageCreatorEmbed from "@/components/embed-creator"; 11 + import SelectInput from "@/components/inputs/SelectMenu"; 12 + import TextInput from "@/components/inputs/TextInput"; 13 + import { ScreenMessage } from "@/components/screen-message"; 14 + import { Permissions } from "@/lib/discord"; 15 + import { ApiV1GuildsModulesTagsGetResponse, RouteErrorResponse } from "@/typings"; 16 + 17 + import CreateTag, { Style } from "./create.component"; 18 + import DeleteTag from "./delete.component"; 19 + 20 + export default function Home() { 21 + const guild = guildStore((g) => g); 22 + 23 + const [error, setError] = useState<string>(); 24 + const [tags, setTags] = useState<ApiV1GuildsModulesTagsGetResponse[]>([]); 25 + 26 + const pathname = usePathname(); 27 + const search = useSearchParams(); 28 + const router = useRouter(); 29 + const params = useParams(); 30 + 31 + const tagId = search.get("id"); 32 + const tag = tags.find((t) => t.tagId === tagId); 33 + 34 + const createQueryString = useCallback((name: string, value: string) => { 35 + const params = new URLSearchParams(search); 36 + params.set(name, value); 37 + 38 + return params.toString(); 39 + }, [search]); 40 + 41 + const setTagId = (id: string) => { 42 + router.push(pathname + "?" + createQueryString("id", id)); 43 + }; 44 + 45 + useEffect(() => { 46 + 47 + fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${params.guildId}/modules/tags`, { 48 + headers: { 49 + authorization: localStorage.getItem("token") as string 50 + } 51 + }) 52 + .then(async (res) => { 53 + const response = await res.json() as ApiV1GuildsModulesTagsGetResponse[]; 54 + if (!response) return; 55 + 56 + switch (res.status) { 57 + case 200: { 58 + setTags(response); 59 + if (!tagId) setTagId(response[0].tagId); 60 + break; 61 + } 62 + default: { 63 + setTags([]); 64 + setError((response as unknown as RouteErrorResponse).message); 65 + break; 66 + } 67 + } 68 + 69 + }) 70 + .catch(() => { 71 + setError("Error while fetching tags data"); 72 + }); 73 + 74 + }, []); 75 + 76 + useEffect(() => { 77 + if (!tag && tags[0]) setTagId(tags[0].tagId); 78 + }, [tags.length]); 79 + 80 + if (error) { 81 + return <> 82 + <ScreenMessage 83 + title="Something went wrong.." 84 + description={error} 85 + href={`/dashboard/${guild?.id}`} 86 + button="Go back to overview" 87 + icon={<HiViewGridAdd />} 88 + /> 89 + </>; 90 + } 91 + 92 + if (!tags) return <></>; 93 + 94 + function save<T extends keyof ApiV1GuildsModulesTagsGetResponse>(key: keyof ApiV1GuildsModulesTagsGetResponse, value: ApiV1GuildsModulesTagsGetResponse[T]) { 95 + if (!tagId) return; 96 + if (!tag) return; 97 + 98 + setTags((tags) => { 99 + const newtags = tags?.filter((t) => t.tagId !== tagId) || []; 100 + newtags.push({ ...tag, [key]: value }); 101 + return newtags; 102 + }); 103 + } 104 + 105 + return ( 106 + <> 107 + 108 + <div className="flex flex-wrap items-center gap-2 -mt-2 mb-5"> 109 + {tags 110 + .sort((a, b) => a.name.localeCompare(b.name)) 111 + .map((tag) => ( 112 + <Chip 113 + key={"guildTags-" + tag.tagId} 114 + as={Button} 115 + className="default border-0" 116 + variant={tagId === tag.tagId ? "flat" : "faded"} 117 + color={tagId === tag.tagId ? "secondary" : undefined} 118 + startContent={<span className="opacity-50 relative left-2">{tag.applicationCommandId ? "/" : "wm -"}</span>} 119 + onClick={() => setTagId(tag.tagId)} 120 + > 121 + {tag.name + " "} 122 + </Chip> 123 + ))} 124 + <CreateTag guildId={guild?.id as string} style={Style.Compact} setTags={setTags} setTagId={setTagId} /> 125 + 126 + <div className="ml-auto flex items-center gap-4"> 127 + <span className="dark:text-orange-600 text-orange-400 font-medium hidden md:block">This feature is in early testing</span> 128 + <Tooltip content="Created tags / Limit" closeDelay={0}> 129 + <span className="dark:text-neutral-600 text-neutral-400 cursor-default">{tags.length}/{30}</span> 130 + </Tooltip> 131 + 132 + <DeleteTag guildId={guild?.id as string} tagId={tagId} name={tag?.name} setTags={setTags} /> 133 + </div> 134 + 135 + </div> 136 + 137 + {tag ? 138 + <> 139 + 140 + <div className="relative rounded-md overflow-hidden"> 141 + <StatsBar 142 + items={[ 143 + { 144 + name: "All time Unique Users", 145 + number: 420, 146 + gained: 69, 147 + append: "yesterday" 148 + }, 149 + { 150 + name: "All time Command Uses", 151 + number: 69420, 152 + gained: 42, 153 + append: "yesterday" 154 + } 155 + ]} 156 + /> 157 + 158 + <div className="absolute top-0 left-0 flex flex-col justify-center items-center w-full h-full backdrop-blur-md backdrop-brightness-75" > 159 + <div className="text-3xl dark:text-neutral-100 text-neutral-900 font-semibold mb-2">Nothing to see here.. yet..</div> 160 + <div className="text-md dark:text-neutral-400 text-neutral-600 font-semibold">Here will be the usage of your custom commands soon</div> 161 + </div> 162 + </div> 163 + 164 + <div className="lg:flex gap-3 mt-6"> 165 + <div className="lg:w-1/2"> 166 + <TextInput 167 + key={tag.tagId} 168 + name="Name" 169 + url={`/guilds/${guild?.id}/modules/tags/${tag.tagId}`} 170 + dataName="name" 171 + description="The name of the custom command." 172 + defaultState={tag.name} 173 + resetState={tag.name} 174 + onSave={(value) => save("name", value as string)} 175 + /> 176 + </div> 177 + 178 + <div className="lg:w-1/2"> 179 + <SelectInput 180 + key={tag.tagId} 181 + name="Permissions" 182 + url={`/guilds/${guild?.id}/modules/tags/${tag.tagId}`} 183 + items={ 184 + Permissions.sort((a, b) => a.localeCompare(b)).map((p) => { 185 + return { name: convertCamelCaseToSpaced(p), value: p }; 186 + }) || [] 187 + } 188 + dataName="permission" 189 + description="The permissions needed to execute this tag." 190 + defaultState={tag.permission} 191 + onSave={(option) => save("permission", option.value as string)} 192 + showClear 193 + /> 194 + </div> 195 + </div> 196 + 197 + <MessageCreatorEmbed 198 + key={tag.tagId} 199 + name="Message" 200 + url={`/guilds/${guild?.id}/modules/tags/${tag.tagId}`} 201 + dataName="message" 202 + defaultMessage={tag.message} 203 + /> 204 + </> 205 + : 206 + <div className="w-full flex flex-col items-center justify-center" style={{ marginTop: "20vh" }}> 207 + <div> 208 + 209 + <div className="mb-10 flex flex-col items-center text-center"> 210 + <span className="text-4xl dark:text-neutral-100 text-neutral-900 font-semibold">You dont have any tags yet</span> <br /> 211 + <span className="text-lg dark:text-neutral-400 text-neutral-600 font-semibold">Create new custom commands to get started</span> 212 + </div> 213 + 214 + <div className="w-full flex flex-col items-center"> 215 + <CreateTag guildId={guild?.id as string} style={Style.Big} setTags={setTags} setTagId={setTagId} /> 216 + </div> 217 + 218 + </div> 219 + </div> 220 + } 221 + 222 + </> 223 + ); 224 + } 225 + 226 + function convertCamelCaseToSpaced(input: string): string { 227 + const spacedString = input.replace(/([A-Z])/g, " $1"); 228 + return spacedString.charAt(0).toUpperCase() + spacedString.slice(1); 229 + }
+5 -1
components/List.tsx
··· 33 33 return ( 34 34 <li className="mr-2" key={tab.name}> 35 35 <button 36 - className={cn("inline-block p-3 pb-2 border-b-2 border-transparent rounded-t-lg font-medium hover:text-violet-400 duration-200", isCurrent && "text-violet-500 border-b-2 border-violet-500", disabled && "cursor-not-allowed")} 36 + className={cn( 37 + "inline-block p-3 pb-2 border-b-2 border-transparent rounded-t-lg font-medium hover:text-violet-400 duration-200", 38 + isCurrent && "text-violet-500 border-b-2 border-violet-500", 39 + disabled && "cursor-not-allowed opacity-75" 40 + )} 37 41 onClick={() => { 38 42 if (disabled) return; 39 43
+1 -1
components/embed-creator.tsx
··· 17 17 url: string; 18 18 dataName: string; 19 19 20 - defaultMessage?: { content?: string, embed?: GuildEmbed }; 20 + defaultMessage?: { content?: string | null, embed?: GuildEmbed }; 21 21 collapseable?: boolean; 22 22 23 23 messageAttachmentComponent?: React.ReactNode;
+47
lib/discord.ts
··· 1 + export const Permissions = [ 2 + "CreateInstantInvite", 3 + "KickMembers", 4 + "BanMembers", 5 + "Administrator", 6 + "ManageChannels", 7 + "ManageGuild", 8 + "AddReactions", 9 + "ViewAuditLog", 10 + "PrioritySpeaker", 11 + "Stream", 12 + "ViewChannel", 13 + "SendMessages", 14 + "SendTtsMessages", 15 + "ManageMessages", 16 + "EmbedLinks", 17 + "AttachFiles", 18 + "ReadMessageHistory", 19 + "MentionEveryone", 20 + "UseExternalEmojis", 21 + "ViewGuildInsights", 22 + "Connect", 23 + "Speak", 24 + "MuteMembers", 25 + "DeafenMembers", 26 + "MoveMembers", 27 + "UseVad", 28 + "ChangeNickname", 29 + "ManageNicknames", 30 + "ManageRoles", 31 + "ManageWebhooks", 32 + "ManageGuildExpressions", 33 + "UseApplicationCommands", 34 + "RequestToSpeak", 35 + "ManageEvents", 36 + "ManageThreads", 37 + "CreatePublicThreads", 38 + "CreatePrivateThreads", 39 + "UseExternalStickers", 40 + "SendMessagesInThreads", 41 + "UseEmbeddedActivities", 42 + "ModerateMembers", 43 + "ViewCreatorMonetizationAnalytics", 44 + "UseSoundboard", 45 + "UseExternalSounds", 46 + "SendVoiceMessages" 47 + ] as string[];
+20
typings.ts
··· 262 262 messages: number; 263 263 voiceminutes: number; 264 264 invites: number; 265 + formattedVoicetime: string; 265 266 }; 266 267 } 267 268 268 269 export interface ApiV1UsersMeConnectionsSpotifyGetResponse { 269 270 displayName: string; 270 271 avatar: string | null; 272 + } 273 + 274 + export interface ApiV1GuildsModulesTagsGetResponse { 275 + tagId: string; 276 + guildId: string; 277 + applicationCommandId?: string; 278 + 279 + name: string; 280 + permission: string | null; 281 + aliases: string[]; 282 + 283 + message: { 284 + content: string | null; 285 + embed?: GuildEmbed; 286 + }; 287 + 288 + authorId: string; 289 + 290 + createdAt: Date; 271 291 } 272 292 273 293 export interface PronounsResponse {