feat: add server settings for default board, default color scheme and default locale (#1373)

* feat: add server settings for default board, default color scheme and default locale

* chore: address pull request feedback

* test: adjust unit tests to match requirements

* fix: deepsource issue

* chore: add deepsource as dependency to translation library

* refactor: restructure language-combobox, adjust default locale for next-intl

* chore: change cookie keys prefix from homarr- to homarr.
This commit is contained in:
Meier Lukas
2024-11-02 21:15:46 +01:00
committed by GitHub
parent 49c0ebea6d
commit 326b769c23
42 changed files with 599 additions and 214 deletions

View File

@@ -0,0 +1,12 @@
"use client";
import { useChangeLocale, useCurrentLocale } from "@homarr/translation/client";
import { LanguageCombobox } from "./language-combobox";
export const CurrentLanguageCombobox = () => {
const currentLocale = useCurrentLocale();
const { changeLocale, isPending } = useChangeLocale();
return <LanguageCombobox value={currentLocale} onChange={changeLocale} isPending={isPending} />;
};

View File

@@ -6,26 +6,30 @@ import { IconCheck } from "@tabler/icons-react";
import type { SupportedLanguage } from "@homarr/translation";
import { localeConfigurations, supportedLanguages } from "@homarr/translation";
import { useChangeLocale, useCurrentLocale } from "@homarr/translation/client";
import classes from "./language-combobox.module.css";
export const LanguageCombobox = () => {
interface LanguageComboboxProps {
label?: string;
value: SupportedLanguage;
onChange: (value: SupportedLanguage) => void;
isPending?: boolean;
}
export const LanguageCombobox = ({ label, value, onChange, isPending }: LanguageComboboxProps) => {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const currentLocale = useCurrentLocale();
const { changeLocale, isPending } = useChangeLocale();
const handleOnOptionSubmit = React.useCallback(
(value: string) => {
if (!value) {
return;
}
changeLocale(value as SupportedLanguage);
onChange(value as SupportedLanguage);
combobox.closeDropdown();
},
[changeLocale, combobox],
[onChange, combobox],
);
const handleOnClick = React.useCallback(() => {
@@ -39,20 +43,21 @@ export const LanguageCombobox = () => {
component="button"
type="button"
pointer
label={label}
leftSection={isPending ? <Loader size={16} /> : null}
rightSection={<Combobox.Chevron />}
rightSectionPointerEvents="none"
onClick={handleOnClick}
variant="filled"
>
<OptionItem currentLocale={currentLocale} localeKey={currentLocale} />
<OptionItem currentLocale={value} localeKey={value} />
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>
{supportedLanguages.map((languageKey) => (
<Combobox.Option value={languageKey} key={languageKey}>
<OptionItem currentLocale={currentLocale} localeKey={languageKey} showCheck />
<OptionItem currentLocale={value} localeKey={languageKey} showCheck />
</Combobox.Option>
))}
</Combobox.Options>

View File

@@ -1,11 +1,12 @@
import Script from "next/script";
import { UMAMI_WEBSITE_ID } from "@homarr/analytics";
import { api } from "@homarr/api/server";
import { db } from "@homarr/db";
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
export const Analytics = async () => {
// For static pages it will not find any analytics data so we do not include the script on them
const analytics = await api.serverSettings.getAnalytics().catch(() => null);
const analytics = await getServerSettingByKeyAsync(db, "analytics").catch(() => null);
if (analytics?.enableGeneral) {
return <Script src="https://umami.homarr.dev/script.js" data-website-id={UMAMI_WEBSITE_ID} defer />;

View File

@@ -1,28 +1,28 @@
import SuperJSON from "superjson";
import { db, eq } from "@homarr/db";
import { serverSettings } from "@homarr/db/schema/sqlite";
import type { defaultServerSettings } from "@homarr/server-settings";
import { db } from "@homarr/db";
import { getServerSettingByKeyAsync } from "@homarr/db/queries";
export const SearchEngineOptimization = async () => {
const crawlingAndIndexingSetting = await db.query.serverSettings.findFirst({
where: eq(serverSettings.settingKey, "crawlingAndIndexing"),
});
const crawlingAndIndexingSetting = await getServerSettingByKeyAsync(db, "crawlingAndIndexing");
if (!crawlingAndIndexingSetting) {
return null;
const robotsAttributes: string[] = [];
if (crawlingAndIndexingSetting.noIndex) {
robotsAttributes.push("noindex");
}
const value = SuperJSON.parse<(typeof defaultServerSettings)["crawlingAndIndexing"]>(
crawlingAndIndexingSetting.value,
);
if (crawlingAndIndexingSetting.noFollow) {
robotsAttributes.push("nofollow");
}
const robotsAttributes = [...(value.noIndex ? ["noindex"] : []), ...(value.noIndex ? ["nofollow"] : [])];
const googleAttributes: string[] = [];
const googleAttributes = [
...(value.noSiteLinksSearchBox ? ["nositelinkssearchbox"] : []),
...(value.noTranslate ? ["notranslate"] : []),
];
if (crawlingAndIndexingSetting.noSiteLinksSearchBox) {
googleAttributes.push("nositelinkssearchbox");
}
if (crawlingAndIndexingSetting.noTranslate) {
googleAttributes.push("notranslate");
}
return (
<>

View File

@@ -24,7 +24,7 @@ import { useScopedI18n } from "@homarr/translation/client";
import "flag-icons/css/flag-icons.min.css";
import { useAuthContext } from "~/app/[locale]/_client-providers/session";
import { LanguageCombobox } from "./language/language-combobox";
import { CurrentLanguageCombobox } from "./language/current-language-combobox";
interface UserAvatarMenuProps {
children: ReactNode;
@@ -72,7 +72,7 @@ export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
<Menu.Divider />
<Menu.Item p={0} closeMenuOnClick={false}>
<LanguageCombobox />
<CurrentLanguageCombobox />
</Menu.Item>
<Menu.Divider />
{Boolean(session.data) && (