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.

fix some react query things

Luna fedd9635 62c7a215

+170 -181
+4 -6
app/dashboard/[guildId]/custom-commands/create.component.tsx
··· 1 1 "use client"; 2 2 import { Button, Chip } from "@nextui-org/react"; 3 - import { Dispatch, SetStateAction, useState } from "react"; 3 + import { useState } from "react"; 4 4 import { HiPencil } from "react-icons/hi"; 5 5 6 6 import DumbTextInput from "@/components/inputs/Dumb_TextInput"; ··· 16 16 guildId: string; 17 17 style: Style; 18 18 19 - setTags: Dispatch<SetStateAction<ApiV1GuildsModulesTagsGetResponse[]>>; 19 + addTag: (tag: ApiV1GuildsModulesTagsGetResponse) => void; 20 20 setTagId: (id: string) => void; 21 21 } 22 22 23 - export default function CreateTag({ guildId, style, setTags, setTagId }: Props) { 23 + export default function CreateTag({ guildId, style, addTag, setTagId }: Props) { 24 24 25 25 const [open, setOpen] = useState(false); 26 26 const [name, setName] = useState(""); ··· 63 63 }); 64 64 }} 65 65 onSuccess={(tag) => { 66 - setTags((tags) => { 67 - return [tag, ...tags]; 68 - }); 66 + addTag(tag); 69 67 setTagId(tag.tagId); 70 68 }} 71 69 >
+4 -8
app/dashboard/[guildId]/custom-commands/delete.component.tsx
··· 1 1 "use client"; 2 2 import { Button, Tooltip } from "@nextui-org/react"; 3 - import { Dispatch, SetStateAction, useState } from "react"; 3 + import { useState } from "react"; 4 4 import { HiTrash } from "react-icons/hi"; 5 5 6 6 import Modal from "@/components/modal"; 7 - import { ApiV1GuildsModulesTagsGetResponse } from "@/typings"; 8 7 9 8 interface Props { 10 9 guildId: string; 11 10 tagId: string | null; 12 11 name?: string; 13 12 14 - setTags: Dispatch<SetStateAction<ApiV1GuildsModulesTagsGetResponse[]>>; 13 + removeTag: (tagId: string) => void; 15 14 } 16 15 17 - export default function DeleteTag({ guildId, tagId, name, setTags }: Props) { 16 + export default function DeleteTag({ guildId, tagId, name, removeTag }: Props) { 18 17 19 18 const [open, setOpen] = useState(false); 20 19 ··· 48 47 }); 49 48 }} 50 49 onSuccess={() => { 51 - setTags((tags) => { 52 - const newtags = tags?.filter((t) => t.tagId !== tagId) || []; 53 - return newtags; 54 - }); 50 + if (tagId) removeTag(tagId); 55 51 }} 56 52 > 57 53 Are you sure you want to delete the {'"'}{name}{'"'} tag? It will be gone forever, probably, who knows.
+63 -68
app/dashboard/[guildId]/custom-commands/page.tsx
··· 2 2 3 3 import { Button, Chip, Tooltip } from "@nextui-org/react"; 4 4 import { useParams, usePathname, useRouter, useSearchParams } from "next/navigation"; 5 - import { useCallback, useEffect, useState } from "react"; 5 + import { useCallback, useEffect } from "react"; 6 6 import { HiViewGridAdd } from "react-icons/hi"; 7 + import { useQuery, useQueryClient } from "react-query"; 7 8 8 9 import { guildStore } from "@/common/guilds"; 9 10 import { StatsBar } from "@/components/counter"; ··· 11 12 import SelectInput from "@/components/inputs/SelectMenu"; 12 13 import TextInput from "@/components/inputs/TextInput"; 13 14 import { ScreenMessage } from "@/components/screen-message"; 15 + import { cacheOptions, getData } from "@/lib/api"; 14 16 import { Permissions } from "@/lib/discord"; 15 - import { ApiV1GuildsModulesTagsGetResponse, RouteErrorResponse } from "@/typings"; 17 + import { ApiV1GuildsModulesTagsGetResponse } from "@/typings"; 16 18 17 19 import CreateTag, { Style } from "./create.component"; 18 20 import DeleteTag from "./delete.component"; 19 21 20 22 export default function Home() { 21 23 const guild = guildStore((g) => g); 22 - 23 - const [error, setError] = useState<string>(); 24 - const [tags, setTags] = useState<ApiV1GuildsModulesTagsGetResponse[]>([]); 25 - 26 24 const pathname = usePathname(); 27 25 const search = useSearchParams(); 28 26 const router = useRouter(); 29 27 const params = useParams(); 28 + const queryClient = useQueryClient(); 29 + 30 + const url = `/guilds/${params.guildId}/modules/tags` as const; 31 + const key = ["guilds", params.guildId, "modules", "custom-commands"] as const; 32 + 33 + const { data, isLoading, error } = useQuery( 34 + key, 35 + () => getData<ApiV1GuildsModulesTagsGetResponse[]>(url), 36 + { 37 + enabled: !!params.guildId, 38 + ...cacheOptions 39 + } 40 + ); 30 41 31 42 const tagId = search.get("id"); 32 - const tag = tags.find((t) => t.tagId === tagId); 43 + const tag = data?.find((t) => t.tagId === tagId); 33 44 34 45 const createQueryString = useCallback((name: string, value: string) => { 35 46 const params = new URLSearchParams(search); ··· 42 53 router.push(pathname + "?" + createQueryString("id", id)); 43 54 }; 44 55 45 - useEffect(() => { 56 + const editTag = <T extends keyof ApiV1GuildsModulesTagsGetResponse>(k: keyof ApiV1GuildsModulesTagsGetResponse, value: ApiV1GuildsModulesTagsGetResponse[T]) => { 57 + if (!tag) return; 46 58 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; 59 + queryClient.setQueryData<ApiV1GuildsModulesTagsGetResponse[]>(key, () => [ 60 + ...(data?.filter((t) => t.tagId !== tag.tagId) || []), 61 + { ...tag, [k]: value } 62 + ]); 63 + }; 55 64 56 - switch (res.status) { 57 - case 200: { 58 - setTags(response); 59 - if (!tagId && response[0]) setTagId(response[0].tagId); 60 - break; 61 - } 62 - default: { 63 - setTags([]); 64 - setError((response as unknown as RouteErrorResponse).message); 65 - break; 66 - } 67 - } 65 + const addTag = (tag: ApiV1GuildsModulesTagsGetResponse) => { 66 + queryClient.setQueryData<ApiV1GuildsModulesTagsGetResponse[]>(key, () => [ 67 + ...(data || []), 68 + tag 69 + ]); 70 + }; 68 71 69 - }) 70 - .catch(() => { 71 - setError("Error while fetching tags data"); 72 - }); 73 - 74 - }, []); 72 + const removeTag = (id: string) => { 73 + queryClient.setQueryData<ApiV1GuildsModulesTagsGetResponse[]>(key, () => 74 + data?.filter((t) => t.tagId !== id) || [] 75 + ); 76 + }; 75 77 76 78 useEffect(() => { 77 - if (!tag && tags[0]) setTagId(tags[0].tagId); 78 - }, [tags.length]); 79 + if (data && !tag && data[0]) setTagId(data[0].tagId); 80 + }, [data?.length]); 79 81 82 + if (!data || isLoading) return <></>; 80 83 if (error) { 81 - return <> 84 + return ( 82 85 <ScreenMessage 83 86 title="Something went wrong.." 84 - description={error} 87 + description={error.toString() || "We couldn't load the data for this page."} 85 88 href={`/dashboard/${guild?.id}`} 86 89 button="Go back to overview" 87 90 icon={<HiViewGridAdd />} 88 91 /> 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 - }); 92 + ); 103 93 } 104 94 105 95 return ( 106 96 <> 107 97 108 98 <div className="flex flex-wrap items-center gap-2 -mt-2 mb-5"> 109 - {tags 99 + {data 110 100 .sort((a, b) => a.name.localeCompare(b.name)) 111 101 .map((tag) => ( 112 102 <Chip ··· 120 110 > 121 111 {tag.name + " "} 122 112 </Chip> 123 - ))} 124 - <CreateTag guildId={guild?.id as string} style={Style.Compact} setTags={setTags} setTagId={setTagId} /> 113 + )) 114 + } 115 + 116 + <CreateTag guildId={guild?.id as string} style={Style.Compact} addTag={addTag} setTagId={setTagId} /> 125 117 126 118 <div className="ml-auto flex items-center gap-4"> 127 119 <span className="dark:text-orange-600 text-orange-400 font-medium hidden md:block">This feature is in early testing</span> 128 120 <Tooltip content="Created tags / Limit" closeDelay={0}> 129 - <span className="dark:text-neutral-600 text-neutral-400 cursor-default">{tags.length}/{30}</span> 121 + <span className="dark:text-neutral-600 text-neutral-400 cursor-default">{data.length}/{30}</span> 130 122 </Tooltip> 131 123 132 - <DeleteTag guildId={guild?.id as string} tagId={tagId} name={tag?.name} setTags={setTags} /> 124 + <DeleteTag guildId={guild?.id as string} tagId={tagId} name={tag?.name} removeTag={removeTag} /> 133 125 </div> 134 126 135 127 </div> 136 128 137 - {tag ? 129 + {tag && 138 130 <> 139 131 140 132 <div className="relative rounded-md overflow-hidden p-[1px]"> ··· 166 158 <TextInput 167 159 key={tag.tagId} 168 160 name="Name" 169 - url={`/guilds/${guild?.id}/modules/tags/${tag.tagId}`} 161 + url={url + "/" + tag.tagId} 170 162 dataName="name" 171 163 description="The name of the custom command." 172 164 defaultState={tag.name} 173 165 resetState={tag.name} 174 - onSave={(value) => save("name", value as string)} 166 + onSave={(value) => editTag("name", value as string)} 175 167 /> 176 168 </div> 177 169 ··· 179 171 <SelectInput 180 172 key={tag.tagId} 181 173 name="Permissions" 182 - url={`/guilds/${guild?.id}/modules/tags/${tag.tagId}`} 174 + url={url + "/" + tag.tagId} 183 175 items={ 184 - Permissions.sort((a, b) => a.localeCompare(b)).map((p) => { 185 - return { name: convertCamelCaseToSpaced(p), value: p }; 186 - }) || [] 176 + Permissions.sort((a, b) => a.localeCompare(b)).map((p) => ( 177 + { name: convertCamelCaseToSpaced(p), value: p } 178 + )) || [] 187 179 } 188 180 dataName="permission" 189 181 description="The permissions needed to execute this tag." 190 182 defaultState={tag.permission} 191 - onSave={(option) => save("permission", option.value as string)} 183 + onSave={(option) => editTag("permission", option.value as string)} 192 184 showClear 193 185 /> 194 186 </div> ··· 197 189 <MessageCreatorEmbed 198 190 key={tag.tagId} 199 191 name="Message" 200 - url={`/guilds/${guild?.id}/modules/tags/${tag.tagId}`} 192 + url={url + "/" + tag.tagId} 201 193 dataName="message" 202 194 defaultMessage={tag.message} 195 + onSave={(value) => editTag("message", value as string)} 203 196 /> 204 197 </> 205 - : 198 + } 199 + 200 + {!data.length && 206 201 <div className="w-full flex flex-col items-center justify-center" style={{ marginTop: "20vh" }}> 207 202 <div> 208 203 ··· 212 207 </div> 213 208 214 209 <div className="w-full flex flex-col items-center"> 215 - <CreateTag guildId={guild?.id as string} style={Style.Big} setTags={setTags} setTagId={setTagId} /> 210 + <CreateTag guildId={guild?.id as string} style={Style.Big} addTag={addTag} setTagId={setTagId} /> 216 211 </div> 217 212 218 213 </div>
+1 -1
app/dashboard/[guildId]/greeting/welcome/page.tsx
··· 307 307 url={`/guilds/${guild?.id}/modules/welcome`} 308 308 dataName="dm.message" 309 309 defaultMessage={welcome?.dm?.message} 310 - collapseable={true} 310 + isCollapseable={true} 311 311 disabled={!welcome.enabled} 312 312 > 313 313
+65 -84
app/dashboard/[guildId]/leaderboards/page.tsx
··· 1 - 2 1 "use client"; 3 2 import { useParams } from "next/navigation"; 4 - import { useEffect, useState } from "react"; 3 + import { useState } from "react"; 5 4 import { HiChartBar, HiViewGridAdd } from "react-icons/hi"; 5 + import { useQuery } from "react-query"; 6 6 7 7 import { Guild, guildStore } from "@/common/guilds"; 8 8 import { webStore } from "@/common/webstore"; ··· 11 11 import MultiSelectMenu from "@/components/inputs/MultiSelectMenu"; 12 12 import TextInput from "@/components/inputs/TextInput"; 13 13 import { ScreenMessage } from "@/components/screen-message"; 14 - import { ApiV1GuildsModulesLeaderboardGetResponse, RouteErrorResponse } from "@/typings"; 14 + import { getData } from "@/lib/api"; 15 + import { ApiV1GuildsModulesLeaderboardGetResponse } from "@/typings"; 15 16 16 17 import OverviewLinkComponent from "../../../../components/OverviewLinkComponent"; 17 18 import UpdatingLeaderboardCard from "./updating.component"; ··· 19 20 export default function Home() { 20 21 const guild = guildStore((g) => g); 21 22 const web = webStore((w) => w); 22 - 23 - const [error, setError] = useState<string>(); 24 - const [leaderboard, setLeaderboard] = useState<ApiV1GuildsModulesLeaderboardGetResponse>(); 25 - 26 23 const params = useParams(); 27 24 28 - useEffect(() => { 25 + const url = `/guilds/${params.guildId}/modules/leaderboard` as const; 29 26 30 - fetch(`${process.env.NEXT_PUBLIC_API}/guilds/${params.guildId}/modules/leaderboard`, { 31 - headers: { 32 - authorization: localStorage.getItem("token") as string 33 - } 34 - }) 35 - .then(async (res) => { 36 - const response = await res.json() as ApiV1GuildsModulesLeaderboardGetResponse; 37 - if (!response) return; 27 + const [data, setData] = useState<ApiV1GuildsModulesLeaderboardGetResponse | null>(null); 38 28 39 - switch (res.status) { 40 - case 200: { 41 - setLeaderboard(response); 42 - break; 43 - } 44 - default: { 45 - setLeaderboard(undefined); 46 - setError((response as unknown as RouteErrorResponse).message); 47 - break; 48 - } 49 - } 29 + const { isLoading, error } = useQuery( 30 + ["guilds", params.guildId, "modules", "leaderboard"], 31 + () => getData<ApiV1GuildsModulesLeaderboardGetResponse>(url), 32 + { 33 + enabled: !!params.guildId, 34 + onSuccess: (d) => setData(d) 35 + } 36 + ); 50 37 51 - }) 52 - .catch(() => { 53 - setError("Error while fetching leaderboard data"); 54 - }); 55 - 56 - }, []); 57 - 38 + if (!data || isLoading) return <></>; 58 39 if (error) { 59 - return <> 40 + return ( 60 41 <ScreenMessage 61 42 title="Something went wrong.." 62 - description={error} 43 + description={error.toString() || "We couldn't load the data for this page."} 63 44 href={`/dashboard/${guild?.id}`} 64 45 button="Go back to overview" 65 46 icon={<HiViewGridAdd />} 66 47 /> 67 - </>; 48 + ); 68 49 } 69 - 70 - if (!leaderboard) return <></>; 71 50 72 51 return ( 73 52 <div> ··· 79 58 icon={<HiChartBar />} 80 59 /> 81 60 82 - <div className={`flex gap-4 border-2 border-violet-400 p-4 mb-4 rounded-lg ${!web.devToolsEnabled && "opacity-50 cursor-not-allowed"}`}> 61 + {web.devToolsEnabled && 62 + <div className={"flex gap-4 border-2 border-violet-400 p-4 mb-4 rounded-lg"}> 63 + 64 + <div className="lg:w-1/2 flex gap-2 w-full"> 65 + 66 + <div className="w-1/2"> 67 + <TextInput 68 + name="Text" 69 + url={url} 70 + dataName="textColor" 71 + description="Color used for text." 72 + type="color" 73 + defaultState={data.textColor ?? 0xe5e5e5} 74 + resetState={0xe5e5e5} 75 + disabled={!web.devToolsEnabled} 76 + /> 77 + </div> 83 78 84 - <div className="lg:w-1/2 flex gap-2 w-full"> 79 + <div className="w-1/2"> 80 + <TextInput 81 + name="Accent" 82 + url={url} 83 + dataName="accentColor" 84 + description="Color used for secondary text." 85 + type="color" 86 + defaultState={data.accentColor ?? 0x8b5cf6} 87 + resetState={0x8b5cf6} 88 + disabled={!web.devToolsEnabled} 89 + /> 90 + </div> 85 91 86 - <div className="w-1/2"> 87 - <TextInput 88 - name="Text" 89 - url={`/guilds/${guild?.id}/modules/leaderboard`} 90 - dataName="textColor" 91 - description="Color used for text." 92 - type="color" 93 - defaultState={leaderboard?.textColor ?? 0xe5e5e5} 94 - resetState={0xe5e5e5} 95 - disabled={!web.devToolsEnabled} 96 - /> 97 92 </div> 98 93 99 94 <div className="w-1/2"> 100 95 <TextInput 101 - name="Accent" 102 - url={`/guilds/${guild?.id}/modules/leaderboard`} 103 - dataName="accentColor" 104 - description="Color used for secondary text." 96 + name="Background" 97 + url={url} 98 + dataName="backgroundColor" 99 + description="Color used for the background." 105 100 type="color" 106 - defaultState={leaderboard?.accentColor ?? 0x8b5cf6} 107 - resetState={0x8b5cf6} 101 + defaultState={data.backgroundColor ?? 0x0d0f11} 102 + resetState={0x0d0f11} 108 103 disabled={!web.devToolsEnabled} 109 104 /> 110 105 </div> 111 106 112 107 </div> 113 - 114 - <div className="w-1/2"> 115 - <TextInput 116 - name="Background" 117 - url={`/guilds/${guild?.id}/modules/leaderboard`} 118 - dataName="backgroundColor" 119 - description="Color used for the background." 120 - type="color" 121 - defaultState={leaderboard?.backgroundColor ?? 0x0d0f11} 122 - resetState={0x0d0f11} 123 - disabled={!web.devToolsEnabled} 124 - /> 125 - </div> 126 - 127 - </div> 108 + } 128 109 129 110 <ImageUrlInput 130 111 name="Banner" 131 - url={`/guilds/${guild?.id}/modules/leaderboard`} 112 + url={url} 132 113 ratio="aspect-[4/1]" 133 114 dataName="banner" 134 115 description="Enter a url which should be the banner of the leaderboard web page. The recomended image ration is 4:1 and recommended resolution 1024x256px." 135 - defaultState={leaderboard.banner || ""} 116 + defaultState={data.banner || ""} 136 117 /> 137 118 138 119 <hr className="mt-6 mb-2 dark:border-wamellow-light border-wamellow-100-light" /> ··· 142 123 <div className="lg:w-1/2"> 143 124 <MultiSelectMenu 144 125 name="Top messager roles" 145 - url={`/guilds/${guild?.id}/modules/leaderboard`} 126 + url={url} 146 127 dataName="roles.messages" 147 128 items={guild?.roles?.sort((a, b) => b.position - a.position).map((r) => ({ name: `@${r.name}`, value: r.id, error: r.missingPermissions.join(", "), color: r.color }))} 148 129 description="Select roles which should be assigned to the top message members." 149 - defaultState={leaderboard?.roles?.messages || []} 130 + defaultState={data.roles?.messages || []} 150 131 max={3} 151 132 /> 152 133 </div> 153 134 <div className="lg:w-1/2"> 154 135 <MultiSelectMenu 155 136 name="Top voice roles" 156 - url={`/guilds/${guild?.id}/modules/leaderboard`} 137 + url={url} 157 138 dataName="roles.voiceminutes" 158 139 items={guild?.roles?.sort((a, b) => b.position - a.position).map((r) => ({ name: `@${r.name}`, value: r.id, error: r.missingPermissions.join(", "), color: r.color }))} 159 140 description="Select roles which should be assigned to the top voice members." 160 - defaultState={leaderboard?.roles?.voiceminutes || []} 141 + defaultState={data.roles?.voiceminutes || []} 161 142 max={3} 162 143 /> 163 144 </div> ··· 168 149 <div className="lg:w-1/2"> 169 150 <MultiSelectMenu 170 151 name="Blacklisted channels" 171 - url={`/guilds/${guild?.id}/modules/leaderboard`} 152 + url={url} 172 153 dataName="blacklistChannelIds" 173 154 items={guild?.channels?.sort((a, b) => a.name.localeCompare(b.name)).map((c) => ({ name: `#${c.name}`, value: c.id }))} 174 155 description="Select channels which should not be able to be counted." 175 - defaultState={leaderboard?.blacklistChannelIds || []} 156 + defaultState={data.blacklistChannelIds || []} 176 157 max={500} 177 158 /> 178 159 </div> ··· 181 162 <span className="dark:text-neutral-500 text-neutral-400 text-sm">Leaderboards update roughtly all 20 minutes</span> 182 163 183 164 <div className="w-full grid gap-4 md:flex md:gap-0 md:items-center mt-5"> 184 - <UpdatingLeaderboardCard guild={guild as Guild} lb={leaderboard.updating.find((lb) => lb.type === "messages")} type="messages" /> 165 + <UpdatingLeaderboardCard guild={guild as Guild} lb={data.updating.find((lb) => lb.type === "messages")} type="messages" /> 185 166 <Betweener /> 186 - <UpdatingLeaderboardCard guild={guild as Guild} lb={leaderboard.updating.find((lb) => lb.type === "voiceminutes")} type="voiceminutes" /> 167 + <UpdatingLeaderboardCard guild={guild as Guild} lb={data.updating.find((lb) => lb.type === "voiceminutes")} type="voiceminutes" /> 187 168 <Betweener /> 188 - <UpdatingLeaderboardCard guild={guild as Guild} lb={leaderboard.updating.find((lb) => lb.type === "invites")} type="invites" /> 169 + <UpdatingLeaderboardCard guild={guild as Guild} lb={data.updating.find((lb) => lb.type === "invites")} type="invites" /> 189 170 </div> 190 171 191 172 </div >
+4 -4
app/dashboard/[guildId]/nsfw-image-scanning/page.tsx
··· 23 23 24 24 const [data, setData] = useState<ApiV1GuildsModulesNsfwModerationGetResponse | null>(null); 25 25 26 - const { status } = useQuery( 26 + const { isLoading, error } = useQuery( 27 27 ["guilds", params.guildId, "modules", "nsfw-image-scanning"], 28 28 () => getData<ApiV1GuildsModulesNsfwModerationGetResponse>(url), 29 29 { ··· 38 38 setData(updatedLocalData); 39 39 }; 40 40 41 - if (status === "loading") return <></>; 42 - if (!data || status === "error") { 41 + if (!data || isLoading) return <></>; 42 + if (error) { 43 43 return ( 44 44 <ScreenMessage 45 45 title="Something went wrong.." 46 - description="We couldn't load the data for this page." 46 + description={error.toString() || "We couldn't load the data for this page."} 47 47 href={`/dashboard/${guild?.id}`} 48 48 button="Go back to overview" 49 49 icon={<HiViewGridAdd />}
+4 -4
app/dashboard/[guildId]/starboard/page.tsx
··· 25 25 26 26 const [data, setData] = useState<ApiV1GuildsModulesStarboardGetResponse | null>(null); 27 27 28 - const { status } = useQuery( 28 + const { isLoading, error } = useQuery( 29 29 ["guilds", params.guildId, "modules", "starboard"], 30 30 () => getData<ApiV1GuildsModulesStarboardGetResponse>(url), 31 31 { ··· 88 88 } 89 89 }; 90 90 91 - if (status === "loading") return <></>; 92 - if (!data || status === "error") { 91 + if (!data || isLoading) return <></>; 92 + if (error) { 93 93 return ( 94 94 <ScreenMessage 95 95 title="Something went wrong.." 96 - description="We couldn't load the data for this page." 96 + description={error.toString() || "We couldn't load the data for this page."} 97 97 href={`/dashboard/${guild?.id}`} 98 98 button="Go back to overview" 99 99 icon={<HiViewGridAdd />}
+19 -6
components/embed-creator.tsx
··· 19 19 dataName: string; 20 20 21 21 defaultMessage?: { content?: string | null, embed?: GuildEmbed }; 22 - collapseable?: boolean; 22 + isCollapseable?: boolean; 23 23 24 24 messageAttachmentComponent?: React.ReactNode; 25 25 showMessageAttachmentComponentInEmbed?: boolean; 26 26 27 27 disabled?: boolean; 28 + onSave?: (state: { content?: string | null, embed?: GuildEmbed }) => void; 28 29 } 29 30 30 - const MessageCreatorEmbed: FunctionComponent<Props> = ({ children, name, url, dataName, defaultMessage, collapseable, messageAttachmentComponent, showMessageAttachmentComponentInEmbed, disabled }) => { 31 + const MessageCreatorEmbed: FunctionComponent<Props> = ({ 32 + children, 33 + name, 34 + url, 35 + dataName, 36 + defaultMessage, 37 + isCollapseable, 38 + messageAttachmentComponent, 39 + showMessageAttachmentComponentInEmbed, 40 + disabled, 41 + onSave 42 + }) => { 31 43 const [state, setState] = useState<"LOADING" | "ERRORED" | "SUCCESS" | undefined>(); 32 44 const [error, setError] = useState<string>(); 33 45 ··· 35 47 const [embed, setEmbed] = useState<string>(JSON.stringify(defaultMessage?.embed || {})); 36 48 const [embedfooter, setEmbedfooter] = useState<string>(JSON.stringify(defaultMessage?.embed?.footer || {})); 37 49 38 - const [open, setOpen] = useState<boolean>(!collapseable); 50 + const [open, setOpen] = useState<boolean>(!isCollapseable); 39 51 const [mode, setMode] = useState<"DARK" | "LIGHT">("DARK"); 40 52 41 53 const modeToggle = ( ··· 74 86 switch (res.status) { 75 87 case 200: { 76 88 setState("SUCCESS"); 89 + onSave?.(body); 77 90 setTimeout(() => setState(undefined), 1_000 * 8); 78 91 break; 79 92 } ··· 87 100 }) 88 101 .catch(() => { 89 102 setState("ERRORED"); 90 - setError("Error while fetching guilds"); 103 + setError("Error while updating"); 91 104 }); 92 105 93 106 }; ··· 97 110 <div className={cn("mt-8 mb-4 border-2 dark:border-wamellow border-wamellow-100 rounded-xl md:px-4 md:pb-4 px-2 py-2", (error || state === "ERRORED") && "outline outline-red-500 outline-1")}> 98 111 <div className="text-lg py-2 dark:text-neutral-700 text-neutral-300 font-medium px-2">{name}</div> 99 112 100 - {collapseable && 113 + {isCollapseable && 101 114 <div className={cn("md:mx-2 mx-1", open ? "lg:mb-0 mb-2" : "mb-2")}> 102 115 <button 103 116 className="dark:bg-wamellow hover:dark:bg-wamellow-light bg-wamellow-100 hover:bg-wamellow-100-light duration-200 cursor-pointer rounded-md dark:text-neutral-400 text-neutral-600 flex items-center h-12 px-3 w-full" ··· 122 135 <div className="md:m-1 relative"> 123 136 124 137 {children && 125 - <div className={`mx-1 ${collapseable && "mt-6"}`}> 138 + <div className={`mx-1 ${isCollapseable && "mt-6"}`}> 126 139 {children} 127 140 </div> 128 141 }
+6
lib/api.ts
··· 1 + export const cacheOptions = { 2 + cacheTime: 1000 * 60 * 5, 3 + refetchOnWindowFocus: false, 4 + refetchOnMount: false 5 + }; 6 + 1 7 export async function getData<T>(path: string) { 2 8 const response = await fetch(`${process.env.NEXT_PUBLIC_API}${path}`, { 3 9 headers: {