mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-05 17:46:37 +08:00
feat(frontend): add customizable favicon and corner logo with live preview (#218)
* feat(frontend): add customizable favicon and corner logo with live preview - Add app_logo and app_favicon config fields in config page - Implement BrandLogo component with URL fallback to SVG logo - Integrate BrandLogo into all layouts (admin, h5, h5-simple) and navbar - Add real-time preview for favicon and logo in config page with error state - Support both relative paths and full image URLs for branding assets - Sync branding changes (app_name/app_logo/app_favicon) to site config on save * fix(frontend): preserve tunnel node selection order * fix(select): use useMemo for option label map to improve performance
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { siteConfig, getCachedConfigs } from "@/config/site";
|
||||
import { Logo } from "@/components/icons";
|
||||
|
||||
type BrandLogoProps = {
|
||||
size?: number;
|
||||
className?: string;
|
||||
alt?: string;
|
||||
};
|
||||
|
||||
export const BrandLogo = ({
|
||||
size = 24,
|
||||
className,
|
||||
alt = "brand logo",
|
||||
}: BrandLogoProps) => {
|
||||
const [logoUrl, setLogoUrl] = useState<string>(siteConfig.app_logo || "");
|
||||
const [imageFailed, setImageFailed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
const syncLogo = async () => {
|
||||
try {
|
||||
const configMap = await getCachedConfigs();
|
||||
const nextLogo = (
|
||||
configMap.app_logo ||
|
||||
siteConfig.app_logo ||
|
||||
""
|
||||
).trim();
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
siteConfig.app_logo = nextLogo;
|
||||
setLogoUrl(nextLogo);
|
||||
setImageFailed(false);
|
||||
} catch {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setLogoUrl((siteConfig.app_logo || "").trim());
|
||||
}
|
||||
};
|
||||
|
||||
void syncLogo();
|
||||
|
||||
const handleConfigUpdate = () => {
|
||||
const nextLogo = (siteConfig.app_logo || "").trim();
|
||||
|
||||
setLogoUrl(nextLogo);
|
||||
setImageFailed(false);
|
||||
void syncLogo();
|
||||
};
|
||||
|
||||
window.addEventListener("configUpdated", handleConfigUpdate);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
window.removeEventListener("configUpdated", handleConfigUpdate);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!logoUrl || imageFailed) {
|
||||
return <Logo className={className} size={size} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
alt={alt}
|
||||
className={className}
|
||||
height={size}
|
||||
src={logoUrl}
|
||||
width={size}
|
||||
onError={() => setImageFailed(true)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
NavbarBrand,
|
||||
NavbarContent,
|
||||
} from "@/shadcn-bridge/heroui/navbar";
|
||||
import { Logo } from "@/components/icons";
|
||||
import { BrandLogo } from "@/components/brand-logo";
|
||||
import { siteConfig, getCachedConfig } from "@/config/site";
|
||||
import { useWebViewMode } from "@/hooks/useWebViewMode";
|
||||
|
||||
@@ -69,7 +69,7 @@ export const Navbar = () => {
|
||||
color="foreground"
|
||||
href="/"
|
||||
>
|
||||
<Logo size={24} />
|
||||
<BrandLogo size={24} />
|
||||
<p className="font-bold text-inherit truncate">{appName}</p>
|
||||
</Link>
|
||||
</NavbarBrand>
|
||||
|
||||
Reference in New Issue
Block a user