fix: board not found flicker on reloading public boards (#149)
* fix: board not found flicker on reloading public boards * fix: using netx-theme's ThemeProvider * chore: installing next-theme * fix: theme issue on landing page * fix: image theme hydration issue
This commit is contained in:
@@ -49,6 +49,7 @@
|
|||||||
"next": "^15.3.4",
|
"next": "^15.3.4",
|
||||||
"next-logger": "^5.0.1",
|
"next-logger": "^5.0.1",
|
||||||
"next-runtime-env": "^1.7.2",
|
"next-runtime-env": "^1.7.2",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"nextjs-cors": "^2.2.0",
|
"nextjs-cors": "^2.2.0",
|
||||||
"pino": "^9.6.0",
|
"pino": "^9.6.0",
|
||||||
"posthog-js": "^1.254.0",
|
"posthog-js": "^1.254.0",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { authClient } from "@kan/auth/client";
|
|||||||
|
|
||||||
import { useClickOutside } from "~/hooks/useClickOutside";
|
import { useClickOutside } from "~/hooks/useClickOutside";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import { useTheme } from "~/providers/theme";
|
import { useTheme } from "next-themes";
|
||||||
import { WorkspaceProvider } from "~/providers/workspace";
|
import { WorkspaceProvider } from "~/providers/workspace";
|
||||||
import FeedbackModal from "./FeedbackModal";
|
import FeedbackModal from "./FeedbackModal";
|
||||||
import Modal from "./modal";
|
import Modal from "./modal";
|
||||||
@@ -41,7 +41,7 @@ export default function Dashboard({
|
|||||||
rightPanel,
|
rightPanel,
|
||||||
hasRightPanel = false,
|
hasRightPanel = false,
|
||||||
}: DashboardProps) {
|
}: DashboardProps) {
|
||||||
const theme = useTheme();
|
const { theme } = useTheme();
|
||||||
const { modalContentType } = useModal();
|
const { modalContentType } = useModal();
|
||||||
|
|
||||||
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
const { data: session, isPending: sessionLoading } = authClient.useSession();
|
||||||
@@ -90,7 +90,7 @@ export default function Dashboard({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDarkMode = theme.activeTheme === "dark";
|
const isDarkMode = theme === "dark";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import settingsIconLight from "~/assets/settings-light.json";
|
|||||||
import ReactiveButton from "~/components/ReactiveButton";
|
import ReactiveButton from "~/components/ReactiveButton";
|
||||||
import UserMenu from "~/components/UserMenu";
|
import UserMenu from "~/components/UserMenu";
|
||||||
import WorkspaceMenu from "~/components/WorkspaceMenu";
|
import WorkspaceMenu from "~/components/WorkspaceMenu";
|
||||||
import { useTheme } from "~/providers/theme";
|
import { useTheme } from "next-themes";
|
||||||
|
|
||||||
interface SideNavigationProps {
|
interface SideNavigationProps {
|
||||||
user: UserType;
|
user: UserType;
|
||||||
@@ -59,9 +59,9 @@ export default function SideNavigation({
|
|||||||
|
|
||||||
const { pathname } = router;
|
const { pathname } = router;
|
||||||
|
|
||||||
const { activeTheme } = useTheme();
|
const { theme, resolvedTheme } = useTheme();
|
||||||
|
|
||||||
const isDarkMode = activeTheme === "dark";
|
const isDarkMode = resolvedTheme === "dark";
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
import { CgDarkMode } from "react-icons/cg";
|
import { CgDarkMode } from "react-icons/cg";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
import { useTheme } from "~/providers/theme";
|
|
||||||
|
|
||||||
const ThemeToggle = () => {
|
const ThemeToggle = () => {
|
||||||
const { themePreference, switchTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
switchTheme(themePreference === "light" ? "dark" : "light");
|
setTheme(theme === "light" ? "dark" : "light");
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
className="rounded p-1.5 transition-all hover:bg-light-200 dark:hover:bg-dark-100"
|
className="rounded p-1.5 transition-all hover:bg-light-200 dark:hover:bg-dark-100"
|
||||||
aria-label={`Switch to ${themePreference === "light" ? "dark" : "light"} theme`}
|
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} theme`}
|
||||||
>
|
>
|
||||||
<CgDarkMode
|
<CgDarkMode
|
||||||
className={`h-4 w-4 text-light-900 transition-transform duration-200 dark:text-dark-900 ${
|
className={`h-4 w-4 text-light-900 transition-transform duration-200 dark:text-dark-900 ${
|
||||||
themePreference === "dark" ? "rotate-180" : "rotate-0"
|
theme === "dark" ? "rotate-180" : "rotate-0"
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { authClient } from "@kan/auth/client";
|
|||||||
|
|
||||||
import { useIsMobile } from "~/hooks/useMediaQuery";
|
import { useIsMobile } from "~/hooks/useMediaQuery";
|
||||||
import { useModal } from "~/providers/modal";
|
import { useModal } from "~/providers/modal";
|
||||||
import { useTheme } from "~/providers/theme";
|
import { useTheme } from "next-themes";
|
||||||
import { getAvatarUrl } from "~/utils/helpers";
|
import { getAvatarUrl } from "~/utils/helpers";
|
||||||
|
|
||||||
interface UserMenuProps {
|
interface UserMenuProps {
|
||||||
@@ -29,7 +29,7 @@ export default function UserMenu({
|
|||||||
onCloseSideNav,
|
onCloseSideNav,
|
||||||
}: UserMenuProps) {
|
}: UserMenuProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { themePreference, switchTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
@@ -127,12 +127,12 @@ export default function UserMenu({
|
|||||||
</div>
|
</div>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<button
|
<button
|
||||||
onClick={() => switchTheme("system")}
|
onClick={() => setTheme("system")}
|
||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
themePreference === "system" ? "visible" : "invisible",
|
theme === "system" ? "visible" : "invisible",
|
||||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -141,12 +141,12 @@ export default function UserMenu({
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<button
|
<button
|
||||||
onClick={() => switchTheme("dark")}
|
onClick={() => setTheme("dark")}
|
||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
themePreference === "dark" ? "visible" : "invisible",
|
theme === "dark" ? "visible" : "invisible",
|
||||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -155,12 +155,12 @@ export default function UserMenu({
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<button
|
<button
|
||||||
onClick={() => switchTheme("light")}
|
onClick={() => setTheme("light")}
|
||||||
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
className="flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
themePreference === "light" ? "visible" : "invisible",
|
theme === "light" ? "visible" : "invisible",
|
||||||
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
"mr-4 h-[6px] w-[6px] rounded-full bg-light-900 dark:bg-dark-900",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { useEffect } from "react";
|
|||||||
import { LinguiProviderWrapper } from "~/providers/lingui";
|
import { LinguiProviderWrapper } from "~/providers/lingui";
|
||||||
import { ModalProvider } from "~/providers/modal";
|
import { ModalProvider } from "~/providers/modal";
|
||||||
import { PopupProvider } from "~/providers/popup";
|
import { PopupProvider } from "~/providers/popup";
|
||||||
import { ThemeProvider } from "~/providers/theme";
|
import { ThemeProvider } from "next-themes";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
const jakarta = Plus_Jakarta_Sans({
|
const jakarta = Plus_Jakarta_Sans({
|
||||||
@@ -81,9 +81,9 @@ const MyApp: AppType = ({ Component, pageProps }: AppPropsWithLayout) => {
|
|||||||
<script src="/__ENV.js" />
|
<script src="/__ENV.js" />
|
||||||
<main className="font-sans">
|
<main className="font-sans">
|
||||||
<LinguiProviderWrapper>
|
<LinguiProviderWrapper>
|
||||||
<ThemeProvider>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
<PopupProvider>
|
<PopupProvider>
|
||||||
{posthogKey ? (
|
{posthogKey ? (
|
||||||
<PostHogProvider client={posthog}>
|
<PostHogProvider client={posthog}>
|
||||||
{getLayout(<Component {...pageProps} />)}
|
{getLayout(<Component {...pageProps} />)}
|
||||||
@@ -91,8 +91,8 @@ const MyApp: AppType = ({ Component, pageProps }: AppPropsWithLayout) => {
|
|||||||
) : (
|
) : (
|
||||||
getLayout(<Component {...pageProps} />)
|
getLayout(<Component {...pageProps} />)
|
||||||
)}
|
)}
|
||||||
</PopupProvider>
|
</PopupProvider>
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</LinguiProviderWrapper>
|
</LinguiProviderWrapper>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import PatternedBackground from "~/components/PatternedBackground";
|
import PatternedBackground from "~/components/PatternedBackground";
|
||||||
import { useTheme } from "~/providers/theme";
|
import { useTheme } from "next-themes";
|
||||||
import Footer from "./Footer";
|
import Footer from "./Footer";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import { authClient } from "@kan/auth/client";
|
import { authClient } from "@kan/auth/client";
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const theme = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const { data: session } = authClient.useSession();
|
const { data: session } = authClient.useSession();
|
||||||
|
|
||||||
const isLoggedIn = !!session?.user;
|
const isLoggedIn = !!session?.user;
|
||||||
|
|
||||||
const isDarkMode = theme.activeTheme === "dark";
|
const isDarkMode = theme === "dark";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { IoLogoGithub, IoLogoHackernews } from "react-icons/io";
|
|||||||
|
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import { PageHead } from "~/components/PageHead";
|
import { PageHead } from "~/components/PageHead";
|
||||||
import { useTheme } from "~/providers/theme";
|
import { useTheme } from "next-themes";
|
||||||
import Cta from "./components/Cta";
|
import Cta from "./components/Cta";
|
||||||
import FAQs from "./components/Faqs";
|
import FAQs from "./components/Faqs";
|
||||||
import Features from "./components/Features";
|
import Features from "./components/Features";
|
||||||
@@ -14,9 +14,10 @@ import Layout from "./components/Layout";
|
|||||||
import Pricing from "./components/Pricing";
|
import Pricing from "./components/Pricing";
|
||||||
|
|
||||||
export default function HomeView() {
|
export default function HomeView() {
|
||||||
const theme = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
const isDarkMode = resolvedTheme === "dark";
|
||||||
|
|
||||||
const isDarkMode = theme.activeTheme === "dark";
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<PageHead title="Kan.bn | The open source alternative to Trello" />
|
<PageHead title="Kan.bn | The open source alternative to Trello" />
|
||||||
@@ -88,19 +89,27 @@ export default function HomeView() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="px-4">
|
<div className="px-4">
|
||||||
<div className="mb-24 rounded-[16px] border border-light-300 bg-light-50 p-1 shadow-md dark:border-dark-300 dark:bg-dark-100 lg:rounded-[24px] lg:p-2">
|
<div className="mb-24 rounded-[16px] border border-light-300 bg-light-50 p-1 shadow-md dark:border-dark-300 dark:bg-dark-100 lg:rounded-[24px] lg:p-2">
|
||||||
<div className="overflow-hidden rounded-[12px] border border-light-300 shadow-sm dark:border-dark-300 lg:rounded-[16px]">
|
<div className="relative overflow-hidden rounded-[12px] border border-light-300 shadow-sm dark:border-dark-300 lg:rounded-[16px]">
|
||||||
<Image
|
<Image
|
||||||
src={`/hero-${isDarkMode ? "dark" : "light"}.png`}
|
src={`/hero-light.png`}
|
||||||
alt="kanban"
|
alt="kanban"
|
||||||
width={1100}
|
width={1100}
|
||||||
height={1000}
|
height={1000}
|
||||||
|
className="block dark:hidden"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
src={`/hero-dark.png`}
|
||||||
|
alt="kanban"
|
||||||
|
width={1100}
|
||||||
|
height={1000}
|
||||||
|
className="hidden dark:block"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative pt-10">
|
<div className="relative pt-10">
|
||||||
<div id="features" className="absolute -top-20" />
|
<div id="features" className="absolute -top-20" />
|
||||||
<Features theme={theme.activeTheme} />
|
<Features theme={resolvedTheme === "dark" ? "dark" : "light"} />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative pt-10">
|
<div className="relative pt-10">
|
||||||
<div id="pricing" className="absolute -top-20" />
|
<div id="pricing" className="absolute -top-20" />
|
||||||
@@ -111,7 +120,7 @@ export default function HomeView() {
|
|||||||
<FAQs />
|
<FAQs />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Cta theme={theme.activeTheme} />
|
<Cta theme={resolvedTheme ?? "light"} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function PublicBoardView() {
|
|||||||
members: formatToArray(router.query.members),
|
members: formatToArray(router.query.members),
|
||||||
labels: formatToArray(router.query.labels),
|
labels: formatToArray(router.query.labels),
|
||||||
},
|
},
|
||||||
{ enabled: !!boardSlug, placeholderData: keepPreviousData },
|
{ enabled: router.isReady && !!boardSlug, placeholderData: keepPreviousData },
|
||||||
);
|
);
|
||||||
|
|
||||||
const CopyBoardLink = () => {
|
const CopyBoardLink = () => {
|
||||||
@@ -107,7 +107,7 @@ export default function PublicBoardView() {
|
|||||||
<div className="relative h-full overflow-hidden rounded-md border pb-8 dark:border-dark-200">
|
<div className="relative h-full overflow-hidden rounded-md border pb-8 dark:border-dark-200">
|
||||||
<PatternedBackground />
|
<PatternedBackground />
|
||||||
<div className="z-10 flex w-full justify-between p-8">
|
<div className="z-10 flex w-full justify-between p-8">
|
||||||
{isLoading ? (
|
{isLoading || !router.isReady ? (
|
||||||
<div className="flex space-x-2">
|
<div className="flex space-x-2">
|
||||||
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-100" />
|
<div className="h-[2.3rem] w-[150px] animate-pulse rounded-[5px] bg-light-200 dark:bg-dark-100" />
|
||||||
</div>
|
</div>
|
||||||
@@ -134,13 +134,13 @@ export default function PublicBoardView() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="scrollbar-w-none scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] relative h-full flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
|
<div className="scrollbar-w-none scrollbar-track-rounded-[4px] scrollbar-thumb-rounded-[4px] scrollbar-h-[8px] relative h-full flex-1 overflow-y-hidden overflow-x-scroll overscroll-contain scrollbar scrollbar-track-light-200 scrollbar-thumb-light-400 dark:scrollbar-track-dark-100 dark:scrollbar-thumb-dark-300">
|
||||||
{isLoading ? (
|
{isLoading || !router.isReady ? (
|
||||||
<div className="ml-[2rem] flex">
|
<div className="ml-[2rem] flex">
|
||||||
<div className="0 mr-5 h-[500px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
|
<div className="0 mr-5 h-[500px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
|
||||||
<div className="0 mr-5 h-[275px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
|
<div className="0 mr-5 h-[275px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
|
||||||
<div className="0 mr-5 h-[375px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
|
<div className="0 mr-5 h-[375px] w-[18rem] animate-pulse rounded-md bg-light-200 dark:bg-dark-100" />
|
||||||
</div>
|
</div>
|
||||||
) : !data ? (
|
) : !data && !isLoading && router.isReady && !!boardSlug ? (
|
||||||
<div className="z-10 flex h-full w-full flex-col items-center justify-center space-y-8 pb-[150px]">
|
<div className="z-10 flex h-full w-full flex-col items-center justify-center space-y-8 pb-[150px]">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<HiOutlineLockClosed className="h-10 w-10 text-light-800 dark:text-dark-800" />
|
<HiOutlineLockClosed className="h-10 w-10 text-light-800 dark:text-dark-800" />
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { fontFamily } from "tailwindcss/defaultTheme";
|
|||||||
import baseConfig from "@kan/tailwind-config/web";
|
import baseConfig from "@kan/tailwind-config/web";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
darkMode: "class",
|
||||||
content: [...baseConfig.content],
|
content: [...baseConfig.content],
|
||||||
plugins: [require("@tailwindcss/typography")],
|
plugins: [require("@tailwindcss/typography")],
|
||||||
presets: [baseConfig],
|
presets: [baseConfig],
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -171,6 +171,9 @@ importers:
|
|||||||
next-runtime-env:
|
next-runtime-env:
|
||||||
specifier: ^1.7.2
|
specifier: ^1.7.2
|
||||||
version: 1.8.0
|
version: 1.8.0
|
||||||
|
next-themes:
|
||||||
|
specifier: ^0.4.6
|
||||||
|
version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
nextjs-cors:
|
nextjs-cors:
|
||||||
specifier: ^2.2.0
|
specifier: ^2.2.0
|
||||||
version: 2.2.0(next@15.3.4(@babel/core@7.24.5)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
version: 2.2.0(next@15.3.4(@babel/core@7.24.5)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||||
|
|||||||
Reference in New Issue
Block a user