| 1 | "use client"; |
| 2 | |
| 3 | import { useQuery } from "@tanstack/react-query"; |
| 4 | import { useSession } from "next-auth/react"; |
| 5 | import { useMemo } from "react"; |
| 6 | |
| 7 | import { |
| 8 | getSystemPresentationThemes, |
| 9 | getUserCustomThemes, |
| 10 | } from "@/app/_actions/presentation/theme-actions"; |
| 11 | import { getUserFavoriteThemes } from "@/app/_actions/presentation/theme-favorite-actions"; |
| 12 | import { type CustomTheme } from "@/components/notebook/presentation/components/theme/types"; |
| 13 | import { PRESENTATION_AUTO_THEME_ID } from "@/lib/presentation/theme-resolution"; |
| 14 | import { themes as builtInThemes } from "@/lib/presentation/themes"; |
| 15 | import { usePresentationState } from "@/states/presentation-state"; |
| 16 | import { FontsSection } from "../../controls/global-settings/sections/FontsSection"; |
| 17 | import { ThemeFilter } from "./theme/Filter"; |
| 18 | import { useThemePanelState } from "./theme/theme-panel-state"; |
| 19 | import { ThemePanelHeader } from "./theme/ThemeHeader"; |
| 20 | import { ThemeSelector, type ThemeListItem } from "./theme/ThemeSelector"; |
| 21 | import { ThemeTabs } from "./theme/ThemeTabs"; |
| 22 | |
| 23 | export function ThemePanel() { |
| 24 | const { generatedThemeData, theme: activeTheme } = usePresentationState(); |
| 25 | const { tab, showFavorites, showFont } = useThemePanelState(); |
| 26 | const { data: session } = useSession(); |
| 27 | const canEditSystemThemes = session?.user?.isAdmin === true; |
| 28 | |
| 29 | const systemThemesQuery = useQuery({ |
| 30 | queryKey: ["presentation", "themes", "system"], |
| 31 | queryFn: async () => { |
| 32 | const result = await getSystemPresentationThemes(); |
| 33 | return result.success ? (result.themes as CustomTheme[]) : []; |
| 34 | }, |
| 35 | enabled: tab === "standard", |
| 36 | }); |
| 37 | |
| 38 | const userThemesQuery = useQuery({ |
| 39 | queryKey: ["presentation", "themes", "user"], |
| 40 | queryFn: async () => { |
| 41 | const result = await getUserCustomThemes(); |
| 42 | return result.success ? (result.themes as CustomTheme[]) : []; |
| 43 | }, |
| 44 | enabled: tab === "public", |
| 45 | }); |
| 46 | |
| 47 | const favoriteThemesQuery = useQuery({ |
| 48 | queryKey: ["presentation", "themes", "favorites"], |
| 49 | queryFn: async () => { |
| 50 | const result = await getUserFavoriteThemes(); |
| 51 | return result.success ? (result.themes as CustomTheme[]) : []; |
| 52 | }, |
| 53 | enabled: showFavorites, |
| 54 | }); |
| 55 | |
| 56 | const userThemes = userThemesQuery.data ?? []; |
| 57 | const favoriteThemes = favoriteThemesQuery.data ?? []; |
| 58 | const systemThemes = systemThemesQuery.data ?? []; |
| 59 | |
| 60 | const standardThemeItems = useMemo<ThemeListItem[]>(() => { |
| 61 | const systemThemesById = new Map( |
| 62 | systemThemes.map((item) => [item.id, item]), |
| 63 | ); |
| 64 | |
| 65 | const automaticThemeItem: ThemeListItem[] = generatedThemeData |
| 66 | ? [ |
| 67 | { |
| 68 | themeId: PRESENTATION_AUTO_THEME_ID, |
| 69 | theme: { |
| 70 | ...generatedThemeData, |
| 71 | name: generatedThemeData.name || "Custom Theme", |
| 72 | description: |
| 73 | generatedThemeData.description || |
| 74 | "Generated presentation theme", |
| 75 | }, |
| 76 | canLike: false, |
| 77 | showFavoriteButton: false, |
| 78 | }, |
| 79 | ] |
| 80 | : []; |
| 81 | |
| 82 | const builtInThemeItems = Object.entries(builtInThemes).map( |
| 83 | ([key, value]) => { |
| 84 | const systemTheme = systemThemesById.get(key); |
| 85 | const themeData = systemTheme |
| 86 | ? { |
| 87 | ...value, |
| 88 | ...systemTheme.themeData, |
| 89 | name: systemTheme.name, |
| 90 | description: systemTheme.description ?? value.description, |
| 91 | } |
| 92 | : value; |
| 93 | |
| 94 | return { |
| 95 | themeId: key, |
| 96 | theme: themeData, |
| 97 | canLike: false, |
| 98 | showFavoriteButton: false, |
| 99 | isAdminTheme: true, |
| 100 | canEditSystemTheme: canEditSystemThemes, |
| 101 | }; |
| 102 | }, |
| 103 | ); |
| 104 | |
| 105 | return [...automaticThemeItem, ...builtInThemeItems]; |
| 106 | }, [canEditSystemThemes, generatedThemeData, systemThemes]); |
| 107 | |
| 108 | const userThemeItems = useMemo<ThemeListItem[]>( |
| 109 | () => |
| 110 | userThemes.map((item) => ({ |
| 111 | key: item.id, |
| 112 | themeId: item.id, |
| 113 | theme: { ...item.themeData, name: item.name }, |
| 114 | isFavorite: item.isFavorite ?? false, |
| 115 | likeCount: item.likeCount ?? 0, |
| 116 | isLiked: item.isLiked ?? false, |
| 117 | canLike: false, |
| 118 | showFavoriteButton: false, |
| 119 | isUserTheme: true, |
| 120 | })), |
| 121 | [userThemes], |
| 122 | ); |
| 123 | |
| 124 | const favoriteThemeItems = useMemo<ThemeListItem[]>( |
| 125 | () => |
| 126 | favoriteThemes.map((item) => ({ |
| 127 | key: item.id, |
| 128 | themeId: item.id, |
| 129 | theme: { ...item.themeData, name: item.name }, |
| 130 | isFavorite: item.isFavorite ?? true, |
| 131 | likeCount: item.likeCount ?? 0, |
| 132 | isLiked: item.isLiked ?? false, |
| 133 | canLike: false, |
| 134 | showFavoriteButton: true, |
| 135 | isUserTheme: item.userId === session?.user?.id, |
| 136 | })), |
| 137 | [favoriteThemes, session?.user?.id], |
| 138 | ); |
| 139 | |
| 140 | let themesForSelector: ThemeListItem[] = []; |
| 141 | if (showFavorites) { |
| 142 | themesForSelector = favoriteThemeItems; |
| 143 | } else if (tab === "public") { |
| 144 | themesForSelector = userThemeItems; |
| 145 | } else { |
| 146 | themesForSelector = standardThemeItems; |
| 147 | } |
| 148 | |
| 149 | const isLoadingThemes = showFavorites |
| 150 | ? favoriteThemesQuery.isPending |
| 151 | : tab === "public" && userThemesQuery.isPending; |
| 152 | |
| 153 | return ( |
| 154 | <div className="flex h-full flex-col gap-2 bg-background"> |
| 155 | <ThemePanelHeader /> |
| 156 | <ThemeTabs /> |
| 157 | <ThemeFilter /> |
| 158 | |
| 159 | {showFont ? ( |
| 160 | <div className="scrollbar-thin max-h-[calc(100vh-4rem-4rem-4rem)] overflow-y-auto px-4 pt-2 pb-4 scrollbar-thumb-accent"> |
| 161 | <FontsSection /> |
| 162 | </div> |
| 163 | ) : ( |
| 164 | <ThemeSelector |
| 165 | themes={themesForSelector} |
| 166 | activeKey={String(activeTheme)} |
| 167 | isLoading={isLoadingThemes && !themesForSelector.length} |
| 168 | emptyMessage={ |
| 169 | showFavorites |
| 170 | ? "You have not favorited any themes yet." |
| 171 | : tab === "public" |
| 172 | ? "You have not created any themes yet." |
| 173 | : "No themes available right now." |
| 174 | } |
| 175 | /> |
| 176 | )} |
| 177 | </div> |
| 178 | ); |
| 179 | } |
| 180 |