From 0a2eac63b68319f6a28cf3b8b185323a110a7d71 Mon Sep 17 00:00:00 2001 From: Polaris Date: Wed, 24 Jul 2024 23:25:01 -0400 Subject: [PATCH] fixed hydration error when changing theme and added dynamic default avatar --- app/layout.tsx | 11 +-- app/theme-provider.tsx | 24 +++++-- components/avatarcustomization/actions.ts | 67 ++++++++++++------ components/avatarcustomization/page.tsx | 79 +++++++++++++++++----- components/darkmode.tsx | 68 ++++++++++--------- components/navigationbar/navigationbar.tsx | 4 +- next.config.mjs | 4 ++ 7 files changed, 171 insertions(+), 86 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 7b2ae02..f9cb780 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { GeistSans } from "geist/font/sans"; import { Toaster } from "@/components/ui/toaster"; import "./globals.css"; -import { ThemeProvider } from "./theme-provider"; +import Providers from "./theme-provider"; export const metadata: Metadata = { title: "Daphnis", description: "Artemis score viewer", @@ -16,14 +16,9 @@ export default function RootLayout({ return ( - +
{children}
-
+ diff --git a/app/theme-provider.tsx b/app/theme-provider.tsx index b0ff266..3dbd08e 100644 --- a/app/theme-provider.tsx +++ b/app/theme-provider.tsx @@ -1,9 +1,23 @@ "use client"; -import * as React from "react"; -import { ThemeProvider as NextThemesProvider } from "next-themes"; -import { type ThemeProviderProps } from "next-themes/dist/types"; +import { ThemeProvider } from "next-themes"; +import { useEffect, useState } from "react"; -export function ThemeProvider({ children, ...props }: ThemeProviderProps) { - return {children}; +function Providers({ children }: { children: React.ReactNode }) { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return null; + } + return ( + + {children} + + ); } + +export default Providers; diff --git a/components/avatarcustomization/actions.ts b/components/avatarcustomization/actions.ts index ff0c6b1..a5a571d 100644 --- a/components/avatarcustomization/actions.ts +++ b/components/avatarcustomization/actions.ts @@ -7,27 +7,50 @@ import type * as Prisma from "@prisma/client"; // type ChuniScorePlaylog = Prisma.PrismaClient; // type ChuniStaticMusic = Prisma.PrismaClient; +export async function getCurrentAvatarParts() { + const { user } = await getAuth(); + + if (!user || !user.accessCode) { + throw new Error("User is not authenticated or accessCode is missing"); + } + + const avatarParts = await artemis.chuni_profile_data.findMany({ + where: { + user: user.UserId, + }, + select: { + avatarSkin: true, + avatarBack: true, + avatarFace: true, + avatarFront: true, + avatarHead: true, + avatarItem: true, + avatarWear: true, + }, + }); + return avatarParts; +} export async function getAllAvatarParts(category: number) { - const { user } = await getAuth(); - - if (!user || !user.accessCode) { - throw new Error("User is not authenticated or accessCode is missing"); - } - - const avatarParts = await artemis.chuni_static_avatar.findMany({ - where: { - category: category - }, - select: { - id: true, - name: true, - avatarAccessoryId: true, - category: true, - version: true, - iconPath: true, - texturePath: true, - } - }); - return avatarParts; - } \ No newline at end of file + const { user } = await getAuth(); + + if (!user || !user.accessCode) { + throw new Error("User is not authenticated or accessCode is missing"); + } + + const avatarParts = await artemis.chuni_static_avatar.findMany({ + where: { + category: category, + }, + select: { + id: true, + name: true, + avatarAccessoryId: true, + category: true, + version: true, + iconPath: true, + texturePath: true, + }, + }); + return avatarParts; +} diff --git a/components/avatarcustomization/page.tsx b/components/avatarcustomization/page.tsx index 84a4070..d0a0cda 100644 --- a/components/avatarcustomization/page.tsx +++ b/components/avatarcustomization/page.tsx @@ -1,9 +1,9 @@ "use client"; -import React, { FC } from "react"; +import React, { FC, useEffect, useState } from "react"; import { chuni_static_avatar } from "@/prisma/schemas/artemis/generated/artemis"; import { Check, ChevronsUpDown } from "lucide-react"; - +import { getCurrentAvatarParts } from "./actions"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { @@ -28,7 +28,6 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { z } from "zod"; -type chunithm_avatar = chuni_static_avatar; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { toast } from "../ui/use-toast"; @@ -38,11 +37,12 @@ const getAvatarTextureSrc = (id: number | undefined) => { return `avatarAccessory/CHU_UI_Avatar_Tex_0${id}.png`; }; +type chunithm_avatar = chuni_static_avatar; + type AvatarSelectionProps = { avatarHeadSelectionData: { avatarParts: chunithm_avatar[]; }; - avatarFaceSelectionData: { avatarParts: chunithm_avatar[]; }; @@ -86,6 +86,46 @@ export const AvatarCustomization: FC = ({ resolver: zodResolver(FormSchema), }); + const [avatarFaceId, setAvatarFaceId] = useState( + undefined + ); + + const [avatarSkinId, setAvatarSkinId] = useState( + undefined + ); + const [avatarHeadId, setAvatarHeadId] = useState( + undefined + ); + const [avatarWearId, setAvatarWearId] = useState( + undefined + ); + const [avatarBackId, setAvatarBackId] = useState( + undefined + ); + const [avatarItemId, setAvatarItemId] = useState( + undefined + ); + + useEffect(() => { + const fetchAvatarParts = async () => { + try { + const data = await getCurrentAvatarParts(); + if (data.length > 0) { + setAvatarFaceId(data[0].avatarFace!); + setAvatarSkinId(data[0].avatarSkin!); + setAvatarHeadId(data[0].avatarHead!); + setAvatarWearId(data[0].avatarWear!); + setAvatarBackId(data[0].avatarBack!); + setAvatarItemId(data[0].avatarItem!); + } + } catch (error) { + console.error("Error fetching avatar parts:", error); + } + }; + + fetchAvatarParts(); + }, []); + function onSubmit(data: z.infer) { toast({ title: "You submitted the following values:", @@ -105,67 +145,70 @@ export const AvatarCustomization: FC = ({ AvatarHeadAccessory: { src: getTexture( form.watch("AvatarHeadAccessory"), - "avatarAccessory/CHU_UI_Avatar_Tex_DefaultHead.png" + `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarHeadId}.png` ), className: "avatar_head", }, AvatarFaceAccessory: { src: getTexture( form.watch("AvatarFaceAccessory"), - "avatarAccessory/CHU_UI_Avatar_Tex_DefaultFace.png" + `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarFaceId}.png` ), className: "avatar_face", }, AvatarItemAccessoryR: { src: getTexture( form.watch("AvatarItemAccessory"), - "avatarAccessory/CHU_UI_Avatar_Tex_DefaultItem.png" + `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarItemId}.png` ), className: "avatar_item_r ", }, - AvatarItemAccessoryL: { src: getTexture( form.watch("AvatarItemAccessory"), - "avatarAccessory/CHU_UI_Avatar_Tex_DefaultItem.png" + `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarItemId}.png` ), className: "avatar_item_l ", }, - AvatarBackAccessory: { src: getTexture( form.watch("AvatarBackAccessory"), - "avatarAccessory/CHU_UI_Avatar_Tex_DefaultBack.png" + `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarBackId}.png` ), className: "avatar_back", }, AvatarWearAccessory: { src: getTexture( form.watch("AvatarWearAccessory"), - "avatarAccessory/CHU_UI_Avatar_Tex_DefaultWear.png" + `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarWearId}.png` ), className: "avatar_wear", }, avatarSkinAccessory: { - src: "avatarAccessory/CHU_UI_Avatar_Tex_01400001.png", + src: getTexture( + avatarSkinId, + `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarSkinId}.png` + ), className: "avatar_skin", }, AvatarRightHand: { src: "avatarStatic/CHU_UI_Avatar_Tex_RightHand.png", className: "avatar_hand_r", }, + + AvatarSkinFootR: { + src: `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarSkinId}.png`, + className: "avatar_skinfoot_r", + }, AvatarLeftHand: { src: "avatarStatic/CHU_UI_Avatar_Tex_LeftHand.png", className: "avatar_hand_l", }, AvatarSkinFootL: { - src: "avatarAccessory/CHU_UI_Avatar_Tex_01400001.png", + src: `avatarAccessory/CHU_UI_Avatar_Tex_0${avatarSkinId}.png`, + className: "avatar_skinfoot_l", }, - AvatarSkinFootR: { - src: "avatarAccessory/CHU_UI_Avatar_Tex_01400001.png", - className: "avatar_skinfoot_r", - }, AvatarFaceStatic: { src: "avatarStatic/CHU_UI_Avatar_Tex_Face.png", className: "avatar_face_static", diff --git a/components/darkmode.tsx b/components/darkmode.tsx index 9264016..f07c74a 100644 --- a/components/darkmode.tsx +++ b/components/darkmode.tsx @@ -1,40 +1,46 @@ "use client"; -import * as React from "react"; -import { Moon, Sun } from "lucide-react"; +import React, { useState, useEffect } from "react"; import { useTheme } from "next-themes"; +import { Moon, Sun } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; +function DarkToggle() { + const [mounted, setMounted] = useState(false); + const [isDark, setIsDark] = useState(false); + const { theme, setTheme } = useTheme(); -export function ModeToggle() { - const { setTheme } = useTheme(); + useEffect(() => { + setMounted(true); + setIsDark(theme === "dark"); + }, [theme]); + + if (!mounted) { + return null; + } + + const toggleDarkMode = () => { + setIsDark(!isDark); + setTheme(theme === "light" ? "dark" : "light"); + }; return ( - - - - - - setTheme("light")}> - Light - - setTheme("dark")}> - Dark - - setTheme("system")}> - System - - - + ); } + +export default DarkToggle; diff --git a/components/navigationbar/navigationbar.tsx b/components/navigationbar/navigationbar.tsx index 86e1706..be0489b 100644 --- a/components/navigationbar/navigationbar.tsx +++ b/components/navigationbar/navigationbar.tsx @@ -16,7 +16,7 @@ import { getAuth } from "@/auth/queries/getauth"; import NavigationMenuDesktop from "./desktopNavBar"; import NavigationMenuMobile from "./mobileNavBar"; import { signOut } from "@/auth/components/signout"; -import { ModeToggle } from "../darkmode"; +import DarkToggle from "../darkmode"; const HeaderNavigation = async () => { const { user } = await getAuth(); @@ -51,7 +51,7 @@ const HeaderNavigation = async () => { /> - + diff --git a/next.config.mjs b/next.config.mjs index f615264..837bd7b 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,10 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + compiler: { + styledComponents: true, + }, webpack: (config) => { + config.externals.push('@node-rs/argon2', '@node-rs/bcrypt'); return config; },