chore: refactor website configuration structure and update related components

This commit is contained in:
javayhu 2025-04-17 01:18:57 +08:00
parent 377f22c693
commit 4b1d888845
13 changed files with 85 additions and 33 deletions

View File

@ -57,9 +57,10 @@ export default async function DocsRootLayout({ children, params }: DocsLayoutPro
};
// Docs layout configurations
const showLocaleSwitch = Object.keys(websiteConfig.i18n.locales).length > 1;
const docsOptions: BaseLayoutProps = {
i18n: docsI18nConfig,
githubUrl: websiteConfig.metadata.social.github ?? undefined,
i18n: showLocaleSwitch ? docsI18nConfig : undefined,
githubUrl: websiteConfig.metadata.social?.github ?? undefined,
nav: {
url: getUrlWithLocale('/docs', locale),
title: (
@ -84,7 +85,7 @@ export default async function DocsRootLayout({ children, params }: DocsLayoutPro
active: 'none',
external: false,
},
...(websiteConfig.metadata.social.twitter
...(websiteConfig.metadata.social?.twitter
? [
{
type: "icon" as const,

View File

@ -3,6 +3,7 @@
import { ActiveThemeProvider } from '@/components/layout/active-theme-provider';
import { PaymentProvider } from '@/components/layout/payment-provider';
import { TooltipProvider } from '@/components/ui/tooltip';
import { websiteConfig } from '@/config/website';
import { RootProvider } from 'fumadocs-ui/provider';
import { ThemeProvider, useTheme } from 'next-themes';
import { PropsWithChildren } from 'react';
@ -20,12 +21,13 @@ import { PropsWithChildren } from 'react';
*/
export function Providers({ children }: PropsWithChildren) {
const theme = useTheme();
const defaultMode = websiteConfig.metadata.mode?.defaultMode ?? "system";
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
defaultTheme={defaultMode}
enableSystem={true}
disableTransitionOnChange
>
<ActiveThemeProvider>

View File

@ -73,6 +73,9 @@ export function SidebarUser({ user, className }: SidebarUserProps) {
});
}
const showModeSwitch = websiteConfig.metadata.mode?.enableSwitch ?? false;
const showLocaleSwitch = LOCALES.length > 1;
const handleSignOut = async () => {
await authClient.signOut({
fetchOptions: {
@ -137,11 +140,17 @@ export function SidebarUser({ user, className }: SidebarUserProps) {
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
{
(showModeSwitch || showLocaleSwitch) && (
<DropdownMenuSeparator />
)
}
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger className="cursor-pointer">
{
showModeSwitch && (
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger className="cursor-pointer">
<LaptopIcon className="mr-2 size-4" />
<span>{t('Common.mode.label')}</span>
</DropdownMenuSubTrigger>
@ -167,13 +176,14 @@ export function SidebarUser({ user, className }: SidebarUserProps) {
<LaptopIcon className="mr-2 size-4" />
<span>{t('Common.mode.system')}</span>
</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuGroup>
)
}
{
LOCALES.length > 1 && (
showLocaleSwitch && (
<DropdownMenuGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger className="cursor-pointer">

View File

@ -10,7 +10,7 @@ import {
} from "react";
const COOKIE_NAME = "active_theme";
const DEFAULT_THEME = websiteConfig.metadata.theme ?? "default";
const DEFAULT_THEME = websiteConfig.metadata.theme?.defaultTheme ?? "default";
function setThemeCookie(theme: string) {
if (typeof window === "undefined") return;

View File

@ -9,7 +9,7 @@ import {
} from '@/components/ui/select';
import { websiteConfig } from '@/config/website';
import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation';
import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing';
import { DEFAULT_LOCALE } from '@/i18n/routing';
import { useLocaleStore } from '@/stores/locale-store';
import { Locale, useLocale } from 'next-intl';
import { useParams } from 'next/navigation';
@ -27,7 +27,8 @@ import { useEffect, useTransition } from 'react';
*/
export default function LocaleSelector() {
// Return null if there's only one locale available
if (LOCALES.length <= 1) {
const showLocaleSwitch = Object.keys(websiteConfig.i18n.locales).length > 1;
if (!showLocaleSwitch) {
return null;
}

View File

@ -9,7 +9,6 @@ import {
} from '@/components/ui/dropdown-menu';
import { websiteConfig } from '@/config/website';
import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation';
import { LOCALES } from '@/i18n/routing';
import { useLocaleStore } from '@/stores/locale-store';
import { Languages } from 'lucide-react';
import { Locale, useLocale, useTranslations } from 'next-intl';
@ -26,7 +25,8 @@ import { useEffect, useTransition } from 'react';
*/
export default function LocaleSwitcher() {
// Return null if there's only one locale available
if (LOCALES.length <= 1) {
const showLocaleSwitch = Object.keys(websiteConfig.i18n.locales).length > 1;
if (!showLocaleSwitch) {
return null;
}

View File

@ -9,8 +9,8 @@ import { useEffect, useState } from 'react';
export function Logo({ className }: { className?: string }) {
const { theme } = useTheme();
const [mounted, setMounted] = useState(false);
const logoLight = websiteConfig.metadata.logoLight ?? '/logo.png';
const logoDark = websiteConfig.metadata.logoDark ?? logoLight;
const logoLight = websiteConfig.metadata.images?.logoLight ?? '/logo.png';
const logoDark = websiteConfig.metadata.images?.logoDark ?? logoLight;
// During server-side rendering and initial client render, always use logoLight
// This prevents hydration mismatch

View File

@ -1,16 +1,21 @@
'use client';
import { Button } from '@/components/ui/button';
import { LaptopIcon, MoonIcon, SunIcon } from 'lucide-react';
import { useTheme } from 'next-themes';
import { websiteConfig } from '@/config/website';
import { cn } from '@/lib/utils';
import { useEffect, useState } from 'react';
import { LaptopIcon, MoonIcon, SunIcon } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
/**
* Mode switcher component, used in the footer
*/
export function ModeSwitcherHorizontal() {
if (!websiteConfig.metadata.mode?.enableSwitch) {
return null;
}
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
const t = useTranslations('Common.mode');

View File

@ -7,6 +7,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { websiteConfig } from '@/config/website';
import { LaptopIcon, MoonIcon, SunIcon } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useTheme } from 'next-themes';
@ -15,6 +16,10 @@ import { useTheme } from 'next-themes';
* Mode switcher component, used in the navbar
*/
export function ModeSwitcher() {
if (!websiteConfig.metadata.mode?.enableSwitch) {
return null;
}
const { setTheme } = useTheme();
const t = useTranslations('Common.mode');

View File

@ -6,10 +6,10 @@ import {
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue
} from "@/components/ui/select";
import { websiteConfig } from "@/config/website";
import { useTranslations } from "next-intl";
import { useThemeConfig } from "./active-theme-provider";
@ -21,6 +21,10 @@ import { useThemeConfig } from "./active-theme-provider";
* https://github.com/TheOrcDev/orcish-dashboard/blob/main/components/theme-selector.tsx
*/
export function ThemeSelector() {
if (!websiteConfig.metadata.theme?.enableSwitch) {
return null;
}
const { activeTheme, setActiveTheme } = useThemeConfig();
const t = useTranslations('Common.theme');

View File

@ -6,10 +6,19 @@ import { WebsiteConfig } from '@/types';
*/
export const websiteConfig: WebsiteConfig = {
metadata: {
theme: "default",
ogImage: '/og.png',
logoLight: '/logo.png',
logoDark: '/logo-dark.png',
theme: {
defaultTheme: "default",
enableSwitch: true,
},
mode: {
defaultMode: "system",
enableSwitch: true,
},
images: {
ogImage: '/og.png',
logoLight: '/logo.png',
logoDark: '/logo-dark.png',
},
social: {
github: 'https://github.com/MkSaaSHQ',
twitter: 'https://x.com/mksaascom',

View File

@ -24,7 +24,7 @@ export function constructMetadata({
} = {}): Metadata {
title = title || defaultMessages.Metadata.name;
description = description || defaultMessages.Metadata.description;
image = image || websiteConfig.metadata.ogImage;
image = image || websiteConfig.metadata.images?.ogImage;
const ogImageUrl = new URL(`${getBaseUrl()}${image}`);
return {
title,

19
src/types/index.d.ts vendored
View File

@ -17,11 +17,26 @@ export type WebsiteConfig = {
* Website metadata
*/
export interface MetadataConfig {
theme?: "default" | "blue" | "green" | "amber" | "neutral"; // The theme
mode?: ModeConfig;
theme?: ThemeConfig;
images?: ImagesConfig;
social?: SocialConfig;
}
export interface ModeConfig {
defaultMode?: "light" | "dark" | "system"; // The default mode of the website
enableSwitch?: boolean; // Whether to enable the mode switch
}
export interface ThemeConfig {
defaultTheme?: "default" | "blue" | "green" | "amber" | "neutral"; // The default theme of the website
enableSwitch?: boolean; // Whether to enable the theme switch
}
export interface ImagesConfig {
ogImage?: string; // The image as Open Graph image
logoLight?: string; // The light logo image
logoDark?: string; // The dark logo image
social: SocialConfig; // The social media configuration
}
/**