diff --git a/components/Navbar.tsx b/components/Navbar.tsx index fb3678ea..d4fbbdbc 100644 --- a/components/Navbar.tsx +++ b/components/Navbar.tsx @@ -26,12 +26,12 @@ export default function Navbar() { const { width } = useWindowDimensions(); const handleToggle = () => { - if (settings.theme === "dark") { - updateSettings({ theme: "light" }); - } else { - updateSettings({ theme: "dark" }); - } + const [colorTheme, mode] = (settings.theme || "default-light").split('-'); + const newMode = mode === "dark" ? "light" : "dark"; + const newTheme = `${colorTheme}-${newMode}`; + updateSettings({ theme: newTheme }); }; + useEffect(() => { setSidebar(false); @@ -135,16 +135,16 @@ export default function Navbar() {
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - handleToggle(); - }} - tabIndex={0} - role="button" - > - Switch to {settings.theme === "light" ? "Dark" : "Light"} -
    +
    { + (document?.activeElement as HTMLElement)?.blur(); + handleToggle(); + }} + tabIndex={0} + role="button" + > + Switch to {(settings.theme || "default-light").endsWith("-dark") ? "Light" : "Dark"} +
  • { + const storedTheme = localStorage.getItem("theme"); + if (storedTheme) { + setTheme(storedTheme); + } else { + // Default theme if not set in localStorage + localStorage.setItem("theme", "default-light"); + setTheme("default-light"); + } + console.log("Initial theme from localStorage:", storedTheme || "default-light"); + }, []); - const handleToggle = (e: any) => { - setTheme(e.target.checked ? "dark" : "light"); - }; + const handleToggle = () => { + const [currentColorTheme, currentMode] = theme.split('-'); + const newMode = currentMode === 'light' ? 'dark' : 'light'; + const newTheme = `${currentColorTheme}-${newMode}`; - useEffect(() => { - updateSettings({ theme: theme as string }); - }, [theme]); + setTheme(newTheme); + localStorage.setItem("theme", newTheme); + document.documentElement.setAttribute('data-theme', newTheme); + updateSettings({ theme: newTheme }); + console.log("New theme set:", newTheme); + }; - return ( -
    - -
    - ); + const isDarkMode = theme.endsWith('-dark'); + + return ( +
    + +
    + ); } diff --git a/pages/_app.tsx b/pages/_app.tsx index 60c15c38..1ddecc69 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import "@/styles/globals.css"; import "bootstrap-icons/font/bootstrap-icons.css"; import { SessionProvider } from "next-auth/react"; @@ -9,50 +9,62 @@ import { Toaster } from "react-hot-toast"; import { Session } from "next-auth"; export default function App({ - Component, - pageProps, + Component, + pageProps, }: AppProps<{ - session: Session; + session: Session; }>) { - return ( - - - Linkwarden - - - - - - - - - - - - ); + + useEffect(() => { + let theme = localStorage.getItem("theme"); + if (!theme || !theme.includes("-")) { + theme = "default-dark"; // Default theme + localStorage.setItem("theme", theme); + } + + document.documentElement.setAttribute('data-theme', theme); + }, []); + + + return ( + + + Linkwarden + + + + + + + + + + + + ); } diff --git a/pages/collections/[id].tsx b/pages/collections/[id].tsx index aa8ebd78..54fb345d 100644 --- a/pages/collections/[id].tsx +++ b/pages/collections/[id].tsx @@ -101,12 +101,14 @@ export default function Index() { return (
    {activeCollection && (
    diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx index 18973c7d..5987e042 100644 --- a/pages/dashboard.tsx +++ b/pages/dashboard.tsx @@ -19,293 +19,293 @@ import ViewDropdown from "@/components/ViewDropdown"; // import GridView from "@/components/LinkViews/Layouts/GridView"; export default function Dashboard() { - const { collections } = useCollectionStore(); - const { links } = useLinkStore(); - const { tags } = useTagStore(); + const { collections } = useCollectionStore(); + const { links } = useLinkStore(); + const { tags } = useTagStore(); - const [numberOfLinks, setNumberOfLinks] = useState(0); + const [numberOfLinks, setNumberOfLinks] = useState(0); - const [showLinks, setShowLinks] = useState(3); + const [showLinks, setShowLinks] = useState(3); - useLinks({ pinnedOnly: true, sort: 0 }); + useLinks({ pinnedOnly: true, sort: 0 }); - useEffect(() => { - setNumberOfLinks( - collections.reduce( - (accumulator, collection) => - accumulator + (collection._count as any).links, - 0 - ) - ); - }, [collections]); + useEffect(() => { + setNumberOfLinks( + collections.reduce( + (accumulator, collection) => + accumulator + (collection._count as any).links, + 0 + ) + ); + }, [collections]); - const handleNumberOfLinksToShow = () => { - if (window.innerWidth > 1900) { - setShowLinks(8); - } else if (window.innerWidth > 1280) { - setShowLinks(6); - } else if (window.innerWidth > 650) { - setShowLinks(4); - } else setShowLinks(3); - }; + const handleNumberOfLinksToShow = () => { + if (window.innerWidth > 1900) { + setShowLinks(8); + } else if (window.innerWidth > 1280) { + setShowLinks(6); + } else if (window.innerWidth > 650) { + setShowLinks(4); + } else setShowLinks(3); + }; - const { width } = useWindowDimensions(); + const { width } = useWindowDimensions(); - useEffect(() => { - handleNumberOfLinksToShow(); - }, [width]); + useEffect(() => { + handleNumberOfLinksToShow(); + }, [width]); - const importBookmarks = async (e: any, format: MigrationFormat) => { - const file: File = e.target.files[0]; + const importBookmarks = async (e: any, format: MigrationFormat) => { + const file: File = e.target.files[0]; - if (file) { - var reader = new FileReader(); - reader.readAsText(file, "UTF-8"); - reader.onload = async function (e) { - const load = toast.loading("Importing..."); + if (file) { + var reader = new FileReader(); + reader.readAsText(file, "UTF-8"); + reader.onload = async function (e) { + const load = toast.loading("Importing..."); - const request: string = e.target?.result as string; + const request: string = e.target?.result as string; - const body: MigrationRequest = { - format, - data: request, - }; + const body: MigrationRequest = { + format, + data: request, + }; - const response = await fetch("/api/v1/migration", { - method: "POST", - body: JSON.stringify(body), - }); + const response = await fetch("/api/v1/migration", { + method: "POST", + body: JSON.stringify(body), + }); - const data = await response.json(); + const data = await response.json(); - toast.dismiss(load); + toast.dismiss(load); - toast.success("Imported the Bookmarks! Reloading the page..."); + toast.success("Imported the Bookmarks! Reloading the page..."); - setTimeout(() => { - location.reload(); - }, 2000); - }; - reader.onerror = function (e) { - console.log("Error:", e); - }; - } - }; + setTimeout(() => { + location.reload(); + }, 2000); + }; + reader.onerror = function (e) { + console.log("Error:", e); + }; + } + }; - const [newLinkModal, setNewLinkModal] = useState(false); + const [newLinkModal, setNewLinkModal] = useState(false); - const [viewMode, setViewMode] = useState( - localStorage.getItem("viewMode") || ViewMode.Card - ); + const [viewMode, setViewMode] = useState( + localStorage.getItem("viewMode") || ViewMode.Card + ); - const linkView = { - [ViewMode.Card]: CardView, - // [ViewMode.Grid]: GridView, - [ViewMode.List]: ListView, - }; + const linkView = { + [ViewMode.Card]: CardView, + // [ViewMode.Grid]: GridView, + [ViewMode.List]: ListView, + }; - // @ts-ignore - const LinkComponent = linkView[viewMode]; + // @ts-ignore + const LinkComponent = linkView[viewMode]; - return ( - -
    -
    - - -
    - -
    -
    - - -
    - - - -
    - - -
    -
    - -
    -
    - -
    - - View All - - -
    - -
    - {links[0] ? ( -
    - + return ( + +
    +
    + +
    - ) : ( + +
    +
    + + +
    + + + +
    + + +
    +
    + +
    +
    + +
    + + View All + + +
    +
    -

    - View Your Recently Added Links Here! -

    -

    - This section will view your latest added Links across every - Collections you have access to. -

    - -
    -
    { - setNewLinkModal(true); - }} - className="inline-flex items-center gap-2 text-sm btn btn-accent dark:border-violet-400 text-white" - > - - - Add New Link - -
    - -
    -
    - -

    Import From

    + {links[0] ? ( +
    +
    -
      -
    • - -
    • -
    • - -
    • -
    -
    -
    -
    - )} -
    + ) : ( +
    +

    + View Your Recently Added Links Here! +

    +

    + This section will view your latest added Links across every + Collections you have access to. +

    -
    -
    - -
    - - View All - - -
    +
    +
    { + setNewLinkModal(true); + }} + className="inline-flex items-center gap-2 text-sm btn btn-accent dark:border-violet-400 text-white" + > + + + Add New Link + +
    -
    - {links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? ( -
    -
    - {links - .filter((e) => e.pinnedBy && e.pinnedBy[0]) - .map((e, i) => ) - .slice(0, showLinks)} -
    +
    +
    + +

    Import From

    +
    +
      +
    • + +
    • +
    • + +
    • +
    +
    +
    +
    + )}
    - ) : ( + +
    +
    + +
    + + View All + + +
    +
    -

    - Pin Your Favorite Links Here! -

    -

    - You can Pin your favorite Links by clicking on the three dots on - each Link and clicking{" "} - Pin to Dashboard. -

    + {links.some((e) => e.pinnedBy && e.pinnedBy[0]) ? ( +
    +
    + {links + .filter((e) => e.pinnedBy && e.pinnedBy[0]) + .map((e, i) => ) + .slice(0, showLinks)} +
    +
    + ) : ( +
    +

    + Pin Your Favorite Links Here! +

    +

    + You can Pin your favorite Links by clicking on the three dots on + each Link and clicking{" "} + Pin to Dashboard. +

    +
    + )}
    - )} -
    -
    - {newLinkModal ? ( - setNewLinkModal(false)} /> - ) : undefined} -
    - ); +
    + {newLinkModal ? ( + setNewLinkModal(false)} /> + ) : undefined} + + ); } diff --git a/pages/settings/appearance.tsx b/pages/settings/appearance.tsx index 385225a2..93700c2c 100644 --- a/pages/settings/appearance.tsx +++ b/pages/settings/appearance.tsx @@ -1,106 +1,125 @@ + import SettingsLayout from "@/layouts/SettingsLayout"; import { useState, useEffect } from "react"; import useAccountStore from "@/store/account"; import { AccountSettings } from "@/types/global"; import { toast } from "react-hot-toast"; -import React from "react"; import useLocalSettingsStore from "@/store/localSettings"; export default function Appearance() { - const { updateSettings } = useLocalSettingsStore(); - const submit = async () => { + const { updateSettings } = useLocalSettingsStore(); + const { account, updateAccount } = useAccountStore(); + const submit = async () => { setSubmitLoader(true); - const load = toast.loading("Applying..."); - + const response = await updateAccount({ ...user, }); toast.dismiss(load); - if (response.ok) { - toast.success("Settings Applied!"); + toast.success("Settings Applied!"); } else toast.error(response.data as string); - setSubmitLoader(false); - }; + setSubmitLoader(false); + }; + const [submitLoader, setSubmitLoader] = useState(false); + const [user, setUser] = useState( + !objectIsEmpty(account) + ? account + : ({ + // @ts-ignore + id: null, + name: "", + username: "", + email: "", + emailVerified: null, + blurredFavicons: null, + image: "", + isPrivate: true, + // @ts-ignore + createdAt: null, + whitelistedUsers: [], + } as unknown as AccountSettings) + ); - const [submitLoader, setSubmitLoader] = useState(false); + // Combine colorTheme and mode into a single state + const [theme, setTheme] = useState(localStorage.getItem("theme") || "default-dark"); - const { account, updateAccount } = useAccountStore(); + function objectIsEmpty(obj: object) { + return Object.keys(obj).length === 0; + } - const [user, setUser] = useState( - !objectIsEmpty(account) - ? account - : ({ - // @ts-ignore - id: null, - name: "", - username: "", - email: "", - emailVerified: null, - blurredFavicons: null, - image: "", - isPrivate: true, - // @ts-ignore - createdAt: null, - whitelistedUsers: [], - } as unknown as AccountSettings) - ); + useEffect(() => { + if (!objectIsEmpty(account)) setUser({ ...account }); + }, [account]); - function objectIsEmpty(obj: object) { - return Object.keys(obj).length === 0; - } + const handleThemeChange = (newThemePart: string, isColorTheme: boolean) => { + const currentTheme = localStorage.getItem("theme") || "default-light"; + const [currentColorTheme, currentMode] = currentTheme.split('-'); + const newTheme = isColorTheme ? `${newThemePart}-${currentMode}` : `${currentColorTheme}-${newThemePart}`; - useEffect(() => { - if (!objectIsEmpty(account)) setUser({ ...account }); - }, [account]); + localStorage.setItem("theme", newTheme); + document.documentElement.setAttribute('data-theme', newTheme); + updateSettings({ theme: newTheme }); - return ( - -

    Appearance

    + // Update the theme state + setTheme(newTheme); + }; -
    -
    -
    -

    Select Theme

    -
    -
    updateSettings({ theme: "dark" })} - > - -

    Dark

    - {/*
    */} + + + return ( + +

    Appearance

    +
    + +
    + +
    +

    Select Mode

    +
    + {["light", "dark"].map((modeOption) => ( + + ))} +
    -
    updateSettings({ theme: "light" })} - > - -

    Light

    - {/*
    */} -
    -
    -
    - {/* */} -
    - - ); +
    +

    Select Color Theme

    +
    + {["default", "red", "green", "orange"].map((colorTheme) => ( + + ))} +
    +
    + + +
    + + ); } + diff --git a/store/localSettings.ts b/store/localSettings.ts index e38bae8d..bf0552b5 100644 --- a/store/localSettings.ts +++ b/store/localSettings.ts @@ -18,34 +18,39 @@ const useLocalSettingsStore = create((set) => ({ viewMode: "", }, updateSettings: async (newSettings) => { - if ( - newSettings.theme && - newSettings.theme !== localStorage.getItem("theme") - ) { + if (newSettings.theme) { localStorage.setItem("theme", newSettings.theme); - - const localTheme = localStorage.getItem("theme") || ""; - - document.querySelector("html")?.setAttribute("data-theme", localTheme); + document.documentElement.setAttribute('data-theme', newSettings.theme); + + if (newSettings.theme.endsWith("-dark")) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } } - if ( - newSettings.viewMode && - newSettings.viewMode !== localStorage.getItem("viewMode") - ) { + if (newSettings.viewMode) { localStorage.setItem("viewMode", newSettings.viewMode); - - // const localTheme = localStorage.getItem("viewMode") || ""; } set((state) => ({ settings: { ...state.settings, ...newSettings } })); }, setSettings: async () => { - if (!localStorage.getItem("theme")) { - localStorage.setItem("theme", "dark"); + let theme = localStorage.getItem("theme"); + if (!theme || !theme.includes("-")) { + theme = "default-dark"; // Default theme + localStorage.setItem("theme", theme); } - const localTheme = localStorage.getItem("theme") || ""; + const localTheme = theme; + + document.documentElement.setAttribute('data-theme', localTheme); + + if (localTheme.endsWith("-dark")) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } set((state) => ({ settings: { ...state.settings, theme: localTheme }, diff --git a/tailwind.config.js b/tailwind.config.js index 45322f5c..2baa5df3 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -5,7 +5,7 @@ module.exports = { daisyui: { themes: [ { - light: { + "default-light": { primary: "#0369a1", secondary: "#0891b2", accent: "#6d28d9", @@ -21,7 +21,7 @@ module.exports = { }, }, { - dark: { + "default-dark": { primary: "#7dd3fc", secondary: "#22d3ee", accent: "#6d28d9", @@ -36,21 +36,124 @@ module.exports = { error: "#f1293c", }, }, - ], - }, - darkMode: ["class", '[data-theme="dark"]'], - content: [ - "./app/**/*.{js,ts,jsx,tsx}", - "./pages/**/*.{js,ts,jsx,tsx}", - "./components/**/*.{js,ts,jsx,tsx}", + // Red Light Theme + { + "red-light": { + primary: "#ef4444", + secondary: "#dc2626", + accent: "#6d28d9", + neutral: "#6b7280", + "neutral-content": "#d1d5db", + "base-100": "#ffffff", + "base-200": "#f3f4f6", + "base-content": "#0a0a0a", + info: "#a5f3fc", + success: "#22c55e", + warning: "#facc15", + error: "#dc2626", + }, + }, + // Red Dark Theme + { + "red-dark": { + primary: "#ef4444", + secondary: "#dc2626", + accent: "#6d28d9", + neutral: "#9ca3af", + "neutral-content": "#404040", + "base-100": "#171717", + "base-200": "#262626", + "base-content": "#fafafa", + info: "#009ee4", + success: "#00b17d", + warning: "#eac700", + error: "#f1293c", + }, + }, + // Green Light Theme + { + "green-light": { + primary: "#22c55e", + secondary: "#16a34a", + accent: "#6d28d9", + neutral: "#6b7280", + "neutral-content": "#d1d5db", + "base-100": "#ffffff", + "base-200": "#f3f4f6", + "base-content": "#0a0a0a", + info: "#a5f3fc", + success: "#22c55e", + warning: "#facc15", + error: "#dc2626", + }, + }, + // Green Dark Theme + { + "green-dark": { + primary: "#22c55e", + secondary: "#16a34a", + accent: "#6d28d9", + neutral: "#9ca3af", + "neutral-content": "#404040", + "base-100": "#171717", + "base-200": "#262626", + "base-content": "#fafafa", + info: "#009ee4", + success: "#00b17d", + warning: "#eac700", + error: "#f1293c", + }, + }, + // Orange Light Theme + { + "orange-light": { + primary: "#f97316", + secondary: "#ea580c", + accent: "#6d28d9", + neutral: "#9ca3af", + "neutral-content": "#404040", + "base-100": "#171717", + "base-200": "#262626", + "base-content": "#fafafa", + info: "#009ee4", + success: "#00b17d", + warning: "#eac700", + error: "#f1293c", + }, + }, + // Orange Dark Theme + { + "orange-dark": { + primary: "#f97316", + secondary: "#ea580c", + accent: "#6d28d9", + neutral: "#9ca3af", + "neutral-content": "#404040", + "base-100": "#171717", + "base-200": "#262626", + "base-content": "#fafafa", + info: "#009ee4", + success: "#00b17d", + warning: "#eac700", + error: "#f1293c", + }, + }, + ], + }, + darkMode: ["class", '[data-theme="dark"]'], + content: [ + "./app/**/*.{js,ts,jsx,tsx}", + "./pages/**/*.{js,ts,jsx,tsx}", + "./components/**/*.{js,ts,jsx,tsx}", // For the "layouts" directory - "./layouts/**/*.{js,ts,jsx,tsx}", - ], - plugins: [ - require("daisyui"), - plugin(({ addVariant }) => { - addVariant("dark", '&[data-theme="dark"]'); - }), - ], -}; + "./layouts/**/*.{js,ts,jsx,tsx}", + ], + plugins: [ + require("daisyui"), + plugin(({ addVariant }) => { + addVariant("dark", '&[data-theme="dark"]'); + }), + ], + }; + \ No newline at end of file