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 anonymous starboard

Luna 600cc425 fa087dbe

+169 -88
+70
app/dashboard/[guildId]/starboard/hooks.ts
··· 1 + import { useEffect, useState } from "react"; 2 + 3 + import { StarboardStyle } from "@/typings"; 4 + 5 + interface Example { 6 + avatar: string | null; 7 + username: string | null; 8 + } 9 + 10 + export function useExample(style: StarboardStyle) { 11 + const [example, setExample] = useState<Example>({ 12 + avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 13 + username: "@spacewolf." 14 + }); 15 + 16 + useEffect( 17 + () => { 18 + switch (style) { 19 + case StarboardStyle.Username: 20 + setExample((e) => { 21 + return { 22 + ...e, 23 + avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 24 + username: "@spacewolf." 25 + }; 26 + }); 27 + break; 28 + case StarboardStyle.GlobalName: 29 + setExample((e) => { 30 + return { 31 + ...e, 32 + avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 33 + username: "Space Wolf" 34 + }; 35 + }); 36 + break; 37 + case StarboardStyle.Nickname: 38 + setExample((e) => { 39 + return { 40 + ...e, 41 + avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 42 + username: "Luna’s Grandpa <3" 43 + }; 44 + }); 45 + break; 46 + case StarboardStyle.NicknameAndGuildAvatar: 47 + setExample((e) => { 48 + return { 49 + ...e, 50 + avatar: "https://cdn.waya.one/r/a_3a2fa421f079827d31f4fd1b7a9971ba.gif", 51 + username: "Luna’s Grandpa <3" 52 + }; 53 + }); 54 + break; 55 + case StarboardStyle.Anonymous: 56 + setExample((e) => { 57 + return { 58 + ...e, 59 + avatar: null, 60 + username: null 61 + }; 62 + }); 63 + break; 64 + } 65 + }, 66 + [style] 67 + ); 68 + 69 + return example; 70 + }
+52 -74
app/dashboard/[guildId]/starboard/page.tsx
··· 1 1 "use client"; 2 + 2 3 import { Button } from "@nextui-org/react"; 3 4 import Image from "next/image"; 4 5 import Link from "next/link"; 5 6 import { useParams } from "next/navigation"; 6 - import { useState } from "react"; 7 + import { useCallback } from "react"; 7 8 import { HiExternalLink, HiViewGridAdd } from "react-icons/hi"; 8 - import { useQuery } from "react-query"; 9 + import { useQuery, useQueryClient } from "react-query"; 9 10 10 11 import { guildStore } from "@/common/guilds"; 11 12 import { DiscordMarkdown } from "@/components/discord/markdown"; ··· 16 17 import SelectMenu from "@/components/inputs/select-menu"; 17 18 import Switch from "@/components/inputs/switch"; 18 19 import { ScreenMessage } from "@/components/screen-message"; 19 - import { getData } from "@/lib/api"; 20 + import { cacheOptions, getData } from "@/lib/api"; 20 21 import SadWumpusPic from "@/public/sad-wumpus.gif"; 21 - import type { ApiError, ApiV1GuildsModulesStarboardGetResponse } from "@/typings"; 22 + import { type ApiV1GuildsModulesStarboardGetResponse, StarboardStyle } from "@/typings"; 22 23 import { createSelectableItems } from "@/utils/create-selectable-items"; 24 + 25 + import { useExample } from "./hooks"; 23 26 24 27 export default function Home() { 25 28 const guild = guildStore((g) => g); 26 29 const params = useParams(); 27 30 28 31 const url = `/guilds/${params.guildId}/modules/starboard` as const; 29 - 30 - const [data, setData] = useState<ApiV1GuildsModulesStarboardGetResponse | ApiError>(); 32 + const queryClient = useQueryClient(); 31 33 32 - const { isLoading, error } = useQuery( 34 + const { data, isLoading, error } = useQuery( 33 35 url, 34 36 () => getData<ApiV1GuildsModulesStarboardGetResponse>(url), 35 37 { 36 38 enabled: !!params.guildId, 37 - onSuccess: (d) => setData(d) 39 + ...cacheOptions 38 40 } 39 41 ); 40 42 41 - const handleSwitchToggle = (enabled: boolean) => { 42 - if (!data) return; 43 - const updatedLocalData = { ...data, enabled }; 44 - setData(updatedLocalData); 45 - }; 43 + const example = useExample(data && !("message" in data) 44 + ? data.style 45 + : StarboardStyle.Username 46 + ); 46 47 47 - const [example, setExample] = useState({ 48 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 49 - username: "@spacewolf.", 50 - color: 0 51 - }); 48 + const edit = useCallback( 49 + <K extends keyof ApiV1GuildsModulesStarboardGetResponse>(key: K, value: ApiV1GuildsModulesStarboardGetResponse[K]) => { 50 + if (!data || "message" in data) return; 52 51 53 - const handleUserStyle = (value: number) => { 54 - switch (value) { 55 - case 0: 56 - setExample((e) => { 57 - return { 58 - ...e, 59 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 60 - username: "@spacewolf." 61 - }; 62 - }); 63 - break; 64 - case 1: 65 - setExample((e) => { 66 - return { 67 - ...e, 68 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 69 - username: "Space Wolf" 70 - }; 71 - }); 72 - break; 73 - case 2: 74 - setExample((e) => { 75 - return { 76 - ...e, 77 - avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png", 78 - username: "Luna’s Grandpa <3" 79 - }; 80 - }); 81 - break; 82 - case 3: 83 - setExample((e) => { 84 - return { 85 - ...e, 86 - avatar: "https://cdn.waya.one/r/a_3a2fa421f079827d31f4fd1b7a9971ba.gif", 87 - username: "Luna’s Grandpa <3" 88 - }; 89 - }); 90 - break; 91 - } 92 - }; 52 + queryClient.setQueryData<ApiV1GuildsModulesStarboardGetResponse>(url, () => ({ 53 + ...data, 54 + [key]: value 55 + })); 56 + }, 57 + [data] 58 + ); 93 59 94 60 if (error || (data && "message" in data)) { 95 61 return ( ··· 130 96 dataName="enabled" 131 97 defaultState={data.enabled} 132 98 disabled={false} 133 - onSave={handleSwitchToggle} 99 + onSave={(v) => edit("enabled", v)} 134 100 /> 135 101 136 102 <Switch ··· 139 105 dataName="allowBots" 140 106 defaultState={data.allowBots} 141 107 disabled={!data.enabled} 108 + onSave={(v) => edit("allowBots", v)} 142 109 /> 143 110 144 111 <Switch ··· 147 114 dataName="allowNSFW" 148 115 defaultState={data.allowNSFW} 149 116 disabled={!data.enabled} 117 + onSave={(v) => edit("allowNSFW", v)} 150 118 /> 151 119 152 120 <Switch ··· 156 124 dataName="allowEdits" 157 125 defaultState={data.allowEdits} 158 126 disabled={!data.enabled} 127 + onSave={(v) => edit("allowEdits", v)} 159 128 /> 160 129 161 130 <Switch ··· 165 134 dataName="allowSelfReact" 166 135 defaultState={data.allowSelfReact} 167 136 disabled={!data.enabled} 137 + onSave={(v) => edit("allowSelfReact", v)} 168 138 /> 169 139 170 140 <Switch ··· 174 144 dataName="displayReference" 175 145 defaultState={data.displayReference} 176 146 disabled={!data.enabled} 147 + onSave={(v) => edit("displayReference", v)} 177 148 /> 178 149 179 150 <Switch ··· 183 154 dataName="delete" 184 155 defaultState={data.delete} 185 156 disabled={!data.enabled} 157 + onSave={(v) => edit("delete", v)} 186 158 /> 187 159 188 160 <NumberInput ··· 193 165 defaultState={data.requiredEmojis ?? 0} 194 166 disabled={!data.enabled} 195 167 min={1} 168 + onSave={(v) => edit("requiredEmojis", v)} 196 169 /> 197 170 198 171 <SelectMenu ··· 203 176 description="Select the channel where the starboard messages should be send into." 204 177 defaultState={data.channelId} 205 178 disabled={!data.enabled} 179 + onSave={(o) => edit("channelId", o.value as string)} 206 180 /> 207 181 208 182 <div className="lg:flex gap-3"> ··· 230 204 ]} 231 205 description="Select the emoji that needs to be reacted with." 232 206 defaultState={data.emoji} 233 - onSave={(options) => { 234 - setData({ 235 - ...data, 236 - emoji: options.value as string 237 - }); 238 - }} 239 207 disabled={!data.enabled} 208 + onSave={(o) => edit("emoji", o.value as string)} 240 209 /> 241 210 </div> 242 211 ··· 248 217 items={[ 249 218 { 250 219 name: "Username", 251 - value: 0 220 + value: StarboardStyle.Username 252 221 }, 253 222 { 254 223 name: "Global Nickname", 255 - value: 1 224 + value: StarboardStyle.GlobalName 256 225 }, 257 226 { 258 227 name: "Nickname", 259 - value: 2 228 + value: StarboardStyle.Nickname 260 229 }, 261 230 { 262 231 name: "Nickname & Per-guild Avatar", 263 - value: 3 232 + value: StarboardStyle.NicknameAndGuildAvatar 233 + }, 234 + { 235 + name: "Anonymous (removes the username & avatar)", 236 + value: StarboardStyle.Anonymous 264 237 } 265 238 ]} 266 239 description="The style members profile gets displayed." 267 240 defaultState={data.style} 268 - onSave={(options) => handleUserStyle(options.value as number)} 269 241 disabled={!data.enabled} 242 + onSave={(o) => edit("style", o.value as number)} 270 243 /> 271 244 </div> 272 245 </div> ··· 282 255 defaultState={data.blacklistChannelIds || []} 283 256 max={500} 284 257 disabled={!data.enabled} 258 + onSave={(o) => edit("blacklistChannelIds", o.map(({ value }) => value as string))} 285 259 /> 286 260 </div> 287 261 <div className="lg:w-1/2"> ··· 294 268 defaultState={data.blacklistRoleIds || []} 295 269 max={500} 296 270 disabled={!data.enabled} 271 + onSave={(o) => edit("blacklistChannelIds", o.map(({ value }) => value as string))} 297 272 /> 298 273 </div> 299 274 </div> ··· 314 289 315 290 <DiscordMessageEmbed 316 291 className="max-w-lg" 317 - author={{ 318 - icon_url: example.avatar, 319 - text: example.username 320 - }} 292 + author={example.username 293 + ? { 294 + icon_url: example.avatar!, 295 + text: example.username 296 + } 297 + : undefined 298 + } 321 299 mode={"DARK"} 322 300 color={data.embedColor} 323 301 >
+4 -3
components/dashboard/lists/hook.ts
··· 38 38 [search] 39 39 ); 40 40 41 - const setItemId = (id: string) => { 42 - router.push(`${pathname}?${createQueryString("id", id)}`); 43 - }; 41 + const setItemId = useCallback( 42 + (id: string) => router.push(`${pathname}?${createQueryString("id", id)}`), 43 + [router] 44 + ); 44 45 45 46 const editItem = useCallback( 46 47 <K extends keyof T>(key: K, value: T[K]) => {
+31 -8
components/inputs/number-input.tsx
··· 17 17 className?: string; 18 18 19 19 name: string; 20 - url: string; 21 - dataName: string; 20 + url?: string; 21 + dataName?: string; 22 22 disabled?: boolean; 23 23 description?: string; 24 24 defaultState: number; 25 25 26 26 min?: number; 27 27 max?: number; 28 + 29 + onSave?: (state: number) => void; 28 30 } 29 31 30 32 export default function NumberInput({ ··· 35 37 disabled, 36 38 description, 37 39 defaultState, 40 + 38 41 min = 0, 39 - max = Infinity 42 + max = Infinity, 43 + 44 + onSave 40 45 }: Props) { 41 46 const web = webStore((w) => w); 42 47 ··· 47 52 const intervalRef = useRef<NodeJS.Timeout | null>(null); 48 53 49 54 const [value, setValue] = useState<number>(); 50 - const [defaultStatealue, _setDefaultalue] = useState<number>(); 55 + const [def, setDef] = useState<number>(); 51 56 52 57 useEffect(() => { 53 58 if (!hold) { ··· 79 84 80 85 useEffect(() => { 81 86 setValue(defaultState); 82 - _setDefaultalue(defaultState); 87 + setDef(defaultState); 83 88 }, [defaultState]); 84 89 85 90 function handleSave() { 86 - if (defaultStatealue === value) return; 91 + if (def === value || !value) return; 87 92 setError(undefined); 88 93 setState(State.Loading); 89 94 95 + if (!url) { 96 + if (!onSave) { 97 + setValue(def); 98 + throw new Error("Warning: <Switch.onSave> must be defined when not using <Switch.url>."); 99 + } 100 + 101 + setState(State.Idle); 102 + setDef(value); 103 + return; 104 + } 105 + 106 + if (!dataName) { 107 + setValue(def); 108 + throw new Error("Warning: <Switch.dataName> must be defined when using <Switch.url>."); 109 + } 110 + 90 111 fetch(`${process.env.NEXT_PUBLIC_API}${url}`, { 91 112 method: "PATCH", 92 113 credentials: "include", ··· 105 126 106 127 switch (res.status) { 107 128 case 200: { 129 + onSave?.(value); 130 + setDef(value); 131 + 108 132 setState(State.Success); 109 - _setDefaultalue(value); 110 133 setTimeout(() => setState(State.Idle), 1_000 * 8); 111 134 break; 112 135 } ··· 146 169 (state === State.Loading || disabled) && "opacity-50" 147 170 )} 148 171 > 149 - {defaultStatealue !== value && 172 + {def !== value && 150 173 <Button 151 174 onClick={handleSave} 152 175 className="mr-2"
+3 -2
public/docs/starboard.md
··· 8 8 1. Install Wamellow on your server by going to [wamellow.com/add](https://wamellow.com/add). 9 9 2. Head to the dashboard by going to [wamellow.com/dashboard](https://wamellow.com/dashboard?to=starboard). 10 10 3. Select your server from the dashboard. 11 - 4. Navigate to the **Starboard** menu. 11 + 4. Navigate to the **Starboard** menu. 12 12 5. Enable the Starboard module by clicking the enable button. 13 13 6. Select a channel for messages to be posted into. 14 14 7. **🎉 Done!** Try it out by reacting to any message with the selected emote. ··· 48 48 - Username (`@mwlica`) 49 49 - Global Nickname (`yll`) 50 50 - Guild Nickname (`Luna`) 51 - - Guild Nickname & Per-Guild Avatar 51 + - Guild Nickname & Per-Guild Avatar (`Luna`) 52 + - Anonymous (removes the username & avatar) 52 53 53 54 ### 🤚 Blacklist roles 54 55 Specify roles that are restricted from starring other people's messages and prevent their own messages from appearing on the starboard.
+9 -1
typings.ts
··· 178 178 }; 179 179 } 180 180 181 + export enum StarboardStyle { 182 + Username = 0, 183 + GlobalName = 1, 184 + Nickname = 2, 185 + NicknameAndGuildAvatar = 3, 186 + Anonymous = 4 187 + } 188 + 181 189 export interface ApiV1GuildsModulesStarboardGetResponse { 182 190 enabled: boolean; 183 191 channelId?: string; 184 192 emoji: string; 185 193 requiredEmojis: number; 186 194 embedColor: number; 187 - style: number; 195 + style: StarboardStyle; 188 196 189 197 allowNSFW: boolean; 190 198 allowBots: boolean;