mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-11 07:55:52 +01:00
🌐 Add missing translations
This commit is contained in:
50
public/locales/en/user/create.json
Normal file
50
public/locales/en/user/create.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"steps": {
|
||||||
|
"account": {
|
||||||
|
"title": "First step",
|
||||||
|
"text": "Create account",
|
||||||
|
"username": {
|
||||||
|
"label": "Username"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"label": "E-Mail"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"title": "Second step",
|
||||||
|
"text": "Password",
|
||||||
|
"password": {
|
||||||
|
"label": "Password",
|
||||||
|
"requirement": "Includes at least 6 characters"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"finish": {
|
||||||
|
"title": "Final step",
|
||||||
|
"text": "Save to database",
|
||||||
|
"card": {
|
||||||
|
"title": "Review your inputs",
|
||||||
|
"text": "After you submit your data to the database, the user will be able to log in. Are you sure that you want to store this user in the database and activate the login?"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"header": {
|
||||||
|
"property": "Property",
|
||||||
|
"value": "Value",
|
||||||
|
"username": "Username",
|
||||||
|
"email": "E-Mail",
|
||||||
|
"password": "Password"
|
||||||
|
},
|
||||||
|
"notSet": "Not set",
|
||||||
|
"valid": "Valid"
|
||||||
|
},
|
||||||
|
"alertConfirmed": "User has been created in the database. They can now log in."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"next": "Next",
|
||||||
|
"previous": "Previous",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"generateRandomPw": "Generate random",
|
||||||
|
"createAnother": "Create another",
|
||||||
|
"goBack": "Go back to users"
|
||||||
|
}
|
||||||
|
}
|
||||||
21
public/locales/en/user/invites.json
Normal file
21
public/locales/en/user/invites.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"title": "Manage user invites",
|
||||||
|
"text": "Using invites, you can invite users to your Homarr instance. An invitation will only be valid for a certain time-span and can be used once. The expiration must be between 5 minutes and 12 months upon creation.",
|
||||||
|
"button": {
|
||||||
|
"createInvite": "Create invitation",
|
||||||
|
"deleteInvite": "Delete invite"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"header": {
|
||||||
|
"id": "ID",
|
||||||
|
"creator": "Creator",
|
||||||
|
"expires": "Expires",
|
||||||
|
"action": "Actions"
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"expiresAt": "expired {{at}}",
|
||||||
|
"expiresIn": "in {{in}}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"noInvites": "There are no invitations yet."
|
||||||
|
}
|
||||||
16
public/locales/en/user/manage.json
Normal file
16
public/locales/en/user/manage.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"title": "Manage users",
|
||||||
|
"text": "Using users, you have granular control who can access, edit or delete resources on your Homarr instance.",
|
||||||
|
"buttons": {
|
||||||
|
"create": "Create"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"header": {
|
||||||
|
"user": "User"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modals": {
|
||||||
|
"delete": "Delete user {{name}}"
|
||||||
|
},
|
||||||
|
"searchDoesntMatch": "Your search does not match any entries. Please adjust your filter."
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Card, Flex, TextInput } from '@mantine/core';
|
import { Button, Card, Flex, TextInput } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import { IconArrowRight, IconAt, IconUser } from '@tabler/icons-react';
|
import { IconArrowRight, IconAt, IconUser } from '@tabler/icons-react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
interface CreateAccountStepProps {
|
interface CreateAccountStepProps {
|
||||||
@@ -20,11 +21,13 @@ export const CreateAccountStep = ({ defaultEmail, defaultUsername, nextStep }: C
|
|||||||
validate: zodResolver(createAccountStepValidationSchema),
|
validate: zodResolver(createAccountStepValidationSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation('user/create');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card mih={400}>
|
<Card mih={400}>
|
||||||
<TextInput
|
<TextInput
|
||||||
icon={<IconUser size="0.8rem" />}
|
icon={<IconUser size="0.8rem" />}
|
||||||
label="Username"
|
label={t('steps.account.username.label')}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
mb="md"
|
mb="md"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
@@ -32,7 +35,7 @@ export const CreateAccountStep = ({ defaultEmail, defaultUsername, nextStep }: C
|
|||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
icon={<IconAt size="0.8rem" />}
|
icon={<IconAt size="0.8rem" />}
|
||||||
label="E-Mail"
|
label={t('steps.account.email.label')}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
mb="md"
|
mb="md"
|
||||||
{...form.getInputProps('eMail')}
|
{...form.getInputProps('eMail')}
|
||||||
@@ -51,7 +54,7 @@ export const CreateAccountStep = ({ defaultEmail, defaultUsername, nextStep }: C
|
|||||||
variant="light"
|
variant="light"
|
||||||
px="xl"
|
px="xl"
|
||||||
>
|
>
|
||||||
Next
|
{t('buttons.next')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
IconX,
|
IconX,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
import { passwordSchema } from '~/validations/user';
|
import { passwordSchema } from '~/validations/user';
|
||||||
@@ -73,6 +74,8 @@ export const CreateAccountSecurityStep = ({
|
|||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
const { t } = useTranslation('user/create');
|
||||||
|
|
||||||
const strength = getStrength(form.values.password);
|
const strength = getStrength(form.values.password);
|
||||||
const color = strength === 100 ? 'teal' : strength > 50 ? 'yellow' : 'red';
|
const color = strength === 100 ? 'teal' : strength > 50 ? 'yellow' : 'red';
|
||||||
|
|
||||||
@@ -95,7 +98,7 @@ export const CreateAccountSecurityStep = ({
|
|||||||
style={{
|
style={{
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}}
|
}}
|
||||||
label="Password"
|
label={t('steps.security.password.label')}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
mb="md"
|
mb="md"
|
||||||
withAsterisk
|
withAsterisk
|
||||||
@@ -111,7 +114,7 @@ export const CreateAccountSecurityStep = ({
|
|||||||
variant="default"
|
variant="default"
|
||||||
mt="xl"
|
mt="xl"
|
||||||
>
|
>
|
||||||
Generate random
|
{t('buttons.generateRandomPw')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,7 +122,7 @@ export const CreateAccountSecurityStep = ({
|
|||||||
<Popover.Dropdown>
|
<Popover.Dropdown>
|
||||||
<Progress color={color} value={strength} size={5} mb="xs" />
|
<Progress color={color} value={strength} size={5} mb="xs" />
|
||||||
<PasswordRequirement
|
<PasswordRequirement
|
||||||
label="Includes at least 6 characters"
|
label={t('steps.security.password.requirement')}
|
||||||
meets={form.values.password.length > 5}
|
meets={form.values.password.length > 5}
|
||||||
/>
|
/>
|
||||||
{checks}
|
{checks}
|
||||||
@@ -128,7 +131,7 @@ export const CreateAccountSecurityStep = ({
|
|||||||
|
|
||||||
<Group position="apart" noWrap>
|
<Group position="apart" noWrap>
|
||||||
<Button leftIcon={<IconArrowLeft size="1rem" />} onClick={prevStep} variant="light" px="xl">
|
<Button leftIcon={<IconArrowLeft size="1rem" />} onClick={prevStep} variant="light" px="xl">
|
||||||
Previous
|
{t('buttons.previous')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
rightIcon={<IconArrowRight size="1rem" />}
|
rightIcon={<IconArrowRight size="1rem" />}
|
||||||
@@ -141,7 +144,7 @@ export const CreateAccountSecurityStep = ({
|
|||||||
px="xl"
|
px="xl"
|
||||||
disabled={!form.isValid()}
|
disabled={!form.isValid()}
|
||||||
>
|
>
|
||||||
Next
|
{t('buttons.next')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Alert, Button, Card, Flex, Group, Stepper, Table, Text, Title } from '@mantine/core';
|
import { Alert, Button, Card, Group, Stepper, Table, Text, Title } from '@mantine/core';
|
||||||
import { useForm, zodResolver } from '@mantine/form';
|
import { useForm, zodResolver } from '@mantine/form';
|
||||||
import {
|
import {
|
||||||
IconArrowLeft,
|
IconArrowLeft,
|
||||||
@@ -14,6 +14,7 @@ import { GetServerSideProps } from 'next';
|
|||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import {
|
||||||
CreateAccountStep,
|
CreateAccountStep,
|
||||||
@@ -27,6 +28,7 @@ import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
|||||||
import { getServerAuthSession } from '~/server/auth';
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
|
|
||||||
const CreateNewUserPage = () => {
|
const CreateNewUserPage = () => {
|
||||||
const [active, setActive] = useState(0);
|
const [active, setActive] = useState(0);
|
||||||
@@ -61,6 +63,8 @@ const CreateNewUserPage = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation('user/create');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManageLayout>
|
<ManageLayout>
|
||||||
<Head>
|
<Head>
|
||||||
@@ -72,8 +76,8 @@ const CreateNewUserPage = () => {
|
|||||||
allowStepClick={false}
|
allowStepClick={false}
|
||||||
allowStepSelect={false}
|
allowStepSelect={false}
|
||||||
icon={<IconUser />}
|
icon={<IconUser />}
|
||||||
label="First step"
|
label={t('steps.account.title')}
|
||||||
description="Create account"
|
description={t('steps.account.text')}
|
||||||
>
|
>
|
||||||
<CreateAccountStep
|
<CreateAccountStep
|
||||||
defaultUsername={form.values.account.username}
|
defaultUsername={form.values.account.username}
|
||||||
@@ -88,8 +92,8 @@ const CreateNewUserPage = () => {
|
|||||||
allowStepClick={false}
|
allowStepClick={false}
|
||||||
allowStepSelect={false}
|
allowStepSelect={false}
|
||||||
icon={<IconKey />}
|
icon={<IconKey />}
|
||||||
label="Second step"
|
label={t('steps.security.title')}
|
||||||
description="Password"
|
description={t('steps.security.text')}
|
||||||
>
|
>
|
||||||
<CreateAccountSecurityStep
|
<CreateAccountSecurityStep
|
||||||
defaultPassword={form.values.security.password}
|
defaultPassword={form.values.security.password}
|
||||||
@@ -104,21 +108,18 @@ const CreateNewUserPage = () => {
|
|||||||
allowStepClick={false}
|
allowStepClick={false}
|
||||||
allowStepSelect={false}
|
allowStepSelect={false}
|
||||||
icon={<IconMailCheck />}
|
icon={<IconMailCheck />}
|
||||||
label="Final step"
|
label={t('steps.finish.title')}
|
||||||
description="Save to database"
|
description={t('steps.finish.title')}
|
||||||
>
|
>
|
||||||
<Card mih={400}>
|
<Card mih={400}>
|
||||||
<Title order={5}>Review your inputs</Title>
|
<Title order={5}>{t('steps.finish.card.title')}</Title>
|
||||||
<Text mb="xl">
|
<Text mb="xl">{t('steps.finish.card.text')}</Text>
|
||||||
After you submit your data to the database, the user will be able to log in. Are you
|
|
||||||
sure that you want to store this user in the database and activate the login?
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Table mb="lg" withBorder highlightOnHover>
|
<Table mb="lg" withBorder highlightOnHover>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Property</th>
|
<th>{t('steps.finish.table.header.property')}</th>
|
||||||
<th>Value</th>
|
<th>{t('steps.finish.table.header.value')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -126,7 +127,7 @@ const CreateNewUserPage = () => {
|
|||||||
<td>
|
<td>
|
||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
<IconUser size="1rem" />
|
<IconUser size="1rem" />
|
||||||
<Text>Username</Text>
|
<Text>{t('steps.finish.table.header.username')}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</td>
|
</td>
|
||||||
<td>{form.values.account.username}</td>
|
<td>{form.values.account.username}</td>
|
||||||
@@ -135,7 +136,7 @@ const CreateNewUserPage = () => {
|
|||||||
<td>
|
<td>
|
||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
<IconMail size="1rem" />
|
<IconMail size="1rem" />
|
||||||
<Text>E-Mail</Text>
|
<Text>{t('steps.finish.table.header.email')}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -144,7 +145,7 @@ const CreateNewUserPage = () => {
|
|||||||
) : (
|
) : (
|
||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
<IconInfoCircle size="1rem" color="orange" />
|
<IconInfoCircle size="1rem" color="orange" />
|
||||||
<Text color="orange">Not set</Text>
|
<Text color="orange">{t('steps.finish.table.notSet')}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
@@ -153,13 +154,13 @@ const CreateNewUserPage = () => {
|
|||||||
<td>
|
<td>
|
||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
<IconKey size="1rem" />
|
<IconKey size="1rem" />
|
||||||
<Text>Password</Text>
|
<Text>{t('steps.finish.table.password')}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Group spacing="xs">
|
<Group spacing="xs">
|
||||||
<IconCheck size="1rem" color="green" />
|
<IconCheck size="1rem" color="green" />
|
||||||
<Text color="green">Valid</Text>
|
<Text color="green">{t('steps.finish.table.valid')}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -173,7 +174,7 @@ const CreateNewUserPage = () => {
|
|||||||
variant="light"
|
variant="light"
|
||||||
px="xl"
|
px="xl"
|
||||||
>
|
>
|
||||||
Previous
|
{t('buttons.previous')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
@@ -188,14 +189,14 @@ const CreateNewUserPage = () => {
|
|||||||
variant="light"
|
variant="light"
|
||||||
px="xl"
|
px="xl"
|
||||||
>
|
>
|
||||||
Confirm
|
{t('buttons.confirm')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
<Stepper.Completed>
|
<Stepper.Completed>
|
||||||
<Alert title="User was created" color="green" mb="md">
|
<Alert title="User was created" color="green" mb="md">
|
||||||
User has been created in the database. They can now log in.
|
{t('steps.finish.alertConfirmed')}
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Group>
|
<Group>
|
||||||
@@ -207,7 +208,7 @@ const CreateNewUserPage = () => {
|
|||||||
leftIcon={<IconUserPlus size="1rem" />}
|
leftIcon={<IconUserPlus size="1rem" />}
|
||||||
variant="default"
|
variant="default"
|
||||||
>
|
>
|
||||||
Create another
|
{t('buttons.createAnother')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
@@ -215,7 +216,7 @@ const CreateNewUserPage = () => {
|
|||||||
variant="default"
|
variant="default"
|
||||||
href="/manage/users"
|
href="/manage/users"
|
||||||
>
|
>
|
||||||
Go back to users
|
{t('buttons.goBack')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stepper.Completed>
|
</Stepper.Completed>
|
||||||
@@ -234,7 +235,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
const translations = await getServerSideTranslations(
|
||||||
['common'],
|
manageNamespaces,
|
||||||
ctx.locale,
|
ctx.locale,
|
||||||
undefined,
|
undefined,
|
||||||
undefined
|
undefined
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { GetServerSideProps } from 'next';
|
|||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||||
import { getServerAuthSession } from '~/server/auth';
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
@@ -32,17 +33,16 @@ const ManageUsersPage = () => {
|
|||||||
search: debouncedSearch,
|
search: debouncedSearch,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation('user/manage');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManageLayout>
|
<ManageLayout>
|
||||||
<Head>
|
<Head>
|
||||||
<title>Users • Homarr</title>
|
<title>Users • Homarr</title>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<Title mb="md">Manage users</Title>
|
<Title mb="md">{t('title')}</Title>
|
||||||
<Text mb="xl">
|
<Text mb="xl">{t('text')}</Text>
|
||||||
Using users, you have granular control who can access, edit or delete resources on your
|
|
||||||
Homarr instance.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Flex columnGap={10} justify="end" mb="md">
|
<Flex columnGap={10} justify="end" mb="md">
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
@@ -61,7 +61,7 @@ const ManageUsersPage = () => {
|
|||||||
href="/manage/users/create"
|
href="/manage/users/create"
|
||||||
variant="default"
|
variant="default"
|
||||||
>
|
>
|
||||||
Create
|
{t('buttons.create')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ const ManageUsersPage = () => {
|
|||||||
<Table mb="md" withBorder highlightOnHover>
|
<Table mb="md" withBorder highlightOnHover>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>User</th>
|
<th>{t('table.header.user')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -87,7 +87,9 @@ const ManageUsersPage = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
openContextModal({
|
openContextModal({
|
||||||
modal: 'deleteUserModal',
|
modal: 'deleteUserModal',
|
||||||
title: <Text weight="bold">Delete user {user.name}</Text>,
|
title: (
|
||||||
|
<Text weight="bold">{t('modals.delete', { name: user.name })}</Text>
|
||||||
|
),
|
||||||
innerProps: {
|
innerProps: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
username: user.name ?? '',
|
username: user.name ?? '',
|
||||||
@@ -110,7 +112,7 @@ const ManageUsersPage = () => {
|
|||||||
<td colSpan={1}>
|
<td colSpan={1}>
|
||||||
<Box p={15}>
|
<Box p={15}>
|
||||||
<Text>
|
<Text>
|
||||||
Your search does not match any entries. Please adjust your filter.
|
{t('searchDoesntMatch')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ import { IconPlus, IconTrash } from '@tabler/icons-react';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { GetServerSideProps } from 'next';
|
import { GetServerSideProps } from 'next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
import { ManageLayout } from '~/components/layout/Templates/ManageLayout';
|
||||||
import { getServerAuthSession } from '~/server/auth';
|
import { getServerAuthSession } from '~/server/auth';
|
||||||
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
import { getServerSideTranslations } from '~/tools/server/getServerSideTranslations';
|
||||||
|
import { manageNamespaces } from '~/tools/server/translation-namespaces';
|
||||||
import { api } from '~/utils/api';
|
import { api } from '~/utils/api';
|
||||||
|
|
||||||
const ManageUserInvitesPage = () => {
|
const ManageUserInvitesPage = () => {
|
||||||
@@ -26,7 +27,6 @@ const ManageUserInvitesPage = () => {
|
|||||||
const { data } = api.invites.all.useQuery({
|
const { data } = api.invites.all.useQuery({
|
||||||
page: activePage,
|
page: activePage,
|
||||||
});
|
});
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
|
|
||||||
@@ -38,17 +38,15 @@ const ManageUserInvitesPage = () => {
|
|||||||
setActivePage((prev) => prev - 1);
|
setActivePage((prev) => prev - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t } = useTranslation('user/invites');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManageLayout>
|
<ManageLayout>
|
||||||
<Head>
|
<Head>
|
||||||
<title>User invites • Homarr</title>
|
<title>User invites • Homarr</title>
|
||||||
</Head>
|
</Head>
|
||||||
<Title mb="md">Manage user invites</Title>
|
<Title mb="md">{t('title')}</Title>
|
||||||
<Text mb="xl">
|
<Text mb="xl">{t('text')}</Text>
|
||||||
Using invites, you can invite users to your Homarr instance. An invitation will only be
|
|
||||||
valid for a certain time-span and can be used once. The expiration must be between 5 minutes
|
|
||||||
and 12 months upon creation.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Flex justify="end" mb="md">
|
<Flex justify="end" mb="md">
|
||||||
<Button
|
<Button
|
||||||
@@ -62,7 +60,7 @@ const ManageUserInvitesPage = () => {
|
|||||||
leftIcon={<IconPlus size="1rem" />}
|
leftIcon={<IconPlus size="1rem" />}
|
||||||
variant="default"
|
variant="default"
|
||||||
>
|
>
|
||||||
Create invitation
|
{t('button.createInvite')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -71,10 +69,10 @@ const ManageUserInvitesPage = () => {
|
|||||||
<Table mb="md" withBorder highlightOnHover>
|
<Table mb="md" withBorder highlightOnHover>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>{t('table.header.id')}</th>
|
||||||
<th>Creator</th>
|
<th>{t('table.header.creator')}</th>
|
||||||
<th>Expires</th>
|
<th>{t('table.header.expires')}</th>
|
||||||
<th>Actions</th>
|
<th>{t('table.header.action')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -88,9 +86,13 @@ const ManageUserInvitesPage = () => {
|
|||||||
</td>
|
</td>
|
||||||
<td className={classes.tableCell}>
|
<td className={classes.tableCell}>
|
||||||
{dayjs(dayjs()).isAfter(invite.expires) ? (
|
{dayjs(dayjs()).isAfter(invite.expires) ? (
|
||||||
<Text>expired {dayjs(invite.expires).fromNow()}</Text>
|
<Text>
|
||||||
|
{t('table.data.expiresAt', { at: dayjs(invite.expires).fromNow() })}
|
||||||
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text>in {dayjs(invite.expires).fromNow(true)}</Text>
|
<Text>
|
||||||
|
{t('table.data.expiresIn', { in: dayjs(invite.expires).fromNow(true) })}
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td className={classes.tableCell}>
|
<td className={classes.tableCell}>
|
||||||
@@ -98,7 +100,7 @@ const ManageUserInvitesPage = () => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
modals.openContextModal({
|
modals.openContextModal({
|
||||||
modal: 'deleteInviteModal',
|
modal: 'deleteInviteModal',
|
||||||
title: <Text weight="bold">Delete invite</Text>,
|
title: <Text weight="bold">{t('button.deleteInvite')}</Text>,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
tokenId: invite.id,
|
tokenId: invite.id,
|
||||||
},
|
},
|
||||||
@@ -116,7 +118,7 @@ const ManageUserInvitesPage = () => {
|
|||||||
<tr>
|
<tr>
|
||||||
<td colSpan={3}>
|
<td colSpan={3}>
|
||||||
<Center p="md">
|
<Center p="md">
|
||||||
<Text color="dimmed">There are no invitations yet.</Text>
|
<Text color="dimmed">{t('noInvites')}</Text>
|
||||||
</Center>
|
</Center>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -164,7 +166,7 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const translations = await getServerSideTranslations(
|
const translations = await getServerSideTranslations(
|
||||||
['common'],
|
manageNamespaces,
|
||||||
ctx.locale,
|
ctx.locale,
|
||||||
undefined,
|
undefined,
|
||||||
undefined
|
undefined
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export const boardNamespaces = [
|
|||||||
|
|
||||||
export const manageNamespaces = [
|
export const manageNamespaces = [
|
||||||
'user/preferences',
|
'user/preferences',
|
||||||
|
'user/manage',
|
||||||
|
'user/invite',
|
||||||
|
'user/create',
|
||||||
'boards/manage',
|
'boards/manage',
|
||||||
'zod',
|
'zod',
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user