diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e79fbb2..e08068a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,15 +13,19 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Use Node.js 18 + - name: Use Node.js 20 uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Install dependencies run: | npm install + - name: Run Linter + run: | + npm run lint:check + - name: Build project run: | npm run build \ No newline at end of file diff --git a/app/(admin)/admin/beatmaps/[id]/page.tsx b/app/(admin)/admin/beatmaps/[id]/page.tsx index 902a05f..e2f23ce 100644 --- a/app/(admin)/admin/beatmaps/[id]/page.tsx +++ b/app/(admin)/admin/beatmaps/[id]/page.tsx @@ -1,11 +1,12 @@ "use client"; +import { Music2 } from "lucide-react"; +import Image from "next/image"; import { use } from "react"; + +import PrettyHeader from "@/components/General/PrettyHeader"; +import RoundedContent from "@/components/General/RoundedContent"; import Spinner from "@/components/Spinner"; import { useBeatmap } from "@/lib/hooks/api/beatmap/useBeatmap"; -import RoundedContent from "@/components/General/RoundedContent"; -import Image from "next/image"; -import PrettyHeader from "@/components/General/PrettyHeader"; -import { Music2 } from "lucide-react"; import { tryParseNumber } from "@/lib/utils/type.util"; interface BeatmapsProps { @@ -25,9 +26,9 @@ export default function BeatmapsRedirect(props: BeatmapsProps) { const errorMessage = beatmapQuery.error?.message ?? "Beatmapset not found"; return ( -
+
} /> - +

{errorMessage}

@@ -48,9 +49,9 @@ export default function BeatmapsRedirect(props: BeatmapsProps) { } return ( -

+
} /> -
+
diff --git a/app/(admin)/admin/beatmaps/requests/layout.tsx b/app/(admin)/admin/beatmaps/requests/layout.tsx index 4595325..660353f 100644 --- a/app/(admin)/admin/beatmaps/requests/layout.tsx +++ b/app/(admin)/admin/beatmaps/requests/layout.tsx @@ -1,4 +1,5 @@ -import { Metadata } from "next"; +import type { Metadata } from "next"; + import Page from "./page"; export const metadata: Metadata = { diff --git a/app/(admin)/admin/beatmaps/requests/page.tsx b/app/(admin)/admin/beatmaps/requests/page.tsx index 7e96d8e..c5fa105 100644 --- a/app/(admin)/admin/beatmaps/requests/page.tsx +++ b/app/(admin)/admin/beatmaps/requests/page.tsx @@ -1,48 +1,43 @@ "use client"; -import type React from "react"; - +import { ChevronDown, ChevronsUp } from "lucide-react"; +import type * as React from "react"; import { useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { Search, Filter, ChevronDown, Rocket, ChevronsUp } from "lucide-react"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { BeatmapsSearchFilters } from "@/components/Beatmaps/Search/BeatmapsSearchFilters"; -import { BeatmapSetCard } from "@/components/Beatmaps/BeatmapSetCard"; -import { twMerge } from "tailwind-merge"; import BeatmapSetOverview from "@/app/(website)/user/[id]/components/BeatmapSetOverview"; -import useDebounce from "@/lib/hooks/useDebounce"; +import { BeatmapSetCard } from "@/components/Beatmaps/BeatmapSetCard"; +import PrettyHeader from "@/components/General/PrettyHeader"; +import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useBeatmapSetGetHypedSets } from "@/lib/hooks/api/beatmap/useBeatmapSetHypedSets"; -import PrettyHeader from "@/components/General/PrettyHeader"; export default function Page() { const [viewMode, setViewMode] = useState("grid"); const { data, setSize, size, isLoading } = useBeatmapSetGetHypedSets(); - const beatmapsets = data?.flatMap((item) => item.sets); - const totalCount = - data?.find((item) => item.total_count !== undefined)?.total_count ?? 0; + const beatmapsets = data?.flatMap(item => item.sets); + const totalCount + = data?.find(item => item.total_count !== undefined)?.total_count ?? 0; - const isLoadingMore = - isLoading || (size > 0 && data && typeof data[size - 1] === "undefined"); + const isLoadingMore + = isLoading || (size > 0 && data && data[size - 1] === undefined); const handleShowMore = () => { setSize(size + 1); }; return ( -
+
} text="Beatmap requests" roundBottom={true} />
-
-
+
+
Total requests: {totalCount}
@@ -52,7 +47,7 @@ export default function Page() { onValueChange={setViewMode} className="h-9 " > - + Grid List @@ -64,26 +59,26 @@ export default function Page() {
- {beatmapsets?.map((beatmapSet, i) => ( - + {beatmapsets?.map(beatmapSet => ( + ))}
- - {beatmapsets?.map((beatmapSet, i) => ( - + + {beatmapsets?.map(beatmapSet => ( + ))}
{beatmapsets && beatmapsets?.length < totalCount && ( -
+
{beatmapSet ? ( <> -
-
-
+
+
+

{beatmapSet.title}

-

{beatmapSet.artist}

+

{beatmapSet.artist}

@@ -78,10 +80,10 @@ export default function AdminBeatmapset(props: BeatmapsetProps) { alt="" width={48} height={48} - className="rounded-lg object-contain bg-stone-800 max-h-12 max-w-12" + className="max-h-12 max-w-12 rounded-lg bg-stone-800 object-contain" fallBackSrc="/images/placeholder.png" /> -
+
submitted by 

@@ -95,20 +97,20 @@ export default function AdminBeatmapset(props: BeatmapsetProps) { className="font-bold" />

- {beatmapSet.ranked_date && - beatmapSet.status === BeatmapStatusWeb.RANKED && ( -
- ranked on  - -
- )} + {beatmapSet.ranked_date + && beatmapSet.status === BeatmapStatusWeb.RANKED && ( +
+ ranked on  + +
+ )}
-
+
-
+
{/* Cards */} -
+
Beatmap Status @@ -169,24 +171,26 @@ export default function AdminBeatmapset(props: BeatmapsetProps) {
- ) : beatmapsetQuery?.error ? ( - -
-

{errorMessage}

-

- The beatmapset you are looking for does not exist or has been - deleted. -

-
- 404 -
- ) : null} + ) : beatmapsetQuery?.error + ? ( + +
+

{errorMessage}

+

+ The beatmapset you are looking for does not exist or has been + deleted. +

+
+ 404 +
+ ) + : null}
); diff --git a/app/(admin)/admin/beatmapsets/components/BeatmapPerformanceTooltip.tsx b/app/(admin)/admin/beatmapsets/components/BeatmapPerformanceTooltip.tsx index c540565..5e83806 100644 --- a/app/(admin)/admin/beatmapsets/components/BeatmapPerformanceTooltip.tsx +++ b/app/(admin)/admin/beatmapsets/components/BeatmapPerformanceTooltip.tsx @@ -1,16 +1,26 @@ +import { useCallback, useEffect, useState } from "react"; + import { Tooltip } from "@/components/Tooltip"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { EosIconsThreeDotsLoading } from "@/components/ui/icons/three-dots-loading"; import { ShortenedMods } from "@/lib/hooks/api/score/types"; import fetcher from "@/lib/services/fetcher"; -import { +import type { BeatmapResponse, + PerformanceAttributes, +} from "@/lib/types/api"; +import { GameMode, Mods, - PerformanceAttributes, } from "@/lib/types/api"; -import { useEffect, useState } from "react"; + +const performance = [ + Mods.NONE, + Mods.HIDDEN, + Mods.HARD_ROCK, + Mods.DOUBLE_TIME, +]; export function BeatmapPerformanceTooltip({ beatmap, @@ -19,59 +29,55 @@ export function BeatmapPerformanceTooltip({ }) { const gamerule = [Mods.NONE]; - if (beatmap.mode != GameMode.MANIA && !gamerule.includes(Mods.RELAX)) { + if (beatmap.mode !== GameMode.MANIA && !gamerule.includes(Mods.RELAX)) { gamerule.push(Mods.RELAX); } - if (beatmap.mode == GameMode.STANDARD && !gamerule.includes(Mods.RELAX2)) { + if (beatmap.mode === GameMode.STANDARD && !gamerule.includes(Mods.RELAX2)) { gamerule.push(Mods.RELAX2); } - const performance = [ - Mods.NONE, - Mods.HIDDEN, - Mods.HARD_ROCK, - Mods.DOUBLE_TIME, - ]; - const [currentGamerule, setCurrentGamerule] = useState(Mods.NONE); const [beatmapPerformances, setBeatmapPerformances] = useState< PerformanceAttributes[] >([]); - const fetchData = async (force?: boolean) => { - if (beatmapPerformances.length > 0 && !force) return; + const fetchData = useCallback(async (force?: boolean) => { + if (beatmapPerformances.length > 0 && !force) + return; const results = await Promise.all( performance.map((acc) => { return fetcher( - `beatmap/${beatmap.id}/pp?mods=${acc}&mods=${currentGamerule}` + `beatmap/${beatmap.id}/pp?mods=${acc}&mods=${currentGamerule}`, ); - }) + }), ); setBeatmapPerformances(results); - }; + }, [beatmap.id, beatmapPerformances.length, currentGamerule]); useEffect(() => { - if (beatmapPerformances.length == 0) return; + if (beatmapPerformances.length === 0) + return; fetchData(true); - }, [currentGamerule]); + }, [beatmapPerformances.length, currentGamerule, fetchData]); return ( -
+ content={( +
+
{beatmapPerformances.length > 0 ? ( beatmapPerformances.map((pp, i) => { return ( {ShortenedMods[performance[i]]} {100}% @@ -82,27 +88,27 @@ export function BeatmapPerformanceTooltip({ ); }) ) : ( - + )}
- {gamerule.map((rule, i) => ( + {gamerule.map(rule => ( ))}
- } + )} > PP diff --git a/app/(admin)/admin/beatmapsets/components/BeatmapSetEvent.tsx b/app/(admin)/admin/beatmapsets/components/BeatmapSetEvent.tsx index 40f9080..16aa058 100644 --- a/app/(admin)/admin/beatmapsets/components/BeatmapSetEvent.tsx +++ b/app/(admin)/admin/beatmapsets/components/BeatmapSetEvent.tsx @@ -1,8 +1,10 @@ +import { Clock } from "lucide-react"; + import { BeatmapNominatorUser } from "@/app/(website)/beatmapsets/components/BeatmapNominatorUser"; import PrettyDate from "@/components/General/PrettyDate"; import { SmallBeatmapElement } from "@/components/SmallBeatmapElement"; -import { BeatmapEventResponse, BeatmapEventType } from "@/lib/types/api"; -import { Clock } from "lucide-react"; +import type { BeatmapEventResponse } from "@/lib/types/api"; +import { BeatmapEventType } from "@/lib/types/api"; export function BeatmapSetEvent({ event }: { event: BeatmapEventResponse }) { const { @@ -29,33 +31,34 @@ export function BeatmapSetEvent({ event }: { event: BeatmapEventResponse }) { eventMessage = ( <> reset beatmapset - hypes + + {" "} + hypes ); break; case BeatmapEventType.BEATMAP_STATUS_CHANGED: - const beatmap = beatmapset.beatmaps.find((b) => b.hash === beatmap_hash); eventMessage = ( <> changed beatmap - + b.hash === beatmap_hash)} /> status to - + {new_status ?? "their default status"} ); break; default: - eventMessage = - "created unhandled event. Please report it to the developer"; + eventMessage + = "created unhandled event. Please report it to the developer"; break; } return ( -
-
-
+
+
+

ID: {event_id}

User @@ -63,8 +66,8 @@ export function BeatmapSetEvent({ event }: { event: BeatmapEventResponse }) { {eventMessage}.

-
- +
+
diff --git a/app/(admin)/admin/beatmapsets/components/BeatmapSetEvents.tsx b/app/(admin)/admin/beatmapsets/components/BeatmapSetEvents.tsx index 9f5b3a9..468e75b 100644 --- a/app/(admin)/admin/beatmapsets/components/BeatmapSetEvents.tsx +++ b/app/(admin)/admin/beatmapsets/components/BeatmapSetEvents.tsx @@ -1,8 +1,9 @@ +import { ChevronDown } from "lucide-react"; + import { BeatmapSetEvent } from "@/app/(admin)/admin/beatmapsets/components/BeatmapSetEvent"; import { Button } from "@/components/ui/button"; import { useBeatmapSetEvents } from "@/lib/hooks/api/beatmap/useBeatmapSetEvents"; -import { BeatmapSetResponse } from "@/lib/types/api"; -import { ChevronDown } from "lucide-react"; +import type { BeatmapSetResponse } from "@/lib/types/api"; export function BeatmapSetEvents({ beatmapSet, @@ -11,15 +12,15 @@ export function BeatmapSetEvents({ }) { const { data, setSize, size, isLoading } = useBeatmapSetEvents( beatmapSet.id, - 5 + 5, ); - const events = data?.flatMap((item) => item.events); - const totalCount = - data?.find((item) => item.total_count !== undefined)?.total_count ?? 0; + const events = data?.flatMap(item => item.events); + const totalCount + = data?.find(item => item.total_count !== undefined)?.total_count ?? 0; - const isLoadingMore = - isLoading || (size > 0 && data && typeof data[size - 1] === "undefined"); + const isLoadingMore + = isLoading || (size > 0 && data && data[size - 1] === undefined); const handleShowMore = () => { setSize(size + 1); @@ -27,14 +28,14 @@ export function BeatmapSetEvents({ return (
- {events?.map((event, i) => { - return ; + {events?.map((event) => { + return ; })} {events && events?.length < totalCount && ( -
+
@@ -130,8 +130,8 @@ export function BeatmapsStatusTable({ - selectedBeatmaps.includes(beatmap.id.toString()) + checked={beatmapSet.beatmaps.every(beatmap => + selectedBeatmaps.includes(beatmap.id.toString()), )} onCheckedChange={handleSelectAll} aria-label="Select all" @@ -159,8 +159,8 @@ export function BeatmapsStatusTable({ {beatmapSet.beatmaps .sort( (a, b) => - getBeatmapStarRating(b, b.mode) - - getBeatmapStarRating(a, a.mode) + getBeatmapStarRating(b, b.mode) + - getBeatmapStarRating(a, a.mode), ) .sort((a, b) => a.mode_int - b.mode_int) .map((beatmap) => { @@ -179,23 +179,22 @@ export function BeatmapsStatusTable({ - handleSelectBeatmap(beatmap.id.toString()) - } + handleSelectBeatmap(beatmap.id.toString())} /> {beatmap.id} - + {beatmap.hash.slice(0, 7)} -
-
+
+
{`${beatmap.creator}'s @@ -254,17 +253,15 @@ export function BeatmapsStatusTable({
- handleUpdateBeatmapStatus(beatmap.id, s) - } + onValueChange={s => + handleUpdateBeatmapStatus(beatmap.id, s)} /> {beatmap.beatmap_nominator_user && ( diff --git a/app/(admin)/admin/dashboard/components/serverStatusCards.tsx b/app/(admin)/admin/dashboard/components/serverStatusCards.tsx index 51440c6..da0a0c3 100644 --- a/app/(admin)/admin/dashboard/components/serverStatusCards.tsx +++ b/app/(admin)/admin/dashboard/components/serverStatusCards.tsx @@ -1,7 +1,8 @@ -import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"; +import { Activity, AlertCircle, BarChart3, Users } from "lucide-react"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { useServerStatus } from "@/lib/hooks/api/useServerStatus"; -import { Users, Activity, AlertCircle, BarChart3 } from "lucide-react"; export function ServerStatusCards() { const serverStatusQuery = useServerStatus(); @@ -12,7 +13,7 @@ export function ServerStatusCards() { Total Users - + {serverStatus ? ( @@ -27,7 +28,7 @@ export function ServerStatusCards() { Users Online - + {serverStatus ? ( @@ -37,8 +38,8 @@ export function ServerStatusCards() {

{( - (serverStatus.users_online / serverStatus.total_users) * - 100 + (serverStatus.users_online / serverStatus.total_users) + * 100 ).toFixed(2)} % of total users

@@ -51,7 +52,7 @@ export function ServerStatusCards() { Restrictions - + {serverStatus ? ( @@ -61,9 +62,9 @@ export function ServerStatusCards() {

{( - ((serverStatus.total_restrictions || 0) / - serverStatus.total_users) * - 100 + ((serverStatus.total_restrictions || 0) + / serverStatus.total_users) + * 100 ).toFixed(2)} % of users

@@ -76,7 +77,7 @@ export function ServerStatusCards() { Total Scores - + {serverStatus ? ( diff --git a/app/(admin)/admin/dashboard/layout.tsx b/app/(admin)/admin/dashboard/layout.tsx index d5213b5..74cad4a 100644 --- a/app/(admin)/admin/dashboard/layout.tsx +++ b/app/(admin)/admin/dashboard/layout.tsx @@ -1,4 +1,5 @@ -import { Metadata } from "next"; +import type { Metadata } from "next"; + import Page from "./page"; export const metadata: Metadata = { diff --git a/app/(admin)/admin/dashboard/page.tsx b/app/(admin)/admin/dashboard/page.tsx index 2e93ec8..871ed21 100644 --- a/app/(admin)/admin/dashboard/page.tsx +++ b/app/(admin)/admin/dashboard/page.tsx @@ -4,10 +4,10 @@ import { BeatmapSetsEvents } from "@/app/(admin)/admin/beatmapsets/components/Be import { ServerStatusCards } from "@/app/(admin)/admin/dashboard/components/serverStatusCards"; import { Card, - CardHeader, - CardTitle, CardContent, CardDescription, + CardHeader, + CardTitle, } from "@/components/ui/card"; import { UserListItem } from "@/components/UserListElement"; import { WorkInProgress } from "@/components/WorkInProgress"; @@ -28,11 +28,11 @@ export default function Page() { Latest registered users - {serverStatus?.recent_users && - serverStatus.recent_users.map((user, i) => ( + {serverStatus?.recent_users + && serverStatus.recent_users.map(user => ( ))} @@ -47,7 +47,7 @@ export default function Page() { - + Recent Beatmap Status Events Latests changes with beatmaps diff --git a/app/(admin)/admin/users/[id]/edit/components/AdminUserBasicInfo.tsx b/app/(admin)/admin/users/[id]/edit/components/AdminUserBasicInfo.tsx index bcf758c..f9686db 100644 --- a/app/(admin)/admin/users/[id]/edit/components/AdminUserBasicInfo.tsx +++ b/app/(admin)/admin/users/[id]/edit/components/AdminUserBasicInfo.tsx @@ -1,18 +1,17 @@ "use client"; -import { UserSensitiveResponse } from "@/lib/types/api"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Label } from "@/components/ui/label"; -import { Mail, User, KeyRound } from "lucide-react"; - -import { useUserPreviousUsernames } from "@/lib/hooks/api/user/useUserPreviousUsernames"; -import { Badge } from "@/components/ui/badge"; +import { Mail, User } from "lucide-react"; -import AdminUserRestrictButton from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserRestrictButton"; import AdminUserEmailInput from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserEmailInput"; -import AdminUserUsernameInput from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserUsernameInput"; -import AdminUserResetPassword from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserResetPassword"; import AdminUserPrivilegeInput from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserPrivilegeInput"; +import AdminUserResetPassword from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserResetPassword"; +import AdminUserRestrictButton from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserRestrictButton"; +import AdminUserUsernameInput from "@/app/(admin)/admin/users/[id]/edit/components/AdminUserUsernameInput"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { useUserPreviousUsernames } from "@/lib/hooks/api/user/useUserPreviousUsernames"; +import type { UserSensitiveResponse } from "@/lib/types/api"; export default function AdminUserBasicInfo({ user, @@ -25,14 +24,14 @@ export default function AdminUserBasicInfo({ - + Basic Information
@@ -40,7 +39,7 @@ export default function AdminUserBasicInfo({
@@ -55,7 +54,8 @@ export default function AdminUserBasicInfo({
{previousUsernames.usernames.map((username, index) => ( - + // eslint-disable-next-line @eslint-react/no-array-index-key -- would actually prefer to use index here + {username} ))} diff --git a/app/(admin)/admin/users/[id]/edit/components/AdminUserConnections.tsx b/app/(admin)/admin/users/[id]/edit/components/AdminUserConnections.tsx index 8dbf361..bb4e237 100644 --- a/app/(admin)/admin/users/[id]/edit/components/AdminUserConnections.tsx +++ b/app/(admin)/admin/users/[id]/edit/components/AdminUserConnections.tsx @@ -1,16 +1,17 @@ "use client"; +import { UserPlus, Users } from "lucide-react"; import { useState } from "react"; -import { UserSensitiveResponse } from "@/lib/types/api"; + +import Spinner from "@/components/Spinner"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Users, UserPlus } from "lucide-react"; import { UserListItem } from "@/components/UserListElement"; -import Spinner from "@/components/Spinner"; import { useAdminUserFollowers, useAdminUserFriends, } from "@/lib/hooks/api/user/useAdminUserEdit"; +import type { UserSensitiveResponse } from "@/lib/types/api"; export default function AdminUserConnections({ user, @@ -19,11 +20,11 @@ export default function AdminUserConnections({ }) { const [activeTab, setActiveTab] = useState("followers"); - const { data: followersData, isLoading: isLoadingFollowers } = - useAdminUserFollowers(user.user_id, 100, 1); + const { data: followersData, isLoading: isLoadingFollowers } + = useAdminUserFollowers(user.user_id, 100, 1); - const { data: followingData, isLoading: isLoadingFollowing } = - useAdminUserFriends(user.user_id, 100, 1); + const { data: followingData, isLoading: isLoadingFollowing } + = useAdminUserFriends(user.user_id, 100, 1); // TODO: Pagination for followers/following lists @@ -31,7 +32,7 @@ export default function AdminUserConnections({ - + Connections @@ -39,11 +40,11 @@ export default function AdminUserConnections({ - + Followers - + Following @@ -53,28 +54,35 @@ export default function AdminUserConnections({
- ) : followersData && followersData.followers.length > 0 ? ( - <> -
- {followersData.followers.map((follower) => ( - - ))} -
-

- Showing {followersData?.followers.length || 0}/ - {followersData?.total_count || 0} followers -

- - ) : ( -
- -

No followers yet

-
- )} + ) : followersData && followersData.followers.length > 0 + ? ( + <> +
+ {followersData.followers.map(follower => ( + + ))} +
+

+ Showing + {" "} + {followersData?.followers.length || 0} + / + {followersData?.total_count || 0} + {" "} + followers +

+ + ) + : ( +
+ +

No followers yet

+
+ )} @@ -82,28 +90,35 @@ export default function AdminUserConnections({
- ) : followingData && followingData.friends.length > 0 ? ( - <> -
- {followingData.friends.map((friend) => ( - - ))} -
-

- Showing {followingData?.friends.length || 0}/ - {followingData?.total_count || 0} following -

- - ) : ( -
- -

Not following anyone yet

-
- )} + ) : followingData && followingData.friends.length > 0 + ? ( + <> +
+ {followingData.friends.map(friend => ( + + ))} +
+

+ Showing + {" "} + {followingData?.friends.length || 0} + / + {followingData?.total_count || 0} + {" "} + following +

+ + ) + : ( +
+ +

Not following anyone yet

+
+ )}
diff --git a/app/(admin)/admin/users/[id]/edit/components/AdminUserEmailInput.tsx b/app/(admin)/admin/users/[id]/edit/components/AdminUserEmailInput.tsx index 9c992c5..51e1591 100644 --- a/app/(admin)/admin/users/[id]/edit/components/AdminUserEmailInput.tsx +++ b/app/(admin)/admin/users/[id]/edit/components/AdminUserEmailInput.tsx @@ -1,10 +1,9 @@ -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { UserSensitiveResponse } from "@/lib/types/api"; -import { Mail, EyeOff, Eye } from "lucide-react"; +import { Eye, EyeOff } from "lucide-react"; import { useState } from "react"; -import { Label } from "recharts"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import type { UserSensitiveResponse } from "@/lib/types/api"; export default function AdminUserEmailInput({ user, @@ -30,9 +29,9 @@ export default function AdminUserEmailInput({ onClick={() => setShowEmail(!showEmail)} > {showEmail ? ( - + ) : ( - + )}
diff --git a/app/(admin)/admin/users/[id]/edit/components/AdminUserImages.tsx b/app/(admin)/admin/users/[id]/edit/components/AdminUserImages.tsx index 37774c7..0e81ce2 100644 --- a/app/(admin)/admin/users/[id]/edit/components/AdminUserImages.tsx +++ b/app/(admin)/admin/users/[id]/edit/components/AdminUserImages.tsx @@ -1,17 +1,18 @@ "use client"; -import { useEffect, useState } from "react"; -import { UserSensitiveResponse } from "@/lib/types/api"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Image as ImageIcon, Upload } from "lucide-react"; +import { useEffect, useState } from "react"; + import ImageSelect from "@/components/General/ImageSelect"; import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useToast } from "@/hooks/use-toast"; import { useAdminUploadAvatar, useAdminUploadBanner, } from "@/lib/hooks/api/user/useAdminUserEdit"; -import { useToast } from "@/hooks/use-toast"; +import type { UserSensitiveResponse } from "@/lib/types/api"; export default function AdminUserImages({ user, @@ -23,14 +24,14 @@ export default function AdminUserImages({ const [activeTab, setActiveTab] = useState("avatar"); - const { trigger: uploadAvatar, isMutating: isUploadingAvatar } = - useAdminUploadAvatar(user.user_id); - const { trigger: uploadBanner, isMutating: isUploadingBanner } = - useAdminUploadBanner(user.user_id); + const { trigger: uploadAvatar, isMutating: isUploadingAvatar } + = useAdminUploadAvatar(user.user_id); + const { trigger: uploadBanner, isMutating: isUploadingBanner } + = useAdminUploadBanner(user.user_id); const { toast } = useToast(); useEffect(() => { - const fetches: Promise[] = []; + const fetches: Array> = []; let isCancelled = false; if (avatarFile == null) { @@ -39,7 +40,8 @@ export default function AdminUserImages({ fetches.push( fetch(avatarUrl) .then(async (res) => { - if (isCancelled) return; + if (isCancelled) + return; const file = await res.blob(); if (!isCancelled) { setAvatarFile(new File([file], "file.png")); @@ -49,7 +51,7 @@ export default function AdminUserImages({ if (!isCancelled) { console.error("Failed to fetch avatar:", error); } - }) + }), ); } @@ -59,7 +61,8 @@ export default function AdminUserImages({ fetches.push( fetch(bannerUrl) .then(async (res) => { - if (isCancelled) return; + if (isCancelled) + return; const file = await res.blob(); if (!isCancelled) { setBannerFile(new File([file], "file.png")); @@ -69,7 +72,7 @@ export default function AdminUserImages({ if (!isCancelled) { console.error("Failed to fetch banner:", error); } - }) + }), ); } @@ -82,7 +85,8 @@ export default function AdminUserImages({ const handleUpload = async (type: "avatar" | "banner") => { const file = type === "avatar" ? avatarFile : bannerFile; - if (!file) return; + if (!file) + return; try { if (type === "avatar") { @@ -91,14 +95,16 @@ export default function AdminUserImages({ title: "Avatar uploaded successfully!", variant: "success", }); - } else { + } + else { await uploadBanner(file); toast({ title: "Banner uploaded successfully!", variant: "success", }); } - } catch (error: any) { + } + catch (error: any) { toast({ title: `Failed to upload ${type}`, description: error?.message || "An error occurred", @@ -111,7 +117,7 @@ export default function AdminUserImages({ - + Images @@ -122,7 +128,7 @@ export default function AdminUserImages({ Banner - + - + Upload Avatar

@@ -143,7 +149,7 @@ export default function AdminUserImages({

- + - + Upload Banner

diff --git a/app/(admin)/admin/users/[id]/edit/components/AdminUserPrivilegeInput.tsx b/app/(admin)/admin/users/[id]/edit/components/AdminUserPrivilegeInput.tsx index 7b076d6..5f08df0 100644 --- a/app/(admin)/admin/users/[id]/edit/components/AdminUserPrivilegeInput.tsx +++ b/app/(admin)/admin/users/[id]/edit/components/AdminUserPrivilegeInput.tsx @@ -1,13 +1,15 @@ "use client"; -import { useState, useEffect } from "react"; -import { UserSensitiveResponse, UserPrivilege } from "@/lib/types/api"; -import { Button } from "@/components/ui/button"; import { Shield } from "lucide-react"; +import { useEffect, useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; import { MultiSelect } from "@/components/ui/multi-select"; -import { useAdminEditPrivilege } from "@/lib/hooks/api/user/useAdminUserEdit"; import { useToast } from "@/hooks/use-toast"; -import { Label } from "@/components/ui/label"; +import { useAdminEditPrivilege } from "@/lib/hooks/api/user/useAdminUserEdit"; +import type { UserSensitiveResponse } from "@/lib/types/api"; +import { UserPrivilege } from "@/lib/types/api"; const PRIVILEGE_OPTIONS = [ { @@ -38,12 +40,12 @@ export default function AdminUserPrivilegeInput({ user: UserSensitiveResponse; }) { const [selectedPrivileges, setSelectedPrivileges] = useState( - user.privilege + user.privilege, ); const [error, setError] = useState(null); - const { trigger: editPrivilege, isMutating: isUpdatingPrivilege } = - useAdminEditPrivilege(user.user_id); + const { trigger: editPrivilege, isMutating: isUpdatingPrivilege } + = useAdminEditPrivilege(user.user_id); const { toast } = useToast(); useEffect(() => { @@ -63,7 +65,8 @@ export default function AdminUserPrivilegeInput({ description: `Updated privileges for ${user.username}.`, variant: "success", }); - } catch (err: any) { + } + catch (err: any) { const errorMessage = err.message ?? "Unknown error."; setError(errorMessage); toast({ @@ -76,24 +79,23 @@ export default function AdminUserPrivilegeInput({ const currentPrivileges = user.privilege; - const hasChanges = - selectedPrivileges.length !== currentPrivileges.length || - !selectedPrivileges.every((p) => currentPrivileges.includes(p)); + const hasChanges + = selectedPrivileges.length !== currentPrivileges.length + || !selectedPrivileges.every(p => currentPrivileges.includes(p)); return (

- setSelectedPrivileges(values as UserPrivilege[]) - } + setSelectedPrivileges(values as UserPrivilege[])} defaultValue={Object.values(user.privilege).filter( - (v) => v != UserPrivilege.USER + v => v !== UserPrivilege.USER, )} placeholder="Select privileges..." className="flex-1" @@ -107,10 +109,12 @@ export default function AdminUserPrivilegeInput({
{error &&

{error}

} - {selectedPrivileges.filter((p) => p !== UserPrivilege.USER).length > - 0 && ( + {selectedPrivileges.some(p => p !== UserPrivilege.USER) && (

- Selected {selectedPrivileges.length}{" "} + Selected + {" "} + {selectedPrivileges.length} + {" "} {selectedPrivileges.length === 1 ? "privilege" : "privileges"}

)} diff --git a/app/(admin)/admin/users/[id]/edit/components/AdminUserProfile.tsx b/app/(admin)/admin/users/[id]/edit/components/AdminUserProfile.tsx index cef8577..48a312e 100644 --- a/app/(admin)/admin/users/[id]/edit/components/AdminUserProfile.tsx +++ b/app/(admin)/admin/users/[id]/edit/components/AdminUserProfile.tsx @@ -1,16 +1,17 @@ "use client"; +import { FileText, Flag, Gamepad2, Globe } from "lucide-react"; import { useState } from "react"; -import { UserSensitiveResponse } from "@/lib/types/api"; + +import ChangeCountryInput from "@/app/(website)/settings/components/ChangeCountryInput"; +import ChangeDescriptionInput from "@/app/(website)/settings/components/ChangeDescriptionInput"; +import ChangePlaystyleForm from "@/app/(website)/settings/components/ChangePlaystyleForm"; +import ChangeSocialsForm from "@/app/(website)/settings/components/ChangeSocialsForm"; +import Spinner from "@/components/Spinner"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { FileText, Flag, Globe, Gamepad2 } from "lucide-react"; import { useUserMetadata } from "@/lib/hooks/api/user/useUserMetadata"; -import Spinner from "@/components/Spinner"; -import ChangeCountryInput from "@/app/(website)/settings/components/ChangeCountryInput"; -import ChangeSocialsForm from "@/app/(website)/settings/components/ChangeSocialsForm"; -import ChangePlaystyleForm from "@/app/(website)/settings/components/ChangePlaystyleForm"; -import ChangeDescriptionInput from "@/app/(website)/settings/components/ChangeDescriptionInput"; +import type { UserSensitiveResponse } from "@/lib/types/api"; export default function AdminUserProfile({ user, @@ -24,7 +25,7 @@ export default function AdminUserProfile({ - + Profile Information @@ -32,32 +33,32 @@ export default function AdminUserProfile({ - + About - + Country - + Socials - + Playstyle - + - + - + {userMetadata && user ? ( - + {userMetadata && user ? ( data.password === data.confirmPassword, { + .refine(data => data.password === data.confirmPassword, { message: "Passwords do not match", path: ["confirmPassword"], }); @@ -62,8 +61,8 @@ export default function AdminUserResetPassword({ const [showPasswordDialog, setShowPasswordDialog] = useState(false); const [error, setError] = useState(null); - const { trigger: changePassword, isMutating: isChangingPassword } = - useAdminPasswordChange(user.user_id); + const { trigger: changePassword, isMutating: isChangingPassword } + = useAdminPasswordChange(user.user_id); const { toast } = useToast(); const form = useForm>({ @@ -104,7 +103,7 @@ export default function AdminUserResetPassword({ variant: "destructive", }); }, - } + }, ); } @@ -112,7 +111,7 @@ export default function AdminUserResetPassword({ @@ -120,7 +119,8 @@ export default function AdminUserResetPassword({ Reset Password - Set a new password for{" "} + Set a new password for + {" "} {user.username} @@ -137,7 +137,7 @@ export default function AdminUserResetPassword({ New Password Confirm Password - + Unrestrict User @@ -72,7 +74,7 @@ export default function AdminUserRestrictButton({ className="w-full" disabled={isChangingRestriction} > - + Restrict User @@ -100,14 +102,15 @@ function DialogSelectRestrictionReason({ onOpenChange={setShowRestrictionDialog} > {children} - e.preventDefault()}> + e.preventDefault()}> - + Restrict User - Select a reason for restricting{" "} + Select a reason for restricting + {" "} {user.username} @@ -125,7 +128,7 @@ function DialogSelectRestrictionReason({ - {[...RESTRICTION_REASONS, "Other"].map((reason) => ( + {[...RESTRICTION_REASONS, "Other"].map(reason => ( {reason} @@ -138,12 +141,11 @@ function DialogSelectRestrictionReason({
+ /> -
+
{isMobile ? ( -
{formattors}
+
{formattors}
) : ( formattors )} diff --git a/components/BBCode/BBCodePreset.tsx b/components/BBCode/BBCodePreset.tsx index e6b3f3e..dcd6a46 100644 --- a/components/BBCode/BBCodePreset.tsx +++ b/components/BBCode/BBCodePreset.tsx @@ -34,25 +34,25 @@ export const allowedTags = [ "td", ]; -export const customBBCodePreset = htmlPreset.extend((tags) => ({ +export const customBBCodePreset = htmlPreset.extend(tags => ({ ...tags, br: () => ({ tag: "br" }), hr: () => ({ tag: "hr" }), - code: (node) => ({ + code: node => ({ tag: "pre", attrs: { className: "p-2 bg-card rounded", }, content: node.content, }), - c: (node) => ({ + c: node => ({ tag: "code", attrs: { className: "p-1 bg-card rounded", }, content: node.content, }), - spoiler: (node) => ({ + spoiler: node => ({ tag: "span", attrs: { className: "bg-black text-black", @@ -74,12 +74,12 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ }; }, imagemap: (node, { render }) => { - const lines = - !!node.content && Object.keys(node.content).length > 0 + const lines + = !!node.content && Object.keys(node.content).length > 0 ? Object.values(node.content) .join(" ") .split("[break]") - .filter((line) => line.trim() !== "") + .filter(line => line.trim() !== "") : []; const imageUrl = lines[0].trim(); @@ -98,7 +98,7 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ style: `position:absolute;display:block;left:${x}%;top:${y}%;width:${width}%;height:${height}%;`, ...(title ? { - title: title, + title, } : {}), }, @@ -128,7 +128,7 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ ], }; }, - centre: (node) => ({ + centre: node => ({ tag: "center", content: node.content, }), @@ -136,7 +136,8 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ const isOrdered = !!node.attrs && Object.keys(node.attrs).length > 0; const normalizeContent = (content: any | any[] | undefined): any[] => { - if (content === undefined || content === null) return []; + if (content === undefined || content === null) + return []; return Array.isArray(content) ? content : [content]; }; @@ -154,7 +155,8 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ result.push(current); current = []; } - } else if (current != null) { + } + else if (current != null) { current.push(item); } } @@ -169,7 +171,7 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ const contentArray = normalizeContent(node.content); const splitItems = splitContentByMarker(contentArray); - const items = splitItems.map((itemContent) => ({ + const items = splitItems.map(itemContent => ({ tag: "li", content: itemContent, })); @@ -181,14 +183,14 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ content: items, }; }, - notice: (node) => ({ + notice: node => ({ tag: "div", attrs: { class: "well", }, content: node.content, }), - quote: (node) => ({ + quote: node => ({ tag: "blockquote", content: [ { @@ -196,7 +198,7 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ attrs: { className: "text-base font-bold" }, content: !!node.attrs && Object.keys(node.attrs).length > 0 - ? Object.keys(node.attrs).join(" ") + " wrote" + ? `${Object.keys(node.attrs).join(" ")} wrote` : "", }, { @@ -205,7 +207,7 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ }, ], }), - heading: (node) => ({ + heading: node => ({ tag: "h2", attrs: { className: "text-2xl font-bold" }, content: node.content, @@ -218,13 +220,13 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ node, !!node.attrs && Object.keys(node.attrs).length > 0 ? Object.keys(node.attrs).join(" ") - : "" + : "", ); }, - profile: (node) => ({ + profile: node => ({ tag: "div", attrs: { - class: "user-name js-usercard", + "class": "user-name js-usercard", "data-user-id": !!node.attrs && Object.keys(node.attrs).length > 0 ? Object.keys(node.attrs).join(" ") @@ -232,7 +234,7 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ }, content: node.content, }), - youtube: (node) => ({ + youtube: node => ({ tag: "iframe", attrs: { width: "560", @@ -246,11 +248,11 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ }, }), size: (node) => { - let sizeValue = parseInt( + let sizeValue = Number.parseInt( !!node.attrs && Object.keys(node.attrs).length > 0 ? Object.keys(node.attrs).join(" ") : "", - 10 + 10, ); const minSize = 30; @@ -292,7 +294,7 @@ export const customBBCodePreset = htmlPreset.extend((tags) => ({ }, })); -const createSpoilerBox = (node: any, label: string) => { +function createSpoilerBox(node: any, label: string) { return { tag: "div", attrs: { @@ -327,4 +329,4 @@ const createSpoilerBox = (node: any, label: string) => { }, ], }; -}; +} diff --git a/components/BBCode/BBCodeReactParser.tsx b/components/BBCode/BBCodeReactParser.tsx index 022e0c4..a7077bb 100644 --- a/components/BBCode/BBCodeReactParser.tsx +++ b/components/BBCode/BBCodeReactParser.tsx @@ -1,3 +1,10 @@ +import { ChevronRight } from "lucide-react"; +import Link from "next/link"; +import { NextIntlClientProvider, useLocale, useMessages } from "next-intl"; +import * as React from "react"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import { createRoot } from "react-dom/client"; + import { Popover, PopoverContent, @@ -5,15 +12,8 @@ import { } from "@/components/ui/popover"; import UserHoverCard from "@/components/UserHoverCard"; import fetcher from "@/lib/services/fetcher"; -import { UserResponse } from "@/lib/types/api"; +import type { UserResponse } from "@/lib/types/api"; import { tryParseNumber } from "@/lib/utils/type.util"; -import { ChevronRight } from "lucide-react"; -import Link from "next/link"; -import React, { useEffect, useState } from "react"; -import { useLayoutEffect, useRef } from "react"; -import { createRoot } from "react-dom/client"; -import { NextIntlClientProvider } from "next-intl"; -import { useLocale, useMessages } from "next-intl"; export const BBCodeReactParser = React.memo( ({ textHtml }: { textHtml: string }) => { @@ -21,9 +21,20 @@ export const BBCodeReactParser = React.memo( const locale = useLocale(); const messages = useMessages(); + const parseHtml = React.useCallback((container: HTMLDivElement) => { + parseSpoilerBoxes(container); + parseBlockquote(container); + parseLinks(container); + parseWell(container); + parseBreaks(container); + parseImageMapLink(container); + parseProfileLink(container, locale, messages); + }, [locale, messages]); + useLayoutEffect(() => { const container = containerRef.current; - if (!container) return; + if (!container) + return; const observer = new MutationObserver(() => { parseHtml(container); @@ -34,28 +45,18 @@ export const BBCodeReactParser = React.memo( parseHtml(container); return () => observer.disconnect(); - }, []); - - const parseHtml = (container: HTMLDivElement) => { - parseSpoilerBoxes(container); - parseBlockquote(container); - parseLinks(container); - parseWell(container); - parseBreaks(container); - parseImageMapLink(container); - parseProfileLink(container, locale, messages); - }; + }, [parseHtml]); return (
); - } + }, ); function parseLinks(container: HTMLDivElement) { @@ -67,40 +68,42 @@ function parseLinks(container: HTMLDivElement) { function walk(node: Node) { if ( - node.nodeType === Node.ELEMENT_NODE && - (node as Element).tagName.toLowerCase() === "a" + node.nodeType === Node.ELEMENT_NODE + && (node as Element).tagName.toLowerCase() === "a" ) { return; } if (node.nodeType === Node.TEXT_NODE) { const text = node.textContent; - if (!text || !urlRegex.test(text)) return; + if (!text || !urlRegex.test(text)) + return; const frag = document.createDocumentFragment(); let lastIndex = 0; - text.replace(urlRegex, (match, _url, offset) => { - frag.appendChild( - document.createTextNode(text.slice(lastIndex, offset)) + text.replaceAll(urlRegex, (match, _url, offset) => { + frag.append( + document.createTextNode(text.slice(lastIndex, offset)), ); const a = document.createElement("a"); a.href = match; a.className = "text-primary hover:underline"; a.textContent = match; - frag.appendChild(a); + frag.append(a); lastIndex = offset + match.length; return match; }); if (lastIndex < text.length) { - frag.appendChild(document.createTextNode(text.slice(lastIndex))); + frag.append(document.createTextNode(text.slice(lastIndex))); } node.parentNode?.replaceChild(frag, node); - } else if (node.nodeType === Node.ELEMENT_NODE) { + } + else if (node.nodeType === Node.ELEMENT_NODE) { Array.from(node.childNodes).forEach(walk); } } @@ -119,16 +122,16 @@ function parseBreaks(container: HTMLDivElement) { function parseBlockquote(container: HTMLDivElement) { const quotes = container.querySelectorAll("blockquote"); quotes.forEach((quote) => { - quote.className = - "px-4 my-2 border-l-4 border-gray-300 dark:border-gray-500"; + quote.className + = "px-4 my-2 border-l-4 border-gray-300 dark:border-gray-500"; }); } function parseWell(container: HTMLDivElement) { const quotes = container.querySelectorAll(".well"); quotes.forEach((quote) => { - quote.className = - "p-4 bg-card border-2 rounded border-gray-300 dark:border-gray-500"; + quote.className + = "p-4 bg-card border-2 rounded border-gray-300 dark:border-gray-500"; }); } @@ -137,23 +140,24 @@ function parseSpoilerBoxes(container: HTMLDivElement) { spoilerboxes.forEach((box) => { const body = box.querySelector( - ".js-spoilerbox__body" + ".js-spoilerbox__body", ) as HTMLElement | null; const link = box.querySelector( - ".js-spoilerbox__link" + ".js-spoilerbox__link", ) as HTMLElement | null; const icon = link?.querySelector( - ".bbcode-spoilerbox__link-icon" + ".bbcode-spoilerbox__link-icon", ) as HTMLElement | null; - if (!body || !link || !icon) return; + if (!body || !link || !icon) + return; const mountPoint = document.createElement("span"); mountPoint.className = "inline-block transition-transform"; icon.insertBefore(mountPoint, icon.firstChild); createRoot(mountPoint).render( - + , ); body.style.paddingLeft = "20px"; @@ -164,8 +168,8 @@ function parseSpoilerBoxes(container: HTMLDivElement) { e.preventDefault(); const isOpen = body.style.display === "block"; body.style.display = isOpen ? "none" : "block"; - mountPoint.style.transform = - body.style.display === "block" ? "rotate(90deg)" : "rotate(0deg)"; + mountPoint.style.transform + = body.style.display === "block" ? "rotate(90deg)" : "rotate(0deg)"; }; link.addEventListener("click", handleClick); @@ -180,7 +184,8 @@ function parseImageMapLink(container: HTMLDivElement) { const imageMapLinks = container.querySelectorAll(".imagemap__link"); imageMapLinks.forEach((link) => { - if (!link) return; + if (!link) + return; link.className = ""; @@ -195,7 +200,7 @@ function parseImageMapLink(container: HTMLDivElement) { triggerRef.current.innerHTML = ""; triggerRef.current.setAttribute( "style", - link.getAttribute("style") || "" + link.getAttribute("style") || "", ); } }, []); @@ -219,12 +224,13 @@ function parseImageMapLink(container: HTMLDivElement) { function parseProfileLink( container: HTMLDivElement, locale: string, - messages: Record + messages: Record, ) { const profileLinks = container.querySelectorAll(".js-usercard"); profileLinks.forEach((link) => { - if (!link) return; + if (!link) + return; link.className = ""; @@ -237,7 +243,9 @@ function parseProfileLink( const [user, setUser] = useState(null); + // eslint-disable-next-line unicorn/prefer-dom-node-dataset -- intentional const userId = tryParseNumber(link.getAttribute("data-user-id")) + // eslint-disable-next-line unicorn/prefer-dom-node-dataset -- intentional ? Number(link.getAttribute("data-user-id")) : null; @@ -252,12 +260,13 @@ function parseProfileLink( triggerRef.current.innerHTML = ""; triggerRef.current.setAttribute( "style", - link.getAttribute("style") || "" + link.getAttribute("style") || "", ); } - }, []); + }, [userId]); - if (!user) return null; + if (!user) + return null; return ( diff --git a/components/BBCode/BBCodeTextField.tsx b/components/BBCode/BBCodeTextField.tsx index 34ecac9..2ee2ac5 100644 --- a/components/BBCode/BBCodeTextField.tsx +++ b/components/BBCode/BBCodeTextField.tsx @@ -1,9 +1,10 @@ import bbobHTML from "@bbob/html"; -import { BBCodeReactParser } from "@/components/BBCode/BBCodeReactParser"; + import { allowedTags, customBBCodePreset, } from "@/components/BBCode/BBCodePreset"; +import { BBCodeReactParser } from "@/components/BBCode/BBCodeReactParser"; export default function BBCodeTextField({ text }: { text: string }) { const textWithCustomBreaks = text @@ -12,7 +13,7 @@ export default function BBCodeTextField({ text }: { text: string }) { const htmlString = bbobHTML(textWithCustomBreaks, customBBCodePreset(), { onlyAllowTags: allowedTags, - }).replace(/className/g, "class"); + }).replaceAll("className", "class"); return ; } diff --git a/components/BeatmapDifficultyBadge.tsx b/components/BeatmapDifficultyBadge.tsx index 1a9dc66..6453f70 100644 --- a/components/BeatmapDifficultyBadge.tsx +++ b/components/BeatmapDifficultyBadge.tsx @@ -1,7 +1,7 @@ import DifficultyIcon from "@/components/DifficultyIcon"; import { Tooltip } from "@/components/Tooltip"; import { Badge } from "@/components/ui/badge"; -import { BeatmapResponse } from "@/lib/types/api"; +import type { BeatmapResponse } from "@/lib/types/api"; import { getBeatmapStarRating } from "@/lib/utils/getBeatmapStarRating"; interface BeatmapDifficultyBadgeProps { @@ -16,31 +16,32 @@ export default function BeatmapDifficultyBadge({ return ( + content={( +
<> - ★{getBeatmapStarRating(beatmap)} + ★ + {getBeatmapStarRating(beatmap)} -

{beatmap.version}

+

{beatmap.version}

- } + )} > -
+
{!iconPreview && ( -

{beatmap.version}

+

{beatmap.version}

)}
diff --git a/components/BeatmapStatus.tsx b/components/BeatmapStatus.tsx index 90f642a..c1858c0 100644 --- a/components/BeatmapStatus.tsx +++ b/components/BeatmapStatus.tsx @@ -1,4 +1,3 @@ -import { BeatmapStatusWeb } from "@/lib/types/api"; import { Check, ChevronsUp, @@ -9,11 +8,13 @@ import { } from "lucide-react"; import { twMerge } from "tailwind-merge"; +import { BeatmapStatusWeb } from "@/lib/types/api"; + interface BeatmapStatusIconProps { status: BeatmapStatusWeb; } -export const getBeatmapStatusStatusColor = (status: BeatmapStatusWeb) => { +export function getBeatmapStatusStatusColor(status: BeatmapStatusWeb) { switch (status) { case BeatmapStatusWeb.LOVED: return "pink-500"; @@ -33,7 +34,7 @@ export const getBeatmapStatusStatusColor = (status: BeatmapStatusWeb) => { default: return ""; } -}; +} export default function BeatmapStatusIcon({ status }: BeatmapStatusIconProps) { const color = `text-${getBeatmapStatusStatusColor(status)}`; diff --git a/components/BeatmapStatusBadge.tsx b/components/BeatmapStatusBadge.tsx index 37e2aa3..c672dec 100644 --- a/components/BeatmapStatusBadge.tsx +++ b/components/BeatmapStatusBadge.tsx @@ -1,8 +1,8 @@ +import { twMerge } from "tailwind-merge"; + import { getBeatmapStatusStatusColor } from "@/components/BeatmapStatus"; import { Badge } from "@/components/ui/badge"; -import { BeatmapStatusWeb } from "@/lib/types/api"; - -import { twMerge } from "tailwind-merge"; +import type { BeatmapStatusWeb } from "@/lib/types/api"; interface BeatmapStatusBadgeProps { status: BeatmapStatusWeb; @@ -18,7 +18,7 @@ export default function BeatmapStatusBadge({ variant="outline" className={twMerge( "font-normal", - `bg-${color}/20 text-${color} hover:bg-${color}/20 hover:text-${color}` + `bg-${color}/20 text-${color} hover:bg-${color}/20 hover:text-${color}`, )} > {status} diff --git a/components/Beatmaps/BeatmapSetCard.tsx b/components/Beatmaps/BeatmapSetCard.tsx index c5825e2..957e849 100644 --- a/components/Beatmaps/BeatmapSetCard.tsx +++ b/components/Beatmaps/BeatmapSetCard.tsx @@ -1,22 +1,22 @@ -import { Card, CardContent, CardFooter } from "@/components/ui/card"; -import { Button } from "@/components/ui/button"; -import { Download, ExternalLink } from "lucide-react"; +import { ExternalLink } from "lucide-react"; import Link from "next/link"; -import BeatmapStatusBadge from "@/components/BeatmapStatusBadge"; -import { BeatmapSetResponse } from "@/lib/types/api"; +import { usePathname } from "next/navigation"; +import { useEffect, useState } from "react"; +import { twMerge } from "tailwind-merge"; + +import AudioPreview from "@/app/(website)/user/[id]/components/AudioPreview"; import BeatmapDifficultyBadge from "@/components/BeatmapDifficultyBadge"; +import BeatmapStatusIcon from "@/components/BeatmapStatus"; import { CollapsibleBadgeList } from "@/components/CollapsibleBadgeList"; -import AudioPreview from "@/app/(website)/user/[id]/components/AudioPreview"; +import PrettyDate from "@/components/General/PrettyDate"; import ProgressBar from "@/components/ProgressBar"; -import useAudioPlayer from "@/lib/hooks/useAudioPlayer"; -import { twMerge } from "tailwind-merge"; -import { useState, useEffect } from "react"; -import { getBeatmapStarRating } from "@/lib/utils/getBeatmapStarRating"; import { BanchoSmallUserElement } from "@/components/SmallUserElement"; -import BeatmapStatusIcon from "@/components/BeatmapStatus"; -import PrettyDate from "@/components/General/PrettyDate"; -import { usePathname } from "next/navigation"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardFooter } from "@/components/ui/card"; +import useAudioPlayer from "@/lib/hooks/useAudioPlayer"; import { useT } from "@/lib/i18n/utils"; +import type { BeatmapSetResponse } from "@/lib/types/api"; +import { getBeatmapStarRating } from "@/lib/utils/getBeatmapStarRating"; interface BeatmapSetCardProps { beatmapSet: BeatmapSetResponse; @@ -26,49 +26,48 @@ export function BeatmapSetCard({ beatmapSet }: BeatmapSetCardProps) { const t = useT("components.beatmapSetCard"); const pathname = usePathname(); - const { player, isPlaying, isPlayingThis, currentTimestamp } = - useAudioPlayer(); + const { player, isPlaying, isPlayingThis, currentTimestamp } + = useAudioPlayer(); const [isPlayingCurrent, setIsPlayingCurrent] = useState(false); useEffect(() => { - if (!player.current) return; + if (!player.current) + return; setIsPlayingCurrent(isPlayingThis(`${beatmapSet.id}.mp3`)); - }, [isPlaying]); + }, [beatmapSet.id, isPlaying, isPlayingThis, player]); return ( - +
-
+
-
+

{beatmapSet.title}

-

+

{beatmapSet.artist}

@@ -77,29 +76,29 @@ export function BeatmapSetCard({ beatmapSet }: BeatmapSetCardProps) { maxValue={player.current?.duration || 10} className={twMerge( "absolute bottom-0 left-0 w-full h-0.5", - !isPlayingCurrent || - !isPlaying || - currentTimestamp === player.current?.duration || - currentTimestamp === 0 + !isPlayingCurrent + || !isPlaying + || currentTimestamp === player.current?.duration + || currentTimestamp === 0 ? "hidden" - : undefined + : undefined, )} />
- +
getBeatmapStarRating(a) - getBeatmapStarRating(b)) .sort((a, b) => a.mode_int - b.mode_int) - .map((beatmap, i) => ( - + .map(beatmap => ( + ))} />

-
+

{t("submittedBy")}

{t("submittedOn")}

@@ -118,12 +117,10 @@ export function BeatmapSetCard({ beatmapSet }: BeatmapSetCardProps) { diff --git a/components/Beatmaps/Search/BeatmapsSearch.tsx b/components/Beatmaps/Search/BeatmapsSearch.tsx index d8dfb9c..d3e3c38 100644 --- a/components/Beatmaps/Search/BeatmapsSearch.tsx +++ b/components/Beatmaps/Search/BeatmapsSearch.tsx @@ -1,22 +1,22 @@ "use client"; -import type React from "react"; +import { ChevronDown, Filter, Search } from "lucide-react"; +import type * as React from "react"; +import { useCallback, useState } from "react"; +import { twMerge } from "tailwind-merge"; -import { useState, useCallback } from "react"; -import { Input } from "@/components/ui/input"; +import BeatmapSetOverview from "@/app/(website)/user/[id]/components/BeatmapSetOverview"; +import { BeatmapSetCard } from "@/components/Beatmaps/BeatmapSetCard"; +import { BeatmapsSearchFilters } from "@/components/Beatmaps/Search/BeatmapsSearchFilters"; import { Button } from "@/components/ui/button"; -import { Search, Filter, ChevronDown } from "lucide-react"; +import { Card, CardContent } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { BeatmapStatusWeb, GameMode } from "@/lib/types/api"; - -import { BeatmapsSearchFilters } from "@/components/Beatmaps/Search/BeatmapsSearchFilters"; import { useBeatmapsetSearch } from "@/lib/hooks/api/beatmap/useBeatmapsetSearch"; -import { BeatmapSetCard } from "@/components/Beatmaps/BeatmapSetCard"; -import { twMerge } from "tailwind-merge"; -import BeatmapSetOverview from "@/app/(website)/user/[id]/components/BeatmapSetOverview"; import useDebounce from "@/lib/hooks/useDebounce"; -import { Card, CardContent } from "@/components/ui/card"; import { useT } from "@/lib/i18n/utils"; +import type { GameMode } from "@/lib/types/api"; +import { BeatmapStatusWeb } from "@/lib/types/api"; export default function BeatmapsSearch({ forceThreeGridCols = false, @@ -48,13 +48,13 @@ export default function BeatmapsSearch({ refreshInterval: 0, revalidateOnFocus: false, keepPreviousData: true, - } + }, ); - const beatmapsets = data?.flatMap((item) => item.sets); + const beatmapsets = data?.flatMap(item => item.sets); - const isLoadingMore = - isLoading || (size > 0 && data && typeof data[size - 1] === "undefined"); + const isLoadingMore + = isLoading || (size > 0 && data && data[size - 1] === undefined); const handleShowMore = useCallback(() => { setSize(size + 1); @@ -66,13 +66,13 @@ export default function BeatmapsSearch({ status: BeatmapStatusWeb[] | null; searchByCustomStatus: boolean; }) => { - let { mode, status, searchByCustomStatus } = filters; + const { mode, status, searchByCustomStatus } = filters; setModeFilter(mode); setStatusFilter(status ?? null); setSearchByCustomStatus(searchByCustomStatus); }, - [] + [], ); return ( @@ -80,14 +80,14 @@ export default function BeatmapsSearch({
- + setSearchQuery(e.target.value)} + onChange={e => setSearchQuery(e.target.value)} />
@@ -113,7 +113,7 @@ export default function BeatmapsSearch({ onValueChange={setViewMode} className="h-9 " > - + {t("viewMode.grid")} {t("viewMode.list")} @@ -128,8 +128,8 @@ export default function BeatmapsSearch({
- {beatmapsets?.map((beatmapSet, i) => ( - + {beatmapsets?.map(beatmapSet => ( + ))}
- - {beatmapsets?.map((beatmapSet, i) => ( - + + {beatmapsets?.map(beatmapSet => ( + ))} - {beatmapsets && - beatmapsets?.length >= (searchByCustomStatus ? 12 : 24) && ( -
- -
- )} + {beatmapsets + && beatmapsets?.length >= (searchByCustomStatus ? 12 : 24) && ( +
+ +
+ )}
); diff --git a/components/Beatmaps/Search/BeatmapsSearchFilters.tsx b/components/Beatmaps/Search/BeatmapsSearchFilters.tsx index d55e355..d8f2c92 100644 --- a/components/Beatmaps/Search/BeatmapsSearchFilters.tsx +++ b/components/Beatmaps/Search/BeatmapsSearchFilters.tsx @@ -1,5 +1,7 @@ "use client"; +import { useCallback, useState } from "react"; + import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { MultiSelect } from "@/components/ui/multi-select"; @@ -11,12 +13,11 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; -import { BeatmapStatusWeb, GameMode } from "@/lib/types/api"; -import { useState, useCallback, useMemo } from "react"; import { useT } from "@/lib/i18n/utils"; +import { BeatmapStatusWeb, GameMode } from "@/lib/types/api"; const beatmapSearchStatusList = Object.values(BeatmapStatusWeb) - .filter((v) => v != BeatmapStatusWeb.UNKNOWN) + .filter(v => v !== BeatmapStatusWeb.UNKNOWN) .map((v) => { return { value: v, @@ -45,7 +46,7 @@ export function BeatmapsSearchFilters({ const t = useT("pages.beatmaps.components.filters"); const [mode, setMode] = useState(defaultMode); const [status, setStatus] = useState( - defaultStatus + defaultStatus, ); const [searchByCustomStatus, setSearchByCustomStatus] = useState(false); @@ -65,13 +66,13 @@ export function BeatmapsSearchFilters({ { const file = e.target.files?.[0]; - if (!file) return; + if (!file) + return; if (maxFileSizeBytes && file.size > maxFileSizeBytes) { toast({ title: t("imageTooBig"), @@ -48,20 +50,21 @@ export default function ImageSelect({ }); return; } - if (e.target.files) setFile(file); + if (e.target.files) + setFile(file); }} /> {file ? (
-
+
avatar
diff --git a/components/General/PrettyCounter.tsx b/components/General/PrettyCounter.tsx index bd1f429..0fe114e 100644 --- a/components/General/PrettyCounter.tsx +++ b/components/General/PrettyCounter.tsx @@ -6,20 +6,18 @@ interface Props { duration?: number; } -export default function PrettyCounter({ value, duration }: Props) { +export default function PrettyCounter({ value, duration = 1300 }: Props) { const [count, setCount] = useState(0); - if (duration === undefined) { - duration = 1300; - } - useEffect(() => { let startTime = null as null | number; - const easeOut: (t: number) => number = (t) => --t * t * t + 1; + // eslint-disable-next-line no-param-reassign -- intentional + const easeOut: (t: number) => number = t => --t * t * t + 1; const step = (timestamp: number) => { - if (!startTime) startTime = timestamp; + if (!startTime) + startTime = timestamp; const progress = timestamp - startTime; const percentage = Math.min(progress / duration!, 1); diff --git a/components/General/PrettyDate.tsx b/components/General/PrettyDate.tsx index ec5ef85..7d2c946 100644 --- a/components/General/PrettyDate.tsx +++ b/components/General/PrettyDate.tsx @@ -1,7 +1,8 @@ "use client"; -import { Tooltip } from "@/components/Tooltip"; import Cookies from "js-cookie"; +import { Tooltip } from "@/components/Tooltip"; + interface PrettyDateProps { time: string | Date; className?: string; @@ -23,15 +24,17 @@ export default function PrettyDate({ const locale = Cookies.get("locale") || "en"; - return withTime ? ( - -
- {date.toLocaleDateString(locale, options)} -
-
- ) : ( -
{date.toLocaleDateString(locale, options)}
- ); + return withTime + ? ( + +
+ {date.toLocaleDateString(locale, options)} +
+
+ ) + : ( +
{date.toLocaleDateString(locale, options)}
+ ); } export function dateToPrettyString(time: string | Date) { diff --git a/components/General/PrettyHeader.tsx b/components/General/PrettyHeader.tsx index 622a9e0..036e944 100644 --- a/components/General/PrettyHeader.tsx +++ b/components/General/PrettyHeader.tsx @@ -23,19 +23,19 @@ export default function PrettyHeader({ `bg-card rounded-t-lg p-4 flex items-center border shadow`, children ? "place-content-between" : "", roundBottom ? "rounded-b-lg" : "", - className + className, )} >
- {icon &&
{icon}
} + {icon &&
{icon}
}

{text}

{counter && ( -
+
{counter}
)} diff --git a/components/General/RoundedContent.tsx b/components/General/RoundedContent.tsx index 2c3ae21..a32adb8 100644 --- a/components/General/RoundedContent.tsx +++ b/components/General/RoundedContent.tsx @@ -13,7 +13,7 @@ export default function RoundedContent({
{children} diff --git a/components/Header/Header.tsx b/components/Header/Header.tsx index 2a1e656..3868cf1 100644 --- a/components/Header/Header.tsx +++ b/components/Header/Header.tsx @@ -1,13 +1,15 @@ "use client"; +import Link from "next/link"; import { useEffect, useState } from "react"; -import HeaderLink from "@/components/Header/HeaderLink"; - import { twMerge } from "tailwind-merge"; -import { ThemeModeToggle } from "@/components/Header/ThemeModeToggle"; -import HeaderSearchCommand from "@/components/Header/HeaderSearchCommand"; -import HeaderMobileDrawer from "@/components/Header/HeaderMobileDrawer"; + +import { Brand } from "@/components/Brand"; import HeaderAvatar from "@/components/Header/HeaderAvatar"; -import Link from "next/link"; +import HeaderLink from "@/components/Header/HeaderLink"; +import HeaderMobileDrawer from "@/components/Header/HeaderMobileDrawer"; +import HeaderSearchCommand from "@/components/Header/HeaderSearchCommand"; +import { LanguageSelector } from "@/components/Header/LanguageSelector"; +import { ThemeModeToggle } from "@/components/Header/ThemeModeToggle"; import { DropdownMenu, DropdownMenuContent, @@ -15,9 +17,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Brand } from "@/components/Brand"; import { useT } from "@/lib/i18n/utils"; -import { LanguageSelector } from "@/components/Header/LanguageSelector"; export default function Header() { const t = useT("components.header"); @@ -43,7 +43,7 @@ export default function Header() {
@@ -51,7 +51,7 @@ export default function Header() { -
+
@@ -63,11 +63,11 @@ export default function Header() { - {t("links.wiki")} + {t("links.wiki")} - {t("links.rules")} + {t("links.rules")} @@ -86,8 +86,8 @@ export default function Header() { )} - {(process.env.NEXT_PUBLIC_KOFI_LINK || - process.env.NEXT_PUBLIC_BOOSTY_LINK) && ( + {(process.env.NEXT_PUBLIC_KOFI_LINK + || process.env.NEXT_PUBLIC_BOOSTY_LINK) && ( {t("links.supportUs")} @@ -98,14 +98,14 @@ export default function Header() {
-
+
-
+
diff --git a/components/Header/HeaderAvatar.tsx b/components/Header/HeaderAvatar.tsx index ba62a77..8d10d0b 100644 --- a/components/Header/HeaderAvatar.tsx +++ b/components/Header/HeaderAvatar.tsx @@ -1,30 +1,33 @@ "use client"; -import useSelf from "@/lib/hooks/useSelf"; -import HeaderUserDropdown from "@/components/Header/HeaderUserDropdown"; +import Image from "next/image"; +import { Suspense } from "react"; + import HeaderLoginDialog from "@/components/Header/HeaderLoginDialog"; +import HeaderUserDropdown from "@/components/Header/HeaderUserDropdown"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import { Suspense } from "react"; -import Image from "next/image"; import { Button } from "@/components/ui/button"; +import useSelf from "@/lib/hooks/useSelf"; export default function HeaderAvatar() { const { self } = useSelf(); - return self ? ( - - - - ) : ( - - ); + return self + ? ( + + + + ) + : ( + + ); } diff --git a/components/Header/HeaderLink.tsx b/components/Header/HeaderLink.tsx index e18e6ba..532e323 100644 --- a/components/Header/HeaderLink.tsx +++ b/components/Header/HeaderLink.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import React from "react"; +import * as React from "react"; interface Props { name: string; @@ -14,27 +14,27 @@ export default function HeaderLink({ name, href }: Props) { const isActive = pathname === href; const Wrapper = href ? Link : React.Fragment; - const wrapperProps = href ? { href: href } : {}; + const wrapperProps = href ? { href } : {}; return (
- {/* @ts-ignore */} + {/* @ts-expect-error -- We hangle props the same way as Wrapper object */}

{name} {/* Active indicator */} diff --git a/components/Header/HeaderLoginDialog.tsx b/components/Header/HeaderLoginDialog.tsx index f2f16f9..3724aea 100644 --- a/components/Header/HeaderLoginDialog.tsx +++ b/components/Header/HeaderLoginDialog.tsx @@ -1,24 +1,25 @@ "use client"; -import React, { useContext, useState, useMemo } from "react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import Cookies from "js-cookie"; +import { useRouter } from "next/navigation"; +import * as React from "react"; +import { useContext, useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { MobileDrawerContext } from "@/components/Header/HeaderMobileDrawer"; import { Button } from "@/components/ui/button"; import { - DialogHeader, - DialogFooter, Dialog, + DialogClose, DialogContent, DialogDescription, + DialogFooter, + DialogHeader, DialogTitle, DialogTrigger, - DialogClose, } from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import useSelf from "@/lib/hooks/useSelf"; -import useRestriction from "@/lib/hooks/useRestriction"; -import { useAuthorize } from "@/lib/hooks/api/auth/useAuthorize"; - -import Cookies from "js-cookie"; import { Form, FormControl, @@ -27,13 +28,12 @@ import { FormLabel, FormMessage, } from "@/components/ui/form"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useToast } from "@/hooks/use-toast"; +import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; -import { useRouter } from "next/navigation"; -import { MobileDrawerContext } from "@/components/Header/HeaderMobileDrawer"; +import { useToast } from "@/hooks/use-toast"; +import { useAuthorize } from "@/lib/hooks/api/auth/useAuthorize"; +import useRestriction from "@/lib/hooks/useRestriction"; +import useSelf from "@/lib/hooks/useSelf"; import { useT } from "@/lib/i18n/utils"; export default function HeaderLoginDialog() { @@ -70,7 +70,7 @@ export default function HeaderLoginDialog() { message: t("validation.passwordMaxLength"), }), }), - [t] + [t], ); const form = useForm>({ @@ -116,7 +116,7 @@ export default function HeaderLoginDialog() { setError(errorMessage || "Unknown error"); }, - } + }, ); } @@ -169,6 +169,7 @@ export default function HeaderLoginDialog() { @@ -164,14 +167,14 @@ export default function HeaderSearchCommand() { {userSearchQuery.isLoading && !userSearch ? ( -

+
) : ( - searchQuery != "" && - userSearch?.map((result, index) => ( + searchQuery !== "" + && userSearch?.map(result => ( openPage(`/user/${result.user_id}`)} > @@ -182,14 +185,14 @@ export default function HeaderSearchCommand() { {beatmapsetSearchQuery.isLoading && !beatmapsetSearch ? ( -
+
) : ( - searchQuery != "" && - beatmapsetSearch?.map((result, index) => ( + searchQuery !== "" + && beatmapsetSearch?.map(result => ( openPage(`/beatmapsets/${result.id}`)} > @@ -200,7 +203,7 @@ export default function HeaderSearchCommand() { - {pagesList.map((page) => ( + {pagesList.map(page => ( openPage(page.url)} diff --git a/components/Header/HeaderUserDropdown.tsx b/components/Header/HeaderUserDropdown.tsx index 742ba8b..f3a953a 100644 --- a/components/Header/HeaderUserDropdown.tsx +++ b/components/Header/HeaderUserDropdown.tsx @@ -1,6 +1,22 @@ "use client"; +import { + Cog, + Home, + LogOutIcon, + MonitorCog, + UserCircleIcon, + Users2, +} from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; import { Suspense } from "react"; + +import UserPrivilegeBadges from "@/app/(website)/user/[id]/components/UserPrivilegeBadges"; +import { HeaderLogoutAlert } from "@/components/Header/HeaderLogoutAlert"; +import ImageWithFallback from "@/components/ImageWithFallback"; +import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { DropdownMenu, DropdownMenuContent, @@ -10,25 +26,9 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; - -import Image from "next/image"; -import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import ImageWithFallback from "@/components/ImageWithFallback"; -import { HeaderLogoutAlert } from "@/components/Header/HeaderLogoutAlert"; -import Link from "next/link"; -import { UserBadge, UserResponse } from "@/lib/types/api"; -import { - Cog, - Home, - LogOutIcon, - MonitorCog, - UserCircleIcon, - Users2, -} from "lucide-react"; -import UserPrivilegeBadges from "@/app/(website)/user/[id]/components/UserPrivilegeBadges"; -import { usePathname, useRouter } from "next/navigation"; -import { isUserCanUseAdminPanel } from "@/lib/utils/userPrivileges.util"; import { useT } from "@/lib/i18n/utils"; +import type { UserResponse } from "@/lib/types/api"; +import { isUserCanUseAdminPanel } from "@/lib/utils/userPrivileges.util"; interface Props { self: UserResponse | null; @@ -58,22 +58,22 @@ export default function HeaderUserDropdown({ sideOffset={sideOffset} align={align} > - + <>
-
+
- + UA}> -
-
+
+
{self.username}
@@ -104,13 +104,13 @@ export default function HeaderUserDropdown({ - + {t("friends")} - + {t("settings")} @@ -122,12 +122,12 @@ export default function HeaderUserDropdown({ {pathname.includes("/admin") ? ( - + {t("returnToMainSite")} ) : ( - + {t("adminPanel")} @@ -138,7 +138,7 @@ export default function HeaderUserDropdown({ e.preventDefault()} + onSelect={e => e.preventDefault()} asChild className="cursor-pointer" > diff --git a/components/Header/LanguageSelector.tsx b/components/Header/LanguageSelector.tsx index 4c1babf..d095143 100644 --- a/components/Header/LanguageSelector.tsx +++ b/components/Header/LanguageSelector.tsx @@ -1,21 +1,22 @@ "use client"; -import { useCallback, useMemo } from "react"; -import { useRouter } from "next/navigation"; import Cookies from "js-cookie"; +import { Check, Languages } from "lucide-react"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; import { useLocale } from "next-intl"; +import { useCallback, useMemo } from "react"; + +import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; -import { Button } from "@/components/ui/button"; -import { Languages, Check } from "lucide-react"; -import { cn } from "@/lib/utils"; -import Image from "next/image"; import { AVAILABLE_LOCALES, DISPLAY_NAMES_LOCALES } from "@/lib/i18n/messages"; import { getCountryCodeForLocale } from "@/lib/i18n/utils"; +import { cn } from "@/lib/utils"; export function LanguageSelector() { const router = useRouter(); @@ -26,7 +27,7 @@ export function LanguageSelector() { Cookies.set("locale", locale); router.refresh(); }, - [router] + [router], ); const getLanguageName = useCallback( @@ -36,20 +37,21 @@ export function LanguageSelector() { type: "language", }); - const name = - DISPLAY_NAMES_LOCALES[locale] || - displayNames.of(locale) || - locale.toUpperCase(); + const name + = DISPLAY_NAMES_LOCALES[locale] + || displayNames.of(locale) + || locale.toUpperCase(); return name; - } catch { + } + catch { return DISPLAY_NAMES_LOCALES[locale] || locale.toUpperCase(); } }, - [currentLocale] + [currentLocale], ); const languages = useMemo(() => { - return AVAILABLE_LOCALES.map((localeCode) => ({ + return AVAILABLE_LOCALES.map(localeCode => ({ code: localeCode, countryCode: getCountryCodeForLocale(localeCode), nativeName: getLanguageName(localeCode, localeCode), @@ -62,15 +64,15 @@ export function LanguageSelector() { {languages @@ -83,8 +85,8 @@ export function LanguageSelector() { key={locale.code} onClick={() => changeLanguage(locale.code)} className={cn( - "flex items-center gap-3 cursor-pointer py-2.5 px-3", - isActive && "bg-accent" + "flex cursor-pointer items-center gap-3 px-3 py-2.5", + isActive && "bg-accent", )} > {`${locale.nativeName} {locale.nativeName} {isActive && ( - + )} ); diff --git a/components/Header/ThemeModeToggle.tsx b/components/Header/ThemeModeToggle.tsx index d32032b..f6ef9de 100644 --- a/components/Header/ThemeModeToggle.tsx +++ b/components/Header/ThemeModeToggle.tsx @@ -1,8 +1,8 @@ "use client"; -import * as React from "react"; import { Moon, Sun } from "lucide-react"; import { useTheme } from "next-themes"; +import * as React from "react"; import { Button } from "@/components/ui/button"; import { @@ -20,13 +20,11 @@ export function ThemeModeToggle({ children }: { children?: React.ReactNode }) { return ( - {children ? ( - children - ) : ( + {children ?? (