219 lines
6.4 KiB
TypeScript
219 lines
6.4 KiB
TypeScript
import {
|
|
Button,
|
|
Container,
|
|
Group,
|
|
LoadingOverlay,
|
|
Paper,
|
|
Select,
|
|
Stack,
|
|
Text,
|
|
Title,
|
|
} from '@mantine/core';
|
|
import { createFormContext } from '@mantine/form';
|
|
import { GetServerSideProps } from 'next';
|
|
import { useTranslation } from 'next-i18next';
|
|
import Head from 'next/head';
|
|
import { forwardRef } from 'react';
|
|
import { z } from 'zod';
|
|
import { AccessibilitySettings } from '~/components/User/Preferences/AccessibilitySettings';
|
|
import { SearchEngineSettings } from '~/components/User/Preferences/SearchEngineSelector';
|
|
import { MainLayout } from '~/components/layout/Templates/MainLayout';
|
|
import { createTrpcServersideHelpers } from '~/server/api/helper';
|
|
import { getServerAuthSession } from '~/server/auth';
|
|
import { languages } from '~/tools/language';
|
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
|
import { RouterOutputs, api } from '~/utils/api';
|
|
import { useI18nZodResolver } from '~/utils/i18n-zod-resolver';
|
|
import { updateSettingsValidationSchema } from '~/validations/user';
|
|
|
|
const PreferencesPage = () => {
|
|
const { data } = api.user.withSettings.useQuery();
|
|
const { data: boardsData } = api.boards.all.useQuery();
|
|
const { t } = useTranslation('user/preferences');
|
|
const headTitle = `${t('metaTitle')} • Homarr`;
|
|
|
|
return (
|
|
<MainLayout showExperimental>
|
|
<Container>
|
|
<Paper p="xl" mih="100%" withBorder>
|
|
<Head>
|
|
<title>{headTitle}</title>
|
|
</Head>
|
|
<Title mb="xl">{t('pageTitle')}</Title>
|
|
|
|
{data && boardsData && (
|
|
<SettingsComponent settings={data.settings} boardsData={boardsData} />
|
|
)}
|
|
</Paper>
|
|
</Container>
|
|
</MainLayout>
|
|
);
|
|
};
|
|
|
|
export const [FormProvider, useUserPreferencesFormContext, useForm] =
|
|
createFormContext<z.infer<typeof updateSettingsValidationSchema>>();
|
|
|
|
const SettingsComponent = ({
|
|
settings,
|
|
boardsData,
|
|
}: {
|
|
settings: RouterOutputs['user']['withSettings']['settings'];
|
|
boardsData: RouterOutputs['boards']['all'];
|
|
}) => {
|
|
const languagesData = languages.map((language) => ({
|
|
image: 'https://img.icons8.com/clouds/256/000000/futurama-bender.png',
|
|
label: language.originalName,
|
|
description: language.translatedName,
|
|
value: language.shortName,
|
|
country: language.country,
|
|
}));
|
|
|
|
const { t } = useTranslation('user/preferences');
|
|
|
|
const { i18nZodResolver } = useI18nZodResolver();
|
|
|
|
const form = useForm({
|
|
initialValues: {
|
|
defaultBoard: settings.defaultBoard,
|
|
language: settings.language,
|
|
firstDayOfWeek: settings.firstDayOfWeek,
|
|
disablePingPulse: settings.disablePingPulse,
|
|
replaceDotsWithIcons: settings.replacePingWithIcons,
|
|
searchTemplate: settings.searchTemplate,
|
|
openSearchInNewTab: settings.openSearchInNewTab,
|
|
},
|
|
validate: i18nZodResolver(updateSettingsValidationSchema),
|
|
validateInputOnBlur: true,
|
|
validateInputOnChange: true,
|
|
});
|
|
|
|
const context = api.useContext();
|
|
const { mutate, isLoading } = api.user.updateSettings.useMutation({
|
|
onSettled: () => {
|
|
void context.boards.all.invalidate();
|
|
void context.user.withSettings.invalidate();
|
|
},
|
|
});
|
|
|
|
const handleSubmit = (values: z.infer<typeof updateSettingsValidationSchema>) => {
|
|
mutate(values);
|
|
};
|
|
|
|
return (
|
|
<FormProvider form={form}>
|
|
<form style={{ position: 'relative' }} onSubmit={form.onSubmit(handleSubmit)}>
|
|
<LoadingOverlay visible={isLoading} overlayBlur={2} />
|
|
<Stack spacing={5}>
|
|
<Select
|
|
label={t('boards.defaultBoard.label')}
|
|
data={boardsData.map((board) => board.name)}
|
|
searchable
|
|
maxDropdownHeight={400}
|
|
filter={(value, item) => item.label!.toLowerCase().includes(value.toLowerCase().trim())}
|
|
withAsterisk
|
|
{...form.getInputProps('defaultBoard')}
|
|
/>
|
|
|
|
<Select
|
|
label="Language"
|
|
itemComponent={SelectItem}
|
|
data={languagesData}
|
|
searchable
|
|
maxDropdownHeight={400}
|
|
filter={(value, item) =>
|
|
item.label!.toLowerCase().includes(value.toLowerCase().trim()) ||
|
|
item.description.toLowerCase().includes(value.toLowerCase().trim())
|
|
}
|
|
defaultValue={settings.language}
|
|
withAsterisk
|
|
mb="xs"
|
|
{...form.getInputProps('language')}
|
|
/>
|
|
|
|
<Select
|
|
label={t('localization.firstDayOfWeek.label')}
|
|
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">
|
|
{t('accessibility.title')}
|
|
</Title>
|
|
|
|
<AccessibilitySettings />
|
|
|
|
<Title order={2} size="lg" mt="lg" mb="md">
|
|
{t('searchEngine.title')}
|
|
</Title>
|
|
|
|
<SearchEngineSettings />
|
|
|
|
<Button type="submit" fullWidth mt="md">
|
|
{t('common:save')}
|
|
</Button>
|
|
</Stack>
|
|
</form>
|
|
</FormProvider>
|
|
);
|
|
};
|
|
|
|
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
image: string;
|
|
label: string;
|
|
description: string;
|
|
country: string;
|
|
}
|
|
|
|
const SelectItem = forwardRef<HTMLDivElement, ItemProps>(
|
|
({ image, label, description, country, ...others }: ItemProps, ref) => (
|
|
<div ref={ref} {...others}>
|
|
<Group noWrap>
|
|
<span className={`fi fi-${country?.toLowerCase()}`}></span>
|
|
|
|
<div>
|
|
<Text size="sm">{label}</Text>
|
|
<Text size="xs" opacity={0.65}>
|
|
{description}
|
|
</Text>
|
|
</div>
|
|
</Group>
|
|
</div>
|
|
)
|
|
);
|
|
|
|
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(
|
|
['user/preferences'],
|
|
locale,
|
|
undefined,
|
|
undefined
|
|
);
|
|
return {
|
|
props: {
|
|
...translations,
|
|
locale: locale,
|
|
trpcState: helpers.dehydrate(),
|
|
},
|
|
};
|
|
};
|
|
|
|
export default PreferencesPage;
|