♻️ Add ssr for user preferences page, add translations for user preferences page

This commit is contained in:
Meier Lukas
2023-08-05 13:19:30 +02:00
parent 4817c0c267
commit 04b3fa394d
6 changed files with 83 additions and 62 deletions

View File

@@ -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": {

View File

@@ -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;
@@ -166,13 +165,13 @@ const CitySelectModal = ({ opened, closeModal, query, onCitySelected }: CitySele
onClose={closeModal} onClose={closeModal}
zIndex={250} zIndex={250}
> >
<Center> <Center>
<Stack align="center"> <Stack align="center">
<IconAlertTriangle /> <IconAlertTriangle />
<Title order={6}>Nothing found</Title> <Title order={6}>Nothing found</Title>
<Text>Nothing was found, please try again</Text> <Text>Nothing was found, please try again</Text>
</Stack> </Stack>
</Center> </Center>
</Modal> </Modal>
); );
@@ -220,15 +219,20 @@ 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
<Text style={{ whiteSpace: 'nowrap' }}> target="_blank"
{city.latitude}, {city.longitude} href={`https://www.google.com/maps/place/${city.latitude},${city.longitude}`}
</Text> >
<Text style={{ whiteSpace: 'nowrap' }}>
{city.latitude}, {city.longitude}
</Text>
</Anchor> </Anchor>
</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>
)} )}

View File

@@ -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 = () => {

View File

@@ -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,
}));
};

View File

@@ -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;

View File

@@ -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);
} }