mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-13 00:45: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": {
|
||||
"title": "Boards",
|
||||
"defaultBoard": {
|
||||
"label": "Default board"
|
||||
}
|
||||
@@ -21,7 +22,12 @@
|
||||
"label": "Language"
|
||||
},
|
||||
"firstDayOfWeek": {
|
||||
"label": "First day of the week"
|
||||
"label": "First day of the week",
|
||||
"options": {
|
||||
"monday": "Monday",
|
||||
"saturday": "Saturday",
|
||||
"sunday": "Sunday"
|
||||
}
|
||||
}
|
||||
},
|
||||
"searchEngine": {
|
||||
|
||||
@@ -17,13 +17,12 @@ import {
|
||||
} from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { IconAlertTriangle, IconClick, IconListSearch } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { City } from '~/server/api/routers/weather';
|
||||
import { api } from '~/utils/api';
|
||||
|
||||
import { IntegrationOptionsValueType } from '../WidgetsEditModal';
|
||||
import Link from 'next/link';
|
||||
|
||||
type LocationSelectionProps = {
|
||||
widgetId: string;
|
||||
@@ -166,13 +165,13 @@ const CitySelectModal = ({ opened, closeModal, query, onCitySelected }: CitySele
|
||||
onClose={closeModal}
|
||||
zIndex={250}
|
||||
>
|
||||
<Center>
|
||||
<Stack align="center">
|
||||
<IconAlertTriangle />
|
||||
<Title order={6}>Nothing found</Title>
|
||||
<Text>Nothing was found, please try again</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
<Center>
|
||||
<Stack align="center">
|
||||
<IconAlertTriangle />
|
||||
<Title order={6}>Nothing found</Title>
|
||||
<Text>Nothing was found, please try again</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -220,15 +219,20 @@ const CitySelectModal = ({ opened, closeModal, query, onCitySelected }: CitySele
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>{city.country}</Text>
|
||||
</td>
|
||||
<td>
|
||||
<Anchor target='_blank' href={`https://www.google.com/maps/place/${city.latitude},${city.longitude}`}>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>
|
||||
{city.latitude}, {city.longitude}
|
||||
</Text>
|
||||
<Anchor
|
||||
target="_blank"
|
||||
href={`https://www.google.com/maps/place/${city.latitude},${city.longitude}`}
|
||||
>
|
||||
<Text style={{ whiteSpace: 'nowrap' }}>
|
||||
{city.latitude}, {city.longitude}
|
||||
</Text>
|
||||
</Anchor>
|
||||
</td>
|
||||
<td>
|
||||
{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>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Stack, Switch } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
||||
|
||||
export const AccessibilitySettings = () => {
|
||||
|
||||
@@ -3,22 +3,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { useUserPreferencesFormContext } from '~/pages/user/preferences';
|
||||
|
||||
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,
|
||||
}));
|
||||
};
|
||||
|
||||
export const SearchEngineSelector = () => {
|
||||
export const SearchEngineSettings = () => {
|
||||
const { t } = useTranslation('user/preferences');
|
||||
const form = useUserPreferencesFormContext();
|
||||
const segmentData = useSegmentData();
|
||||
@@ -51,6 +36,7 @@ export const SearchEngineSelector = () => {
|
||||
label={t('searchEngine.template.label')}
|
||||
description={t('searchEngine.template.description')}
|
||||
inputWrapperOrder={['label', 'input', 'description', 'error']}
|
||||
withAsterisk
|
||||
{...form.getInputProps('searchTemplate')}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -58,3 +44,18 @@ export const SearchEngineSelector = () => {
|
||||
</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,
|
||||
} from '@mantine/core';
|
||||
import { createFormContext } from '@mantine/form';
|
||||
import type { InferGetServerSidePropsType } from 'next';
|
||||
import { GetServerSidePropsContext } from 'next';
|
||||
import { createServerSideHelpers } from '@trpc/react-query/server';
|
||||
import { GetServerSideProps, GetServerSidePropsContext } from 'next';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Head from 'next/head';
|
||||
import { forwardRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
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 { sleep } from '~/tools/client/time';
|
||||
import { createTrpcServersideHelpers } from '~/server/api/helper';
|
||||
import { getServerAuthSession } from '~/server/auth';
|
||||
import { languages } from '~/tools/language';
|
||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||
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 { updateSettingsValidationSchema } from '~/validations/user';
|
||||
|
||||
const PreferencesPage = ({ locale }: InferGetServerSidePropsType<typeof getServerSideProps>) => {
|
||||
const PreferencesPage = () => {
|
||||
const { data } = api.user.withSettings.useQuery();
|
||||
const { data: boardsData } = api.boards.all.useQuery();
|
||||
const { t } = useTranslation('user/preferences');
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<Container>
|
||||
<Paper p="xl" mih="100%" withBorder>
|
||||
<Head>
|
||||
<title>Preferences • Homarr</title>
|
||||
<title>{t('metaTitle')} • Homarr</title>
|
||||
</Head>
|
||||
<Title mb="xl">Preferences</Title>
|
||||
<Title mb="xl">{t('pageTitle')}</Title>
|
||||
|
||||
{data && boardsData && (
|
||||
<SettingsComponent settings={data.settings} boardsData={boardsData} />
|
||||
@@ -103,10 +105,6 @@ const SettingsComponent = ({
|
||||
<form style={{ position: 'relative' }} onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<LoadingOverlay visible={isLoading} overlayBlur={2} />
|
||||
<Stack spacing={5}>
|
||||
<Title order={2} size="lg">
|
||||
{t('boards.title')}
|
||||
</Title>
|
||||
|
||||
<Select
|
||||
label={t('boards.defaultBoard.label')}
|
||||
data={boardsData.map((board) => board.name)}
|
||||
@@ -114,14 +112,9 @@ const SettingsComponent = ({
|
||||
maxDropdownHeight={400}
|
||||
filter={(value, item) => item.label!.toLowerCase().includes(value.toLowerCase().trim())}
|
||||
withAsterisk
|
||||
mb="xs"
|
||||
{...form.getInputProps('defaultBoard')}
|
||||
/>
|
||||
|
||||
<Title order={2} size="lg" mt="lg">
|
||||
{t('localization.language.label')}
|
||||
</Title>
|
||||
|
||||
<Select
|
||||
label="Language"
|
||||
itemComponent={SelectItem}
|
||||
@@ -140,12 +133,12 @@ const SettingsComponent = ({
|
||||
|
||||
<Select
|
||||
label={t('localization.firstDayOfWeek.label')}
|
||||
//TODO: Make it use the configured value
|
||||
data={[
|
||||
{ value: 'monday', label: 'Monday' },
|
||||
{ value: 'sunday', label: 'Sunday' },
|
||||
{ value: 'saturday', label: 'Saturday' },
|
||||
]}
|
||||
withAsterisk
|
||||
data={firstDayOfWeekOptions.map((day) => ({
|
||||
label: t(`localization.firstDayOfWeek.options.${day}`) as string,
|
||||
value: day,
|
||||
}))}
|
||||
{...form.getInputProps('firstDayOfWeek')}
|
||||
/>
|
||||
|
||||
<Title order={2} size="lg" mt="lg" mb="md">
|
||||
@@ -158,10 +151,10 @@ const SettingsComponent = ({
|
||||
{t('searchEngine.title')}
|
||||
</Title>
|
||||
|
||||
<SearchEngineSelector />
|
||||
<SearchEngineSettings />
|
||||
|
||||
<Button type="submit" fullWidth mt="md">
|
||||
Save
|
||||
{t('common:save')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</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(
|
||||
manageNamespaces,
|
||||
['user/preferences'],
|
||||
locale,
|
||||
undefined,
|
||||
undefined
|
||||
@@ -204,8 +211,9 @@ export async function getServerSideProps({ req, res, locale }: GetServerSideProp
|
||||
props: {
|
||||
...translations,
|
||||
locale: locale,
|
||||
trpcState: helpers.dehydrate(),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default PreferencesPage;
|
||||
|
||||
@@ -10,6 +10,8 @@ export const getServerSideTranslations = async (
|
||||
req?: IncomingMessage,
|
||||
res?: ServerResponse
|
||||
) => {
|
||||
namespaces = namespaces.concat(['common', 'zod']);
|
||||
|
||||
if (!req || !res) {
|
||||
return serverSideTranslations(requestLocale ?? 'en', namespaces);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user