+4
-4
app/dashboard/[guildId]/custom-commands/page.tsx
+4
-4
app/dashboard/[guildId]/custom-commands/page.tsx
···
48
48
return params.toString();
49
49
}, [search]);
50
50
51
+
const setTagId = (id: string) => {
52
+
router.push(pathname + "?" + createQueryString("id", id));
53
+
};
54
+
51
55
useEffect(() => {
52
56
if (!Array.isArray(data)) return;
53
57
if (data && !tag && data[0]) setTagId(data[0].id);
···
69
73
}
70
74
71
75
if (isLoading || !data) return <></>;
72
-
73
-
const setTagId = (id: string) => {
74
-
router.push(pathname + "?" + createQueryString("id", id));
75
-
};
76
76
77
77
const editTag = <T extends keyof ApiV1GuildsModulesTagsGetResponse>(k: keyof ApiV1GuildsModulesTagsGetResponse, value: ApiV1GuildsModulesTagsGetResponse[T]) => {
78
78
if (!tag) return;
+3
-6
app/dashboard/[guildId]/layout.tsx
+3
-6
app/dashboard/[guildId]/layout.tsx
···
43
43
const cookies = useCookies();
44
44
const params = useParams();
45
45
46
-
const [error, setError] = useState<string>();
47
46
const [loaded, setLoaded] = useState<string[]>([]);
48
47
49
48
const guild = guildStore((g) => g);
···
88
87
}
89
88
);
90
89
91
-
useEffect(() => {
92
-
if (data && "message" in data) {
93
-
setError(data?.message);
94
-
return;
95
-
}
90
+
const error = data && "message" in data ? data.message : undefined;
96
91
92
+
useEffect(() => {
93
+
if (!data || "message" in data) return;
97
94
guildStore.setState(data);
98
95
}, [data]);
99
96
+30
-60
app/dashboard/[guildId]/starboard/hooks.ts
+30
-60
app/dashboard/[guildId]/starboard/hooks.ts
···
1
1
import { StarboardStyle } from "@/typings";
2
-
import { useEffect, useState } from "react";
2
+
import { useMemo } from "react";
3
3
4
4
interface Example {
5
5
avatar: string | null;
···
7
7
}
8
8
9
9
export function useExample(style: StarboardStyle) {
10
-
const [example, setExample] = useState<Example>({
11
-
avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png",
12
-
username: "@spacewolf."
13
-
});
14
-
15
-
useEffect(
16
-
() => {
17
-
switch (style) {
18
-
case StarboardStyle.Username:
19
-
setExample((e) => {
20
-
return {
21
-
...e,
22
-
avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png",
23
-
username: "@spacewolf."
24
-
};
25
-
});
26
-
break;
27
-
case StarboardStyle.GlobalName:
28
-
setExample((e) => {
29
-
return {
30
-
...e,
31
-
avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png",
32
-
username: "Space Wolf"
33
-
};
34
-
});
35
-
break;
36
-
case StarboardStyle.Nickname:
37
-
setExample((e) => {
38
-
return {
39
-
...e,
40
-
avatar: "https://cdn.waya.one/r/823554a71e92ca6192ab500d9b597a7f.png",
41
-
username: "Luna’s Grandpa <3"
42
-
};
43
-
});
44
-
break;
45
-
case StarboardStyle.NicknameAndGuildAvatar:
46
-
setExample((e) => {
47
-
return {
48
-
...e,
49
-
avatar: "https://cdn.waya.one/r/a_3a2fa421f079827d31f4fd1b7a9971ba.gif",
50
-
username: "Luna’s Grandpa <3"
51
-
};
52
-
});
53
-
break;
54
-
case StarboardStyle.Anonymous:
55
-
setExample((e) => {
56
-
return {
57
-
...e,
58
-
avatar: null,
59
-
username: null
60
-
};
61
-
});
62
-
break;
63
-
}
64
-
},
65
-
[style]
66
-
);
67
-
68
-
return example;
10
+
return useMemo<Example>(() => {
11
+
switch (style) {
12
+
case StarboardStyle.GlobalName:
13
+
return {
14
+
avatar: "/space.webp",
15
+
username: "Space Wolf"
16
+
};
17
+
case StarboardStyle.Nickname:
18
+
return {
19
+
avatar: "/space.webp",
20
+
username: "Luna’s Grandpa <3"
21
+
};
22
+
case StarboardStyle.NicknameAndGuildAvatar:
23
+
return {
24
+
avatar: "/shiggy.gif",
25
+
username: "Luna’s Grandpa <3"
26
+
};
27
+
case StarboardStyle.Anonymous:
28
+
return {
29
+
avatar: null,
30
+
username: null
31
+
};
32
+
default:
33
+
return {
34
+
avatar: "/space.webp",
35
+
username: "@spacewolf."
36
+
};
37
+
}
38
+
}, [style]);
69
39
}
+1
-1
app/dashboard/[guildId]/starboard/page.tsx
+1
-1
app/dashboard/[guildId]/starboard/page.tsx
+2
-5
components/inputs/dumb-color-input.tsx
+2
-5
components/inputs/dumb-color-input.tsx
···
1
1
import { cn } from "@/utils/cn";
2
2
import { AnimatePresence, motion } from "framer-motion";
3
-
import React, { useEffect, useState } from "react";
3
+
import React, { useState } from "react";
4
4
import { AiOutlineEdit } from "react-icons/ai";
5
5
6
6
interface Props {
···
37
37
);
38
38
39
39
// this cuz there can be multiple color inputs on the same page, so it will bug, so we need to identify them
40
-
const [inputId, setInputId] = useState<string>("");
41
-
useEffect(() => {
42
-
setInputId(Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15));
43
-
}, []);
40
+
const [inputId] = useState<string>(() => Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15));
44
41
45
42
const [isHovered, setIsHovered] = useState<boolean>(false);
46
43
+2
-6
components/inputs/dumb-text-input.tsx
+2
-6
components/inputs/dumb-text-input.tsx
···
1
1
import { cn } from "@/utils/cn";
2
-
import React, { useEffect, useState } from "react";
2
+
import React, { useMemo } from "react";
3
3
4
4
interface Props {
5
5
name?: string;
···
40
40
disabled && "cursor-not-allowed opacity-50"
41
41
);
42
42
43
-
const [length, setLength] = useState(0);
44
-
45
-
useEffect(() => {
46
-
setLength(dataName ? JSON.parse(value)[dataName]?.length : value?.length || 0);
47
-
}, [value]);
43
+
const length = useMemo(() => dataName ? JSON.parse(value)[dataName]?.length : value?.length || 0, [dataName, value]);
48
44
49
45
return (
50
46
<div className="relative select-none w-full max-w-full mb-3">
+29
-10
components/list.tsx
+29
-10
components/list.tsx
···
23
23
24
24
export function ListTab({ tabs, url, searchParamName, disabled }: ListProps) {
25
25
const [position, setPosition] = useState(0);
26
+
const [scrollMetrics, setScrollMetrics] = useState<{ canScroll: boolean; maxScroll: number; }>({ canScroll: false, maxScroll: 0 });
26
27
27
28
const path = usePathname();
28
29
const params = useSearchParams();
···
47
48
function scroll(direction: "left" | "right") {
48
49
if (!ref.current) return;
49
50
50
-
const scrollAmount = ref.current.clientWidth * 0.8; // Scroll 80% of the visible width
51
+
const scrollAmount = ref.current.clientWidth * 0.8;
51
52
52
53
ref.current.scrollBy({
53
54
top: 0,
···
62
63
setPosition(scrollLeft);
63
64
}
64
65
65
-
const isScrollable = ref.current ? ref.current.scrollWidth > ref.current.clientWidth : false;
66
+
useEffect(() => {
67
+
const element = ref.current;
68
+
if (!element) return;
69
+
70
+
const updateMetrics = () => {
71
+
const canScroll = element.scrollWidth > element.clientWidth;
72
+
const maxScroll = Math.max(element.scrollWidth - (element.clientWidth + 10), 0);
73
+
setScrollMetrics((prev) => {
74
+
if (prev.canScroll === canScroll && prev.maxScroll === maxScroll) return prev;
75
+
return { canScroll, maxScroll };
76
+
});
77
+
setPosition(element.scrollLeft);
78
+
};
79
+
80
+
const handleScroll = () => {
81
+
setScrollPosition();
82
+
updateMetrics();
83
+
};
66
84
67
-
useEffect(() => {
68
-
if (!ref.current) return;
85
+
const resizeObserver = new ResizeObserver(updateMetrics);
69
86
70
-
ref.current.addEventListener("scroll", setScrollPosition);
71
-
setScrollPosition();
87
+
element.addEventListener("scroll", handleScroll);
88
+
resizeObserver.observe(element);
89
+
updateMetrics();
72
90
73
91
return () => {
74
-
ref.current?.removeEventListener("scroll", setScrollPosition);
92
+
element.removeEventListener("scroll", handleScroll);
93
+
resizeObserver.disconnect();
75
94
};
76
95
}, []);
77
96
···
88
107
>
89
108
<TabsList
90
109
ref={ref}
91
-
className="bg-inherit border-b-2 border-wamellow p-0 w-full justify-start rounded-none overflow-y-auto overflow-x-auto scrollbar-hide"
110
+
className="bg-inherit border-b-2 border-wamellow p-0 w-full justify-start rounded-none overflow-y-auto overflow-x-auto scrollbar-none"
92
111
>
93
112
{tabs.map((tab) => (
94
113
<TabsTrigger
···
104
123
</TabsList>
105
124
</Tabs>
106
125
107
-
{isScrollable && position > 0 && (
126
+
{scrollMetrics.canScroll && position > 0 && (
108
127
<Button
109
128
className="absolute bottom-2 left-0 backdrop-blur-lg"
110
129
onClick={() => scroll("left")}
···
114
133
</Button>
115
134
)}
116
135
117
-
{isScrollable && ref.current && position < (ref.current.scrollWidth - (ref.current.clientWidth + 10)) && (
136
+
{scrollMetrics.canScroll && position < scrollMetrics.maxScroll && (
118
137
<Button
119
138
className="absolute bottom-2 right-0 backdrop-blur-lg"
120
139
onClick={() => scroll("right")}
+18
-9
components/modal.tsx
+18
-9
components/modal.tsx
···
3
3
import type { ApiError } from "@/typings";
4
4
import { cn } from "@/utils/cn";
5
5
import Link from "next/link";
6
-
import { useEffect, useState } from "react";
6
+
import { useState } from "react";
7
7
import { HiFire } from "react-icons/hi";
8
8
9
9
import Notice, { NoticeType } from "./notice";
···
56
56
const [state, setState] = useState<State>(State.Idle);
57
57
const [error, setError] = useState<string | null>(null);
58
58
59
-
useEffect(() => {
59
+
const reset = () => {
60
60
setError(null);
61
61
setState(State.Idle);
62
-
}, [isOpen]);
62
+
};
63
+
64
+
const handleClose = () => {
65
+
reset();
66
+
onClose();
67
+
};
63
68
64
69
async function submit() {
65
70
if (state === State.Loading) return;
66
71
if (!onSubmit) {
67
-
onClose();
72
+
handleClose();
68
73
return;
69
74
}
70
75
71
-
setError(null);
76
+
reset();
72
77
setState(State.Loading);
73
78
const data = onSubmit?.();
74
79
75
80
if (!data) {
76
-
onClose();
81
+
handleClose();
77
82
return;
78
83
}
79
84
···
88
93
setState(State.Idle);
89
94
90
95
if (res.ok) {
91
-
onClose();
96
+
handleClose();
92
97
onSuccess?.(res.status === 204 ? null : await res.json());
93
98
return;
94
99
}
···
100
105
return (
101
106
<Dialog
102
107
open={isOpen}
103
-
onOpenChange={(open) => !open && onClose()}
108
+
onOpenChange={(open) => {
109
+
if (!open) {
110
+
handleClose();
111
+
}
112
+
}}
104
113
>
105
114
<DialogContent>
106
115
<DialogHeader>
···
130
139
{onSubmit && (
131
140
<Button
132
141
variant="link"
133
-
onClick={() => state !== State.Loading && onClose()}
142
+
onClick={() => state !== State.Loading && handleClose()}
134
143
className="hidden md:block ml-auto text-sm font-medium"
135
144
disabled={state !== State.Idle}
136
145
>
+8
-5
components/ui/input-base.tsx
+8
-5
components/ui/input-base.tsx
···
49
49
}: InputBaseProps) {
50
50
const [focused, setFocused] = React.useState(false);
51
51
const controlRef = React.useRef<HTMLElement>(null);
52
+
const handleClick = React.useCallback<React.MouseEventHandler<HTMLDivElement>>((event) => {
53
+
onClick?.(event);
54
+
if (event.defaultPrevented) return;
55
+
if (controlRef.current && event.currentTarget === event.target) {
56
+
controlRef.current.focus();
57
+
}
58
+
}, [onClick]);
52
59
53
60
return (
54
61
<InputBaseContext.Provider
···
61
68
>
62
69
<Primitive.div
63
70
data-slot="input-base"
64
-
onClick={composeEventHandlers(onClick, (event) => {
65
-
if (controlRef.current && event.currentTarget === event.target) {
66
-
controlRef.current.focus();
67
-
}
68
-
})}
71
+
onClick={handleClick}
69
72
className={cn(
70
73
"border-input dark:bg-input/30 flex min-h-9 cursor-text items-center gap-2 rounded-lg border bg-transparent px-3 py-1 text-base shadow-2xs transition-[color,box-shadow] outline-hidden md:text-sm",
71
74
disabled && "pointer-events-none cursor-not-allowed opacity-50",