mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-13 17:05:47 +01:00
♻️ Add ssr for user preferences page, add translations for user preferences page
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
|
"metaTitle": "Preferences",
|
||||||
|
"pageTitle": "Your preferences",
|
||||||
"boards": {
|
"boards": {
|
||||||
"title": "Boards",
|
|
||||||
"defaultBoard": {
|
"defaultBoard": {
|
||||||
"label": "Default board"
|
"label": "Default board"
|
||||||
}
|
}
|
||||||
@@ -21,7 +22,12 @@
|
|||||||
"label": "Language"
|
"label": "Language"
|
||||||
},
|
},
|
||||||
"firstDayOfWeek": {
|
"firstDayOfWeek": {
|
||||||
"label": "First day of the week"
|
"label": "First day of the week",
|
||||||
|
"options": {
|
||||||
|
"monday": "Monday",
|
||||||
|
"saturday": "Saturday",
|
||||||
|
"sunday": "Sunday"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"searchEngine": {
|
"searchEngine": {
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ import {
|
|||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useDisclosure } from '@mantine/hooks';
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
import { IconAlertTriangle, IconClick, IconListSearch } from '@tabler/icons-react';
|
import { IconAlertTriangle, IconClick, IconListSearch } from '@tabler/icons-react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { City } from '~/server/api/routers/weather';
|
import { City } from '~/server/api/routers/weather';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
import { IntegrationOptionsValueType } from '../WidgetsEditModal';
|
import { IntegrationOptionsValueType } from '../WidgetsEditModal';
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
type LocationSelectionProps = {
|
type LocationSelectionProps = {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
@@ -220,7 +219,10 @@ const CitySelectModal = ({ opened, closeModal, query, onCitySelected }: CitySele
|
|||||||
<Text style={{ whiteSpace: 'nowrap' }}>{city.country}</Text>
|
<Text style={{ whiteSpace: 'nowrap' }}>{city.country}</Text>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Anchor target='_blank' href={`https://www.google.com/maps/place/${city.latitude},${city.longitude}`}>
|
<Anchor
|
||||||
|
target="_blank"
|
||||||
|
href={`https://www.google.com/maps/place/${city.latitude},${city.longitude}`}
|
||||||
|
>
|
||||||
<Text style={{ whiteSpace: 'nowrap' }}>
|
<Text style={{ whiteSpace: 'nowrap' }}>
|
||||||
{city.latitude}, {city.longitude}
|
{city.latitude}, {city.longitude}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -228,7 +230,9 @@ const CitySelectModal = ({ opened, closeModal, query, onCitySelected }: CitySele
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{city.population ? (
|
{city.population ? (
|
||||||
<Text style={{ whiteSpace: 'nowrap' }}>{formatter.format(city.population)}</Text>
|
<Text style={{ whiteSpace: 'nowrap' }}>
|
||||||
|
{formatter.format(city.population)}
|
||||||
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text color="dimmed"> {t('modal.table.population.fallback')}</Text>
|
<Text color="dimmed"> {t('modal.table.population.fallback')}</Text>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Stack, Switch } from '@mantine/core';
|
import { Stack, Switch } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
||||||
|
|
||||||
export const AccessibilitySettings = () => {
|
export const AccessibilitySettings = () => {
|
||||||
|
|||||||
@@ -3,22 +3,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
||||||
|
|
||||||
const searchEngineOptions = [
|
export const SearchEngineSettings = () => {
|
||||||
{ label: 'Google', value: 'https://google.com/search?q=%s' },
|
|
||||||
{ label: 'DuckDuckGo', value: 'https://duckduckgo.com/?q=%s' },
|
|
||||||
{ label: 'Bing', value: 'https://bing.com/search?q=%s' },
|
|
||||||
{ value: 'custom' },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const useSegmentData = () => {
|
|
||||||
const { t } = useTranslation('user/preferences');
|
|
||||||
return searchEngineOptions.map((option) => ({
|
|
||||||
label: option.value === 'custom' ? t('searchEngine.custom') : option.label,
|
|
||||||
value: option.value,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SearchEngineSelector = () => {
|
|
||||||
const { t } = useTranslation('user/preferences');
|
const { t } = useTranslation('user/preferences');
|
||||||
const form = useUserPreferencesFormContext();
|
const form = useUserPreferencesFormContext();
|
||||||
const segmentData = useSegmentData();
|
const segmentData = useSegmentData();
|
||||||
@@ -51,6 +36,7 @@ export const SearchEngineSelector = () => {
|
|||||||
label={t('searchEngine.template.label')}
|
label={t('searchEngine.template.label')}
|
||||||
description={t('searchEngine.template.description')}
|
description={t('searchEngine.template.description')}
|
||||||
inputWrapperOrder={['label', 'input', 'description', 'error']}
|
inputWrapperOrder={['label', 'input', 'description', 'error']}
|
||||||
|
withAsterisk
|
||||||
{...form.getInputProps('searchTemplate')}
|
{...form.getInputProps('searchTemplate')}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -58,3 +44,18 @@ export const SearchEngineSelector = () => {
|
|||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const searchEngineOptions = [
|
||||||
|
{ label: 'Google', value: 'https://google.com/search?q=%s' },
|
||||||
|
{ label: 'DuckDuckGo', value: 'https://duckduckgo.com/?q=%s' },
|
||||||
|
{ label: 'Bing', value: 'https://bing.com/search?q=%s' },
|
||||||
|
{ value: 'custom' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const useSegmentData = () => {
|
||||||
|
const { t } = useTranslation('user/preferences');
|
||||||
|
return searchEngineOptions.map((option) => ({
|
||||||
|
label: option.value === 'custom' ? t('searchEngine.custom') : option.label,
|
||||||
|
value: option.value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,16 +10,17 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { createFormContext } from '@mantine/form';
|
import { createFormContext } from '@mantine/form';
|
||||||
import type { InferGetServerSidePropsType } from 'next';
|
import { createServerSideHelpers } from '@trpc/react-query/server';
|
||||||
import { GetServerSidePropsContext } from 'next';
|
import { GetServerSideProps, GetServerSidePropsContext } from 'next';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { AccessibilitySettings } from '~/components/User/Preferences/AccessibilitySettings';
|
import { AccessibilitySettings } from '~/components/User/Preferences/AccessibilitySettings';
|
||||||
import { SearchEngineSelector } from '~/components/User/Preferences/SearchEngineSelector';
|
import { SearchEngineSettings } from '~/components/User/Preferences/SearchEngineSelector';
|
||||||
import { MainLayout } from '~/components/layout/Templates/MainLayout';
|
import { MainLayout } from '~/components/layout/Templates/MainLayout';
|
||||||
import { sleep } from '~/tools/client/time';
|
import { createTrpcServersideHelpers } from '~/server/api/helper';
|
||||||
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { languages } from '~/tools/language';
|
import { languages } from '~/tools/language';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
@@ -27,18 +28,19 @@ import { RouterOutputs, api } from '~/utils/api';
|
|||||||
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
||||||
import { updateSettingsValidationSchema } from '~/validations/user';
|
import { updateSettingsValidationSchema } from '~/validations/user';
|
||||||
|
|
||||||
const PreferencesPage = ({ locale }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
|
const PreferencesPage = () => {
|
||||||
const { data } = api.user.withSettings.useQuery();
|
const { data } = api.user.withSettings.useQuery();
|
||||||
const { data: boardsData } = api.boards.all.useQuery();
|
const { data: boardsData } = api.boards.all.useQuery();
|
||||||
|
const { t } = useTranslation('user/preferences');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<Container>
|
<Container>
|
||||||
<Paper p="xl" mih="100%" withBorder>
|
<Paper p="xl" mih="100%" withBorder>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Preferences • Homarr</title>
|
<title>{t('metaTitle')} • Homarr</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Title mb="xl">Preferences</Title>
|
<Title mb="xl">{t('pageTitle')}</Title>
|
||||||
|
|
||||||
{data && boardsData && (
|
{data && boardsData && (
|
||||||
<SettingsComponent settings={data.settings} boardsData={boardsData} />
|
<SettingsComponent settings={data.settings} boardsData={boardsData} />
|
||||||
@@ -103,10 +105,6 @@ const SettingsComponent = ({
|
|||||||
<form style={{ position: 'relative' }} onSubmit={form.onSubmit(handleSubmit)}>
|
<form style={{ position: 'relative' }} onSubmit={form.onSubmit(handleSubmit)}>
|
||||||
<LoadingOverlay visible={isLoading} overlayBlur={2} />
|
<LoadingOverlay visible={isLoading} overlayBlur={2} />
|
||||||
<Stack spacing={5}>
|
<Stack spacing={5}>
|
||||||
<Title order={2} size="lg">
|
|
||||||
{t('boards.title')}
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label={t('boards.defaultBoard.label')}
|
label={t('boards.defaultBoard.label')}
|
||||||
data={boardsData.map((board) => board.name)}
|
data={boardsData.map((board) => board.name)}
|
||||||
@@ -114,14 +112,9 @@ const SettingsComponent = ({
|
|||||||
maxDropdownHeight={400}
|
maxDropdownHeight={400}
|
||||||
filter={(value, item) => item.label!.toLowerCase().includes(value.toLowerCase().trim())}
|
filter={(value, item) => item.label!.toLowerCase().includes(value.toLowerCase().trim())}
|
||||||
withAsterisk
|
withAsterisk
|
||||||
mb="xs"
|
|
||||||
{...form.getInputProps('defaultBoard')}
|
{...form.getInputProps('defaultBoard')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Title order={2} size="lg" mt="lg">
|
|
||||||
{t('localization.language.label')}
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label="Language"
|
label="Language"
|
||||||
itemComponent={SelectItem}
|
itemComponent={SelectItem}
|
||||||
@@ -140,12 +133,12 @@ const SettingsComponent = ({
|
|||||||
|
|
||||||
<Select
|
<Select
|
||||||
label={t('localization.firstDayOfWeek.label')}
|
label={t('localization.firstDayOfWeek.label')}
|
||||||
//TODO: Make it use the configured value
|
withAsterisk
|
||||||
data={[
|
data={firstDayOfWeekOptions.map((day) => ({
|
||||||
{ value: 'monday', label: 'Monday' },
|
label: t(`localization.firstDayOfWeek.options.${day}`) as string,
|
||||||
{ value: 'sunday', label: 'Sunday' },
|
value: day,
|
||||||
{ value: 'saturday', label: 'Saturday' },
|
}))}
|
||||||
]}
|
{...form.getInputProps('firstDayOfWeek')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Title order={2} size="lg" mt="lg" mb="md">
|
<Title order={2} size="lg" mt="lg" mb="md">
|
||||||
@@ -158,10 +151,10 @@ const SettingsComponent = ({
|
|||||||
{t('searchEngine.title')}
|
{t('searchEngine.title')}
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<SearchEngineSelector />
|
<SearchEngineSettings />
|
||||||
|
|
||||||
<Button type="submit" fullWidth mt="md">
|
<Button type="submit" fullWidth mt="md">
|
||||||
Save
|
{t('common:save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
@@ -193,9 +186,23 @@ const SelectItem = forwardRef<HTMLDivElement, ItemProps>(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function getServerSideProps({ req, res, locale }: GetServerSidePropsContext) {
|
const firstDayOfWeekOptions = ['monday', 'sunday', 'saturday'] as const;
|
||||||
|
|
||||||
|
export const getServerSideProps: GetServerSideProps = async ({ req, res, locale }) => {
|
||||||
|
const session = await getServerAuthSession({ req, res });
|
||||||
|
if (!session) {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const helpers = await createTrpcServersideHelpers({ req, res });
|
||||||
|
|
||||||
|
await helpers.user.withSettings.prefetch();
|
||||||
|
await helpers.boards.all.prefetch();
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
const translations = await getServerSideTranslations(
|
||||||
manageNamespaces,
|
['user/preferences'],
|
||||||
locale,
|
locale,
|
||||||
undefined,
|
undefined,
|
||||||
undefined
|
undefined
|
||||||
@@ -204,8 +211,9 @@ export async function getServerSideProps({ req, res, locale }: GetServerSideProp
|
|||||||
props: {
|
props: {
|
||||||
...translations,
|
...translations,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
|
trpcState: helpers.dehydrate(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export default PreferencesPage;
|
export default PreferencesPage;
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export const getServerSideTranslations = async (
|
|||||||
req?: IncomingMessage,
|
req?: IncomingMessage,
|
||||||
res?: ServerResponse
|
res?: ServerResponse
|
||||||
) => {
|
) => {
|
||||||
|
namespaces = namespaces.concat(['common', 'zod']);
|
||||||
|
|
||||||
if (!req || !res) {
|
if (!req || !res) {
|
||||||
return serverSideTranslations(requestLocale ?? 'en', namespaces);
|
return serverSideTranslations(requestLocale ?? 'en', namespaces);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user