Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15feac9aaa | ||
|
|
5b1e3ce80e | ||
|
|
41fe5928f1 | ||
|
|
6eb59d6f53 | ||
|
|
2c6b7a818e | ||
|
|
fd10788838 | ||
|
|
fe4598f722 | ||
|
|
a755692d81 | ||
|
|
3ff0d72ba1 | ||
|
|
a63a191203 | ||
|
|
1be906a0cd | ||
|
|
63d8fa8c26 | ||
|
|
b5dd6ae8ef | ||
|
|
d540ad374a | ||
|
|
6a971c1986 | ||
|
|
d86fe3ddf0 | ||
|
|
06db8960a7 | ||
|
|
a5719acf34 | ||
|
|
d1d597c8e1 | ||
|
|
657e42e4bb | ||
|
|
256ebcb864 | ||
|
|
72e86dee01 | ||
|
|
b7910b16e0 | ||
|
|
5a83bb6e39 | ||
|
|
433b66582d | ||
|
|
acbcb6818b | ||
|
|
07d991a0a1 | ||
|
|
92364e9574 | ||
|
|
884fde245b | ||
|
|
689da2fb91 | ||
|
|
22af9e2c3e | ||
|
|
2c388bd449 |
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core";
|
import type { MantineColorScheme, MantineColorSchemeManager } from "@mantine/core";
|
||||||
import { DirectionProvider, MantineProvider } from "@mantine/core";
|
import { DirectionProvider, MantineProvider, v8CssVariablesResolver } from "@mantine/core";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
@@ -18,7 +18,12 @@ export const CustomMantineProvider = ({
|
|||||||
const manager = useColorSchemeManager();
|
const manager = useColorSchemeManager();
|
||||||
return (
|
return (
|
||||||
<DirectionProvider>
|
<DirectionProvider>
|
||||||
<MantineProvider defaultColorScheme={defaultColorScheme} colorSchemeManager={manager} theme={theme}>
|
<MantineProvider
|
||||||
|
defaultColorScheme={defaultColorScheme}
|
||||||
|
colorSchemeManager={manager}
|
||||||
|
theme={theme}
|
||||||
|
cssVariablesResolver={v8CssVariablesResolver}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
</DirectionProvider>
|
</DirectionProvider>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { cache } from "react";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
@@ -34,17 +35,20 @@ interface Props<TParams extends Params> {
|
|||||||
export const createBoardContentPage = <TParams extends Record<string, unknown>>({
|
export const createBoardContentPage = <TParams extends Record<string, unknown>>({
|
||||||
getInitialBoardAsync: getInitialBoard,
|
getInitialBoardAsync: getInitialBoard,
|
||||||
}: Props<TParams>) => {
|
}: Props<TParams>) => {
|
||||||
|
const getCachedBoard = cache((_paramsKey: string, params: TParams) => getInitialBoard(params));
|
||||||
|
const getBoardForParams = (params: TParams) => getCachedBoard(JSON.stringify(params), params);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
layout: createBoardLayout({
|
layout: createBoardLayout({
|
||||||
headerActions: <BoardContentHeaderActions />,
|
headerActions: <BoardContentHeaderActions />,
|
||||||
getInitialBoardAsync: getInitialBoard,
|
getInitialBoardAsync: getBoardForParams,
|
||||||
}),
|
}),
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
page: async ({ params }: { params: Promise<TParams> }) => {
|
page: async ({ params }: { params: Promise<TParams> }) => {
|
||||||
const resolvedParams = await params;
|
const resolvedParams = await params;
|
||||||
const queryClient = getQueryClient();
|
const queryClient = getQueryClient();
|
||||||
|
|
||||||
const [board, session] = await Promise.all([getInitialBoard(resolvedParams), auth()]);
|
const [board, session] = await Promise.all([getBoardForParams(resolvedParams), auth()]);
|
||||||
|
|
||||||
const itemsMap = board.items.reduce((acc, item) => {
|
const itemsMap = board.items.reduce((acc, item) => {
|
||||||
const existing = acc.get(item.kind);
|
const existing = acc.get(item.kind);
|
||||||
@@ -81,7 +85,7 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
|
|||||||
},
|
},
|
||||||
generateMetadataAsync: async ({ params }: { params: Promise<TParams> }): Promise<Metadata> => {
|
generateMetadataAsync: async ({ params }: { params: Promise<TParams> }): Promise<Metadata> => {
|
||||||
try {
|
try {
|
||||||
const board = await getInitialBoard(await params);
|
const board = await getBoardForParams(await params);
|
||||||
const t = await getI18n();
|
const t = await getI18n();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -97,7 +101,6 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Ignore not found errors and return empty metadata
|
|
||||||
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
|
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { MantineColorsTuple } from "@mantine/core";
|
import type { MantineColorsTuple } from "@mantine/core";
|
||||||
import { colorsTuple, createTheme, darken, lighten, MantineProvider, rem } from "@mantine/core";
|
import { colorsTuple, createTheme, darken, lighten, MantineProvider, rem, v8CssVariablesResolver } from "@mantine/core";
|
||||||
|
|
||||||
import { useRequiredBoard } from "@homarr/boards/context";
|
import { useRequiredBoard } from "@homarr/boards/context";
|
||||||
import type { ColorScheme } from "@homarr/definitions";
|
import type { ColorScheme } from "@homarr/definitions";
|
||||||
@@ -33,7 +33,12 @@ export const BoardMantineProvider = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MantineProvider defaultColorScheme={defaultColorScheme} theme={theme} colorSchemeManager={colorSchemeManager}>
|
<MantineProvider
|
||||||
|
defaultColorScheme={defaultColorScheme}
|
||||||
|
theme={theme}
|
||||||
|
colorSchemeManager={colorSchemeManager}
|
||||||
|
cssVariablesResolver={v8CssVariablesResolver}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Button, PasswordInput, Stack, TextInput } from "@mantine/core";
|
import { Button, Stack, TextInput } from "@mantine/core";
|
||||||
import type { z } from "zod/v4";
|
import type { z } from "zod/v4";
|
||||||
|
|
||||||
import { clientApi } from "@homarr/api/client";
|
import { clientApi } from "@homarr/api/client";
|
||||||
import { signIn } from "@homarr/auth/client";
|
import { signIn } from "@homarr/auth/client";
|
||||||
import { revalidatePathActionAsync } from "@homarr/common/client";
|
import { revalidatePathActionAsync } from "@homarr/common/client";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
|
import { UserCreatePasswordFields } from "@homarr/forms-collection";
|
||||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { CustomPasswordInput } from "@homarr/ui";
|
|
||||||
import { userInitSchema } from "@homarr/validation/user";
|
import { userInitSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
export const InitUserForm = () => {
|
export const InitUserForm = () => {
|
||||||
@@ -59,12 +59,10 @@ export const InitUserForm = () => {
|
|||||||
>
|
>
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
<TextInput label={t("field.username.label")} {...form.getInputProps("username")} />
|
<TextInput label={t("field.username.label")} {...form.getInputProps("username")} />
|
||||||
<CustomPasswordInput
|
<UserCreatePasswordFields
|
||||||
withPasswordRequirements
|
passwordInputProps={form.getInputProps("password")}
|
||||||
label={t("field.password.label")}
|
confirmPasswordInputProps={form.getInputProps("confirmPassword")}
|
||||||
{...form.getInputProps("password")}
|
|
||||||
/>
|
/>
|
||||||
<PasswordInput label={t("field.passwordConfirm.label")} {...form.getInputProps("confirmPassword")} />
|
|
||||||
<Button type="submit" fullWidth loading={isPending}>
|
<Button type="submit" fullWidth loading={isPending}>
|
||||||
{t("action.create")}
|
{t("action.create")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -107,6 +107,23 @@ export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"
|
|||||||
refetchOnReconnect: false,
|
refetchOnReconnect: false,
|
||||||
});
|
});
|
||||||
const relativeTime = useTimeAgo(data.timestamp);
|
const relativeTime = useTimeAgo(data.timestamp);
|
||||||
|
const utils = clientApi.useUtils();
|
||||||
|
const { mutate, isPending } = clientApi.docker.invalidate.useMutation({
|
||||||
|
async onSuccess() {
|
||||||
|
await utils.docker.getContainers.invalidate();
|
||||||
|
showSuccessNotification({
|
||||||
|
title: tDocker("action.refresh.notification.success.title"),
|
||||||
|
message: tDocker("action.refresh.notification.success.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError() {
|
||||||
|
showErrorNotification({
|
||||||
|
title: tDocker("action.refresh.notification.error.title"),
|
||||||
|
message: tDocker("action.refresh.notification.error.message"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const table = useTranslatedMantineReactTable({
|
const table = useTranslatedMantineReactTable({
|
||||||
data: data.containers,
|
data: data.containers,
|
||||||
enableDensityToggle: false,
|
enableDensityToggle: false,
|
||||||
@@ -125,35 +142,11 @@ export function DockerTable(initialData: RouterOutputs["docker"]["getContainers"
|
|||||||
},
|
},
|
||||||
|
|
||||||
initialState: { density: "xs", showGlobalFilter: true },
|
initialState: { density: "xs", showGlobalFilter: true },
|
||||||
renderTopToolbarCustomActions: () => {
|
renderTopToolbarCustomActions: () => (
|
||||||
const utils = clientApi.useUtils();
|
<Button variant="default" rightSection={<IconRefresh size="1rem" />} onClick={() => mutate()} loading={isPending}>
|
||||||
const { mutate, isPending } = clientApi.docker.invalidate.useMutation({
|
{tDocker("action.refresh.label")}
|
||||||
async onSuccess() {
|
</Button>
|
||||||
await utils.docker.getContainers.invalidate();
|
),
|
||||||
showSuccessNotification({
|
|
||||||
title: tDocker("action.refresh.notification.success.title"),
|
|
||||||
message: tDocker("action.refresh.notification.success.message"),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onError() {
|
|
||||||
showErrorNotification({
|
|
||||||
title: tDocker("action.refresh.notification.error.title"),
|
|
||||||
message: tDocker("action.refresh.notification.error.message"),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
rightSection={<IconRefresh size="1rem" />}
|
|
||||||
onClick={() => mutate()}
|
|
||||||
loading={isPending}
|
|
||||||
>
|
|
||||||
{tDocker("action.refresh.label")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
renderToolbarAlertBannerContent: ({ groupedAlert, table }) => {
|
renderToolbarAlertBannerContent: ({ groupedAlert, table }) => {
|
||||||
const dockerContainers = table.getSelectedRowModel().rows.map((row) => row.original);
|
const dockerContainers = table.getSelectedRowModel().rows.map((row) => row.original);
|
||||||
return (
|
return (
|
||||||
|
|||||||
+6
-26
@@ -1,20 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { startTransition, useCallback, useState } from "react";
|
import { startTransition, useCallback, useState } from "react";
|
||||||
import {
|
import { Badge, Button, Card, Group, Stack, Stepper, Table, Text, TextInput, Title, Tooltip } from "@mantine/core";
|
||||||
Badge,
|
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
Group,
|
|
||||||
PasswordInput,
|
|
||||||
Stack,
|
|
||||||
Stepper,
|
|
||||||
Table,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
} from "@mantine/core";
|
|
||||||
import { useListState } from "@mantine/hooks";
|
import { useListState } from "@mantine/hooks";
|
||||||
import { IconPlus, IconUserCheck } from "@tabler/icons-react";
|
import { IconPlus, IconUserCheck } from "@tabler/icons-react";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
@@ -24,10 +11,11 @@ import type { GroupPermissionKey } from "@homarr/definitions";
|
|||||||
import { everyoneGroup, groupPermissions } from "@homarr/definitions";
|
import { everyoneGroup, groupPermissions } from "@homarr/definitions";
|
||||||
import type { IsValid } from "@homarr/form";
|
import type { IsValid } from "@homarr/form";
|
||||||
import { useZodForm } from "@homarr/form";
|
import { useZodForm } from "@homarr/form";
|
||||||
|
import { UserCreatePasswordFields } from "@homarr/forms-collection";
|
||||||
import { useModalAction } from "@homarr/modals";
|
import { useModalAction } from "@homarr/modals";
|
||||||
import { showErrorNotification } from "@homarr/notifications";
|
import { showErrorNotification } from "@homarr/notifications";
|
||||||
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
import { useI18n, useScopedI18n } from "@homarr/translation/client";
|
||||||
import { CustomPasswordInput, UserAvatar } from "@homarr/ui";
|
import { UserAvatar } from "@homarr/ui";
|
||||||
import { createCustomErrorParams } from "@homarr/validation/form/i18n";
|
import { createCustomErrorParams } from "@homarr/validation/form/i18n";
|
||||||
import { userPasswordSchema } from "@homarr/validation/user";
|
import { userPasswordSchema } from "@homarr/validation/user";
|
||||||
|
|
||||||
@@ -167,18 +155,10 @@ export const UserCreateStepperComponent = ({ initialGroups }: UserCreateStepperC
|
|||||||
<form>
|
<form>
|
||||||
<Card p="xl">
|
<Card p="xl">
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<CustomPasswordInput
|
<UserCreatePasswordFields
|
||||||
withPasswordRequirements
|
|
||||||
label={tUserField("password.label")}
|
|
||||||
variant="filled"
|
variant="filled"
|
||||||
withAsterisk
|
passwordInputProps={securityForm.getInputProps("password")}
|
||||||
{...securityForm.getInputProps("password")}
|
confirmPasswordInputProps={securityForm.getInputProps("confirmPassword")}
|
||||||
/>
|
|
||||||
<PasswordInput
|
|
||||||
label={tUserField("passwordConfirm.label")}
|
|
||||||
variant="filled"
|
|
||||||
withAsterisk
|
|
||||||
{...securityForm.getInputProps("confirmPassword")}
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.16.0",
|
"node": ">=24.16.0",
|
||||||
"pnpm": ">=10.33.4"
|
"pnpm": ">=10.34.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.4",
|
"packageManager": "pnpm@10.34.1",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"better-sqlite3"
|
"better-sqlite3"
|
||||||
|
|||||||
+6
-6
@@ -60,9 +60,9 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=24.16.0",
|
"node": ">=24.16.0",
|
||||||
"pnpm": ">=10.33.4"
|
"pnpm": ">=10.34.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.33.4",
|
"packageManager": "pnpm@10.34.1",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@tree-sitter-grammars/tree-sitter-yaml",
|
"@tree-sitter-grammars/tree-sitter-yaml",
|
||||||
@@ -76,14 +76,14 @@
|
|||||||
"tree-sitter-json"
|
"tree-sitter-json"
|
||||||
],
|
],
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@babel/helpers@<7.26.10": ">=7.29.2",
|
"@babel/helpers@<7.26.10": ">=7.29.7",
|
||||||
"@babel/runtime@<7.26.10": ">=7.29.2",
|
"@babel/runtime@<7.26.10": ">=7.29.7",
|
||||||
"axios@>=1.0.0 <1.8.2": ">=1.16.1",
|
"axios@>=1.0.0 <1.8.2": ">=1.16.1",
|
||||||
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.6",
|
"brace-expansion@>=2.0.0 <=2.0.1": ">=5.0.6",
|
||||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.6",
|
"brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.6",
|
||||||
"esbuild@<=0.24.2": ">=0.28.0",
|
"esbuild@<=0.24.2": ">=0.28.0",
|
||||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
"form-data@>=4.0.0 <4.0.4": ">=4.0.5",
|
||||||
"hono@<4.6.5": ">=4.12.22",
|
"hono@<4.6.5": ">=4.12.23",
|
||||||
"linkifyjs@<4.3.2": ">=4.3.3",
|
"linkifyjs@<4.3.2": ">=4.3.3",
|
||||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.11",
|
"nanoid@>=4.0.0 <5.0.9": ">=5.1.11",
|
||||||
"prismjs@<1.30.0": ">=1.30.0",
|
"prismjs@<1.30.0": ">=1.30.0",
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"sha.js@<=2.4.11": ">=2.4.12",
|
"sha.js@<=2.4.11": ">=2.4.12",
|
||||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.2",
|
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.2",
|
||||||
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.2",
|
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.2",
|
||||||
"tmp@<=0.2.3": ">=0.2.5",
|
"tmp@<=0.2.3": ">=0.2.7",
|
||||||
"vite@>=5.0.0 <=5.4.18": ">=8.0.14"
|
"vite@>=5.0.0 <=5.4.18": ">=8.0.14"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
|||||||
@@ -81,13 +81,7 @@ describe("initUser should initialize the first user", () => {
|
|||||||
await expect(actAsync()).rejects.toThrow("passwordsDoNotMatch");
|
await expect(actAsync()).rejects.toThrow("passwordsDoNotMatch");
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([["aB2%"], ["short"]])("should reject passwords shorter than 8 characters for '%s'", async (password) => {
|
||||||
["aB2%"], // too short
|
|
||||||
["abc123DEF"], // does not contain special characters
|
|
||||||
["abcDEFghi+"], // does not contain numbers
|
|
||||||
["ABC123+/-"], // does not contain lowercase
|
|
||||||
["abc123+/-"], // does not contain uppercase
|
|
||||||
])("should throw error that password requirements do not match for '%s' as password", async (password) => {
|
|
||||||
const db = createDb();
|
const db = createDb();
|
||||||
await createOnboardingStepAsync(db, "user");
|
await createOnboardingStepAsync(db, "user");
|
||||||
const caller = userRouter.createCaller({
|
const caller = userRouter.createCaller({
|
||||||
@@ -103,7 +97,55 @@ describe("initUser should initialize the first user", () => {
|
|||||||
confirmPassword: password,
|
confirmPassword: password,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(actAsync()).rejects.toThrow("passwordRequirements");
|
await expect(actAsync()).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should accept passwords without complexity requirements", async () => {
|
||||||
|
const db = createDb();
|
||||||
|
await createOnboardingStepAsync(db, "user");
|
||||||
|
const caller = userRouter.createCaller({
|
||||||
|
db,
|
||||||
|
deviceType: undefined,
|
||||||
|
session: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await caller.initUser({
|
||||||
|
username: "test",
|
||||||
|
password: "abc123DEF",
|
||||||
|
confirmPassword: "abc123DEF",
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(user).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should accept passwords with special characters like LoveHomarr<3", async () => {
|
||||||
|
const db = createDb();
|
||||||
|
await createOnboardingStepAsync(db, "user");
|
||||||
|
const caller = userRouter.createCaller({
|
||||||
|
db,
|
||||||
|
deviceType: undefined,
|
||||||
|
session: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
await caller.initUser({
|
||||||
|
username: "test",
|
||||||
|
password: "LoveHomarr<3",
|
||||||
|
confirmPassword: "LoveHomarr<3",
|
||||||
|
});
|
||||||
|
|
||||||
|
const user = await db.query.users.findFirst({
|
||||||
|
columns: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(user).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ export * from "./icon-picker/icon-picker";
|
|||||||
export * from "./new-app/icon-matcher";
|
export * from "./new-app/icon-matcher";
|
||||||
|
|
||||||
export * from "./upload-media/upload-media";
|
export * from "./upload-media/upload-media";
|
||||||
|
|
||||||
|
export * from "./user-create/user-create-password-fields";
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { PasswordInput, Stack } from "@mantine/core";
|
||||||
|
import type { PasswordInputProps } from "@mantine/core";
|
||||||
|
|
||||||
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
|
import { CustomPasswordInput } from "@homarr/ui";
|
||||||
|
|
||||||
|
interface UserCreatePasswordFieldsProps {
|
||||||
|
passwordInputProps: PasswordInputProps;
|
||||||
|
confirmPasswordInputProps: PasswordInputProps;
|
||||||
|
variant?: PasswordInputProps["variant"];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserCreatePasswordFields = ({
|
||||||
|
passwordInputProps,
|
||||||
|
confirmPasswordInputProps,
|
||||||
|
variant,
|
||||||
|
}: UserCreatePasswordFieldsProps) => {
|
||||||
|
const t = useScopedI18n("user.field");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<CustomPasswordInput
|
||||||
|
withPasswordRequirements
|
||||||
|
label={t("password.label")}
|
||||||
|
variant={variant}
|
||||||
|
{...passwordInputProps}
|
||||||
|
/>
|
||||||
|
<PasswordInput label={t("passwordConfirm.label")} variant={variant} {...confirmPasswordInputProps} />
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -44,6 +44,7 @@ const optionMapping: OptionMapping = {
|
|||||||
hideIcon: (oldOptions) => oldOptions.items.some((item) => item.hideIcon),
|
hideIcon: (oldOptions) => oldOptions.items.some((item) => item.hideIcon),
|
||||||
hideHostname: (oldOptions) => oldOptions.items.some((item) => item.hideHostname),
|
hideHostname: (oldOptions) => oldOptions.items.some((item) => item.hideHostname),
|
||||||
openNewTab: (oldOptions) => oldOptions.items.some((item) => item.openNewTab),
|
openNewTab: (oldOptions) => oldOptions.items.some((item) => item.openNewTab),
|
||||||
|
withBorder: () => undefined,
|
||||||
},
|
},
|
||||||
calendar: {
|
calendar: {
|
||||||
releaseType: (oldOptions) => [oldOptions.radarrReleaseType],
|
releaseType: (oldOptions) => [oldOptions.radarrReleaseType],
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Contrasenya",
|
"label": "Contrasenya",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Inclou com a mínim 8 caràcters",
|
"length": "Inclou com a mínim 8 caràcters",
|
||||||
"lowercase": "Inclou minúscules",
|
"lowercase": "Inclou minúscules",
|
||||||
"uppercase": "Inclou majúscules",
|
"uppercase": "Inclou majúscules",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "密码",
|
"label": "密码",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "至少包含 8 个字符",
|
"length": "至少包含 8 个字符",
|
||||||
"lowercase": "包含小写字母",
|
"lowercase": "包含小写字母",
|
||||||
"uppercase": "包含大写字母",
|
"uppercase": "包含大写字母",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "crwdns26:0crwdne26:0",
|
"label": "crwdns26:0crwdne26:0",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "crwdns28:0crwdne28:0",
|
"length": "crwdns28:0crwdne28:0",
|
||||||
"lowercase": "crwdns30:0crwdne30:0",
|
"lowercase": "crwdns30:0crwdne30:0",
|
||||||
"uppercase": "crwdns32:0crwdne32:0",
|
"uppercase": "crwdns32:0crwdne32:0",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Heslo",
|
"label": "Heslo",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Obsahuje alespoň 8 znaků",
|
"length": "Obsahuje alespoň 8 znaků",
|
||||||
"lowercase": "Obsahuje malé písmeno",
|
"lowercase": "Obsahuje malé písmeno",
|
||||||
"uppercase": "Obsahuje velké písmeno",
|
"uppercase": "Obsahuje velké písmeno",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Adgangskode",
|
"label": "Adgangskode",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Inkluderer mindst 8 tegn",
|
"length": "Inkluderer mindst 8 tegn",
|
||||||
"lowercase": "Inkluderer små bogstaver",
|
"lowercase": "Inkluderer små bogstaver",
|
||||||
"uppercase": "Inkluderer store bogstaver",
|
"uppercase": "Inkluderer store bogstaver",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Passwort",
|
"label": "Passwort",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Enthält mindestens 8 Zeichen",
|
"length": "Enthält mindestens 8 Zeichen",
|
||||||
"lowercase": "Enthält Kleinbuchstaben",
|
"lowercase": "Enthält Kleinbuchstaben",
|
||||||
"uppercase": "Enthält Großbuchstaben",
|
"uppercase": "Enthält Großbuchstaben",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Passwort",
|
"label": "Passwort",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Enthält mindestens 8 Zeichen",
|
"length": "Enthält mindestens 8 Zeichen",
|
||||||
"lowercase": "Enthält Kleinbuchstaben",
|
"lowercase": "Enthält Kleinbuchstaben",
|
||||||
"uppercase": "Enthält Großbuchstaben",
|
"uppercase": "Enthält Großbuchstaben",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Κωδικός",
|
"label": "Κωδικός",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "Περιλαμβάνει πεζό γράμμα",
|
"lowercase": "Περιλαμβάνει πεζό γράμμα",
|
||||||
"uppercase": "Περιλαμβάνει κεφαλαίο γράμμα",
|
"uppercase": "Περιλαμβάνει κεφαλαίο γράμμα",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Password",
|
"label": "Password",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Includes a minimum of 8 characters",
|
"length": "Includes a minimum of 8 characters",
|
||||||
"lowercase": "Includes a lower-case letter",
|
"lowercase": "Includes a lower-case letter",
|
||||||
"uppercase": "Includes an upper-case letter",
|
"uppercase": "Includes an upper-case letter",
|
||||||
|
|||||||
@@ -132,8 +132,9 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Password",
|
"label": "Password",
|
||||||
"requirement": {
|
"suggestionsTitle": "Password suggestions",
|
||||||
"length": "Includes at least 8 characters",
|
"suggestion": {
|
||||||
|
"length": "At least 8 characters",
|
||||||
"lowercase": "Includes lowercase letter",
|
"lowercase": "Includes lowercase letter",
|
||||||
"uppercase": "Includes uppercase letter",
|
"uppercase": "Includes uppercase letter",
|
||||||
"number": "Includes number",
|
"number": "Includes number",
|
||||||
@@ -1405,6 +1406,9 @@
|
|||||||
"openNewTab": {
|
"openNewTab": {
|
||||||
"label": "Open in new tab"
|
"label": "Open in new tab"
|
||||||
},
|
},
|
||||||
|
"withBorder": {
|
||||||
|
"label": "Show border"
|
||||||
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"label": "Bookmarks",
|
"label": "Bookmarks",
|
||||||
"add": "Add bookmark"
|
"add": "Add bookmark"
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Contraseña",
|
"label": "Contraseña",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Incluye al menos 8 caracteres",
|
"length": "Incluye al menos 8 caracteres",
|
||||||
"lowercase": "Incluye al menos una letra minúscula",
|
"lowercase": "Incluye al menos una letra minúscula",
|
||||||
"uppercase": "Incluye al menos una letra mayúscula",
|
"uppercase": "Incluye al menos una letra mayúscula",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "",
|
"label": "",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "",
|
"lowercase": "",
|
||||||
"uppercase": "",
|
"uppercase": "",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Mot de passe",
|
"label": "Mot de passe",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Contient au moins 8 caractères",
|
"length": "Contient au moins 8 caractères",
|
||||||
"lowercase": "Inclut une lettre minuscule",
|
"lowercase": "Inclut une lettre minuscule",
|
||||||
"uppercase": "Inclut une lettre majuscule",
|
"uppercase": "Inclut une lettre majuscule",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "סיסמה",
|
"label": "סיסמה",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "כולל לפחות 8 תווים",
|
"length": "כולל לפחות 8 תווים",
|
||||||
"lowercase": "אפשר אותיות קטנות",
|
"lowercase": "אפשר אותיות קטנות",
|
||||||
"uppercase": "אפשר אותיות גדולות",
|
"uppercase": "אפשר אותיות גדולות",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Lozinka",
|
"label": "Lozinka",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "Uključuje mala slova",
|
"lowercase": "Uključuje mala slova",
|
||||||
"uppercase": "Uključuje veliko slovo",
|
"uppercase": "Uključuje veliko slovo",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Jelszó",
|
"label": "Jelszó",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Tartalmazzon legalább 8 karaktert",
|
"length": "Tartalmazzon legalább 8 karaktert",
|
||||||
"lowercase": "Tartalmazzon kisbetűt",
|
"lowercase": "Tartalmazzon kisbetűt",
|
||||||
"uppercase": "Tartalmazzon nagybetűt",
|
"uppercase": "Tartalmazzon nagybetűt",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Password",
|
"label": "Password",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Include almeno 8 caratteri",
|
"length": "Include almeno 8 caratteri",
|
||||||
"lowercase": "Include lettera minuscola",
|
"lowercase": "Include lettera minuscola",
|
||||||
"uppercase": "Include lettera maiuscola",
|
"uppercase": "Include lettera maiuscola",
|
||||||
@@ -1056,7 +1056,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"noResults": {
|
"noResults": {
|
||||||
"title": ""
|
"title": "Nessun media caricato"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
@@ -1088,8 +1088,8 @@
|
|||||||
"checkLogs": "Controlla i log per maggiori dettagli",
|
"checkLogs": "Controlla i log per maggiori dettagli",
|
||||||
"tryAgain": "Riprova",
|
"tryAgain": "Riprova",
|
||||||
"loading": "Caricamento",
|
"loading": "Caricamento",
|
||||||
"show": "",
|
"show": "Visualizza",
|
||||||
"hide": ""
|
"hide": "Nascondi"
|
||||||
},
|
},
|
||||||
"here": "qui",
|
"here": "qui",
|
||||||
"iconPicker": {
|
"iconPicker": {
|
||||||
@@ -1255,7 +1255,7 @@
|
|||||||
"label": {
|
"label": {
|
||||||
"create": "Nuova categoria",
|
"create": "Nuova categoria",
|
||||||
"changePosition": "Cambia posizione",
|
"changePosition": "Cambia posizione",
|
||||||
"settings": ""
|
"settings": "Impostazioni"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"openAllInNewTabs": {
|
"openAllInNewTabs": {
|
||||||
@@ -3459,7 +3459,7 @@
|
|||||||
"private": "Questa board è privata"
|
"private": "Questa board è privata"
|
||||||
},
|
},
|
||||||
"noResults": {
|
"noResults": {
|
||||||
"title": ""
|
"title": "Ancora nessuna board"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"createBoard": {
|
"createBoard": {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "パスワード",
|
"label": "パスワード",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "少なくとも 8 文字以上を含む",
|
"length": "少なくとも 8 文字以上を含む",
|
||||||
"lowercase": "小文字を含む",
|
"lowercase": "小文字を含む",
|
||||||
"uppercase": "大文字を含む",
|
"uppercase": "大文字を含む",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "비밀번호",
|
"label": "비밀번호",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "소문자 포함",
|
"lowercase": "소문자 포함",
|
||||||
"uppercase": "대문자 포함",
|
"uppercase": "대문자 포함",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Slaptažodis",
|
"label": "Slaptažodis",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "",
|
"lowercase": "",
|
||||||
"uppercase": "",
|
"uppercase": "",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Parole",
|
"label": "Parole",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "Ietver mazo burtu",
|
"lowercase": "Ietver mazo burtu",
|
||||||
"uppercase": "Ietver lielo burtu",
|
"uppercase": "Ietver lielo burtu",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Wachtwoord",
|
"label": "Wachtwoord",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Bevat ten minste 8 tekens",
|
"length": "Bevat ten minste 8 tekens",
|
||||||
"lowercase": "Inclusief kleine letter",
|
"lowercase": "Inclusief kleine letter",
|
||||||
"uppercase": "Inclusief hoofdletter",
|
"uppercase": "Inclusief hoofdletter",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Passord",
|
"label": "Passord",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Inkluderer minst 8 tegn",
|
"length": "Inkluderer minst 8 tegn",
|
||||||
"lowercase": "Inkluderer liten bokstav",
|
"lowercase": "Inkluderer liten bokstav",
|
||||||
"uppercase": "Inkluderer stor bokstav",
|
"uppercase": "Inkluderer stor bokstav",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Hasło",
|
"label": "Hasło",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Zawiera co najmniej 8 znaków",
|
"length": "Zawiera co najmniej 8 znaków",
|
||||||
"lowercase": "Zawiera małą literę",
|
"lowercase": "Zawiera małą literę",
|
||||||
"uppercase": "Zawiera wielką literę",
|
"uppercase": "Zawiera wielką literę",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Senha",
|
"label": "Senha",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Inclua pelo menos 8 caracteres",
|
"length": "Inclua pelo menos 8 caracteres",
|
||||||
"lowercase": "Inclui letras minúsculas",
|
"lowercase": "Inclui letras minúsculas",
|
||||||
"uppercase": "Inclui letras maiúsculas",
|
"uppercase": "Inclui letras maiúsculas",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "",
|
"label": "",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "",
|
"lowercase": "",
|
||||||
"uppercase": "",
|
"uppercase": "",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Parola",
|
"label": "Parola",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "Să includă litere mici",
|
"lowercase": "Să includă litere mici",
|
||||||
"uppercase": "Să includă litere mari",
|
"uppercase": "Să includă litere mari",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Пароль",
|
"label": "Пароль",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Содержит не менее 8 символов",
|
"length": "Содержит не менее 8 символов",
|
||||||
"lowercase": "Содержит строчную букву",
|
"lowercase": "Содержит строчную букву",
|
||||||
"uppercase": "Содержит заглавную букву",
|
"uppercase": "Содержит заглавную букву",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Heslo",
|
"label": "Heslo",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Obsahuje aspoň 8 znakov",
|
"length": "Obsahuje aspoň 8 znakov",
|
||||||
"lowercase": "Zahŕňa malé písmeno",
|
"lowercase": "Zahŕňa malé písmeno",
|
||||||
"uppercase": "Zahŕňa veľké písmená",
|
"uppercase": "Zahŕňa veľké písmená",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Geslo",
|
"label": "Geslo",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "",
|
"length": "",
|
||||||
"lowercase": "Vključuje male črke",
|
"lowercase": "Vključuje male črke",
|
||||||
"uppercase": "Vključuje velike tiskane črke",
|
"uppercase": "Vključuje velike tiskane črke",
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
"form": {
|
"form": {
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Gruppens namn",
|
"label": "Gruppens namn",
|
||||||
"description": ""
|
"description": "Namnet måste matcha admin-grupp i extern tjänst"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Lösenord",
|
"label": "Lösenord",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Använd minst 8 tecken",
|
"length": "Använd minst 8 tecken",
|
||||||
"lowercase": "Använd liten bokstav",
|
"lowercase": "Använd liten bokstav",
|
||||||
"uppercase": "Använd stor bokstav",
|
"uppercase": "Använd stor bokstav",
|
||||||
@@ -159,7 +159,7 @@
|
|||||||
"label": "Visa sökresultat i ny flik"
|
"label": "Visa sökresultat i ny flik"
|
||||||
},
|
},
|
||||||
"ddgBangs": {
|
"ddgBangs": {
|
||||||
"label": ""
|
"label": "Aktivera DuckDuckGo bangs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -252,10 +252,10 @@
|
|||||||
"changeDdgBangs": {
|
"changeDdgBangs": {
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": ""
|
"message": "DuckDuckGo bangs inställningen ändrad"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": ""
|
"message": "DuckDuckGo bangs inställningen kunde inte ändras"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -350,12 +350,12 @@
|
|||||||
"description": "Tillåt medlemmar att addera valfri applikation på sina tavlor"
|
"description": "Tillåt medlemmar att addera valfri applikation på sina tavlor"
|
||||||
},
|
},
|
||||||
"modify-all": {
|
"modify-all": {
|
||||||
"label": "",
|
"label": "Modifiera alla appar",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar modifierar alla appar"
|
||||||
},
|
},
|
||||||
"full-all": {
|
"full-all": {
|
||||||
"label": "",
|
"label": "Fullständiga rättigheter till appar",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar hanterar, använder och raderar alla appar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -363,20 +363,20 @@
|
|||||||
"title": "Tavlor",
|
"title": "Tavlor",
|
||||||
"item": {
|
"item": {
|
||||||
"create": {
|
"create": {
|
||||||
"label": "",
|
"label": "Skapa tavlor",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar skapar tavlor"
|
||||||
},
|
},
|
||||||
"view-all": {
|
"view-all": {
|
||||||
"label": "",
|
"label": "Se alla tavlor",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar ser alla tavlor"
|
||||||
},
|
},
|
||||||
"modify-all": {
|
"modify-all": {
|
||||||
"label": "",
|
"label": "Modifiera alla tavlor",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar modifierar alla tavlor (Detta inkluderar inte åtkomstkontroll eller farozonen)"
|
||||||
},
|
},
|
||||||
"full-all": {
|
"full-all": {
|
||||||
"label": "",
|
"label": "Fullständiga rättigheter till tavlor",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar ser, modifierar och tar bort alla tavlor (detta inkluderar åtkomstkontroll och farozonen)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -392,38 +392,38 @@
|
|||||||
"description": "Tillåter medlemmar att addera valfri integration till sina tavlor"
|
"description": "Tillåter medlemmar att addera valfri integration till sina tavlor"
|
||||||
},
|
},
|
||||||
"interact-all": {
|
"interact-all": {
|
||||||
"label": "",
|
"label": "Interagera med alla integrationer",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar interagerar med alla integrationer"
|
||||||
},
|
},
|
||||||
"full-all": {
|
"full-all": {
|
||||||
"label": "",
|
"label": "Fullständiga rättigheter till integrationer",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar hanterar, använder och interagerar med alla integrationer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"media": {
|
"media": {
|
||||||
"title": "",
|
"title": "Media",
|
||||||
"item": {
|
"item": {
|
||||||
"upload": {
|
"upload": {
|
||||||
"label": "",
|
"label": "Ladda upp media",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar laddar upp media"
|
||||||
},
|
},
|
||||||
"view-all": {
|
"view-all": {
|
||||||
"label": "",
|
"label": "Se all media",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar ser all media"
|
||||||
},
|
},
|
||||||
"full-all": {
|
"full-all": {
|
||||||
"label": "",
|
"label": "Fullständiga rättigheter till media",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar hanterar och raderar all media"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"other": {
|
"other": {
|
||||||
"title": "",
|
"title": "Annat",
|
||||||
"item": {
|
"item": {
|
||||||
"view-logs": {
|
"view-logs": {
|
||||||
"label": "",
|
"label": "Se loggar",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar ser loggar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -435,32 +435,32 @@
|
|||||||
"description": "Tillåt medlemmar att skapa sökmotorer"
|
"description": "Tillåt medlemmar att skapa sökmotorer"
|
||||||
},
|
},
|
||||||
"modify-all": {
|
"modify-all": {
|
||||||
"label": "",
|
"label": "Modifiera alla sökmotorer",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar modifierar alla sökmotorer"
|
||||||
},
|
},
|
||||||
"full-all": {
|
"full-all": {
|
||||||
"label": "",
|
"label": "Fullständiga rättigheter till sökmotorer",
|
||||||
"description": ""
|
"description": "Tillåt att medlemmar hanterar och raderar alla sökmotorer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memberNotice": {
|
"memberNotice": {
|
||||||
"mixed": "",
|
"mixed": "Vissa medlemmar är från externa tjänster och kan inte hantera här",
|
||||||
"external": ""
|
"external": "Alla medlemmar är från externa tjänster och kan inte hanteras här"
|
||||||
},
|
},
|
||||||
"reservedNotice": {
|
"reservedNotice": {
|
||||||
"message": ""
|
"message": "Denna grupp är reserverad för systemet så vissa åtgärder är begränsade. <checkoutDocs></checkoutDocs>"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"create": {
|
"create": {
|
||||||
"label": "Addera en grupp",
|
"label": "Addera en grupp",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": ""
|
"message": "Gruppen har skapats"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": ""
|
"message": "Gruppen kunde inte skapas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -470,7 +470,7 @@
|
|||||||
"confirm": "Är du säker på att du vill överföra ägarskapet för gruppen {name} till {username}?",
|
"confirm": "Är du säker på att du vill överföra ägarskapet för gruppen {name} till {username}?",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": ""
|
"message": "Överfört grupp {group} till {user}"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": "Kunde inte överföra ägarskap"
|
"message": "Kunde inte överföra ägarskap"
|
||||||
@@ -501,38 +501,38 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Behörigheter sparade",
|
"title": "Behörigheter sparade",
|
||||||
"message": ""
|
"message": "Rättigheterna sparade"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Rättigheterna ej sparade",
|
||||||
"message": ""
|
"message": "Rättigheterna har inte sparats"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": ""
|
"message": "Gruppen {name} har sparats"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": ""
|
"message": "Kunde inte spara gruppen {name}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"label": "Välj en grupp",
|
"label": "Välj en grupp",
|
||||||
"notFound": ""
|
"notFound": "Ingen grupp hittad"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"board": {
|
"board": {
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Inställningarna sparades",
|
"title": "Inställningarna sparades",
|
||||||
"message": ""
|
"message": "Inställningarna för tavlan har sparats"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Kunde inte spara inställningarna",
|
"title": "Kunde inte spara inställningarna",
|
||||||
"message": ""
|
"message": "Kunde inte spara inställningarna för tavlan"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -540,10 +540,10 @@
|
|||||||
"changePosition": {
|
"changePosition": {
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"message": ""
|
"message": "Ordning ändrad"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"message": ""
|
"message": "Ordningen kunde ändras"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -559,20 +559,20 @@
|
|||||||
"list": {
|
"list": {
|
||||||
"title": "Applikationer",
|
"title": "Applikationer",
|
||||||
"noResults": {
|
"noResults": {
|
||||||
"title": "",
|
"title": "Det finns inga appar ännu",
|
||||||
"action": ""
|
"action": "Skapa din första app"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Addera applikation",
|
"title": "Addera applikation",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Skapandet lyckades",
|
||||||
"message": ""
|
"message": "Appen skapades framgångsrikt"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Skapandet misslyckades",
|
||||||
"message": ""
|
"message": "Appen kunde inte skapas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -581,11 +581,11 @@
|
|||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Ändringarna har sparats",
|
"title": "Ändringarna har sparats",
|
||||||
"message": ""
|
"message": "Appen sparades"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Misslyckades med att spara ändringarna",
|
"title": "Misslyckades med att spara ändringarna",
|
||||||
"message": ""
|
"message": "Appen kunde inte sparas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -594,12 +594,12 @@
|
|||||||
"message": "Är du säker på att du vill ta bort applikationen {name}?",
|
"message": "Är du säker på att du vill ta bort applikationen {name}?",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Borttagning lyckades",
|
||||||
"message": ""
|
"message": "Appen har tagits bort"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Borttagning misslyckades",
|
||||||
"message": ""
|
"message": "Kunde inte ta bort appen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -612,21 +612,21 @@
|
|||||||
"label": "Beskrivning"
|
"label": "Beskrivning"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"label": ""
|
"label": "URL"
|
||||||
},
|
},
|
||||||
"useDifferentUrlForPing": {
|
"useDifferentUrlForPing": {
|
||||||
"checkbox": {
|
"checkbox": {
|
||||||
"label": "",
|
"label": "Använd annan URL för ping",
|
||||||
"description": ""
|
"description": "Användbart om Homarr kan komma åt appen direkt via ett internt host-namn eller nätverk för att undvika bandbreddsanvändning hos internetleverantör"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"select": {
|
"select": {
|
||||||
"label": "",
|
"label": "Välj app",
|
||||||
"notFound": "",
|
"notFound": "Ingen app hittades",
|
||||||
"search": "Sök efter en applikation",
|
"search": "Sök efter en applikation",
|
||||||
"noResults": "",
|
"noResults": "Inga resultat",
|
||||||
"action": "Välj {app}",
|
"action": "Välj {app}",
|
||||||
"title": "Välj en applikation att lägga till på tavlan"
|
"title": "Välj en applikation att lägga till på tavlan"
|
||||||
},
|
},
|
||||||
@@ -642,56 +642,56 @@
|
|||||||
"page": {
|
"page": {
|
||||||
"list": {
|
"list": {
|
||||||
"title": "Integrationer",
|
"title": "Integrationer",
|
||||||
"search": "",
|
"search": "Sök integrationer",
|
||||||
"noResults": {
|
"noResults": {
|
||||||
"title": ""
|
"title": "Det finns inga integrationer ännu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "",
|
"title": "Ny {name} integration",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Skapandet lyckades",
|
||||||
"message": ""
|
"message": "Integrationen har skapats"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Skapandet misslyckades",
|
||||||
"message": ""
|
"message": "Integrationen kunde inte skapas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"option": {
|
"option": {
|
||||||
"existing": {
|
"existing": {
|
||||||
"title": "",
|
"title": "Befintlig",
|
||||||
"label": ""
|
"label": "Välj befintlig app"
|
||||||
},
|
},
|
||||||
"new": {
|
"new": {
|
||||||
"title": "",
|
"title": "Ny",
|
||||||
"url": {
|
"url": {
|
||||||
"label": "",
|
"label": "App URL",
|
||||||
"description": ""
|
"description": "URL:en som appen kommer att öppna när den används från tavlan"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"title": "",
|
"title": "Redigera {name} integration",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "Ändringarna har sparats",
|
"title": "Ändringarna har sparats",
|
||||||
"message": ""
|
"message": "Integrationen sparad"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Kunde inte tillämpa ändringar",
|
||||||
"message": ""
|
"message": "Integrationen kunde inte sparas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"action": {
|
"action": {
|
||||||
"add": "",
|
"add": "Länka en app",
|
||||||
"remove": "",
|
"remove": "Ta bort länk",
|
||||||
"select": ""
|
"select": "Välj en app att länka till"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -700,12 +700,12 @@
|
|||||||
"message": "Är du säker på att du vill ta bort integrationen {name}?",
|
"message": "Är du säker på att du vill ta bort integrationen {name}?",
|
||||||
"notification": {
|
"notification": {
|
||||||
"success": {
|
"success": {
|
||||||
"title": "",
|
"title": "Borttagning lyckades",
|
||||||
"message": ""
|
"message": "Integrationen har tagits bort"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"title": "",
|
"title": "Borttagning misslyckades",
|
||||||
"message": ""
|
"message": "Kunde inte ta bort integrationen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,44 +715,44 @@
|
|||||||
"label": "Namn"
|
"label": "Namn"
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"label": ""
|
"label": "URL"
|
||||||
},
|
},
|
||||||
"attemptSearchEngineCreation": {
|
"attemptSearchEngineCreation": {
|
||||||
"label": "",
|
"label": "Skapa sökmotor",
|
||||||
"description": ""
|
"description": "Integrationen \"{kind}\" kan inte användas med sökmotorer. Markera detta för att automatiskt konfigurera sökmotorn."
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sectionTitle": ""
|
"sectionTitle": "Länkad app"
|
||||||
},
|
},
|
||||||
"createApp": {
|
"createApp": {
|
||||||
"label": "",
|
"label": "Skapa app",
|
||||||
"description": ""
|
"description": "Skapa en app med samma namn och ikon som integrationen. Lämna fältet nedan tomt för att skapa en app med integrationens URL."
|
||||||
},
|
},
|
||||||
"appHref": {
|
"appHref": {
|
||||||
"placeholder": ""
|
"placeholder": "Egen app-URL"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"create": ""
|
"create": "Ny integration"
|
||||||
},
|
},
|
||||||
"testConnection": {
|
"testConnection": {
|
||||||
"action": {
|
"action": {
|
||||||
"create": "",
|
"create": "Testa anslutningen och skapa",
|
||||||
"edit": ""
|
"edit": "Testa anslutningen och skapa"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"common": {
|
"common": {
|
||||||
"cause": {
|
"cause": {
|
||||||
"title": ""
|
"title": "Orsak med mer detaljer"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"unknown": {
|
"unknown": {
|
||||||
"title": "",
|
"title": "Okänt fel",
|
||||||
"description": ""
|
"description": "Ett okänt fel inträffade, öppna orsaken nedan för att se mer detaljer"
|
||||||
},
|
},
|
||||||
"parse": {
|
"parse": {
|
||||||
"title": "",
|
"title": "Parsningsfel",
|
||||||
"description": ""
|
"description": "Svaret kunde inte analyseras. Verifiera att URL pekar på bas-URL för tjänsten."
|
||||||
},
|
},
|
||||||
"authorization": {
|
"authorization": {
|
||||||
"title": "",
|
"title": "",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Şifre",
|
"label": "Şifre",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "En az 8 karakter içermeli",
|
"length": "En az 8 karakter içermeli",
|
||||||
"lowercase": "Küçük harf içermeli",
|
"lowercase": "Küçük harf içermeli",
|
||||||
"uppercase": "Büyük harf içermeli",
|
"uppercase": "Büyük harf içermeli",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Пароль",
|
"label": "Пароль",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Включає принаймні 8 символів",
|
"length": "Включає принаймні 8 символів",
|
||||||
"lowercase": "Включає малу літеру",
|
"lowercase": "Включає малу літеру",
|
||||||
"uppercase": "Включає велику літеру",
|
"uppercase": "Включає велику літеру",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "Mật khẩu",
|
"label": "Mật khẩu",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "Bao gồm ít nhất 8 kí tự",
|
"length": "Bao gồm ít nhất 8 kí tự",
|
||||||
"lowercase": "Bao gồm chữ thường",
|
"lowercase": "Bao gồm chữ thường",
|
||||||
"uppercase": "Bao gồm chữ in hoa",
|
"uppercase": "Bao gồm chữ in hoa",
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
},
|
},
|
||||||
"password": {
|
"password": {
|
||||||
"label": "密碼",
|
"label": "密碼",
|
||||||
"requirement": {
|
"suggestion": {
|
||||||
"length": "至少包含 8 個字符",
|
"length": "至少包含 8 個字符",
|
||||||
"lowercase": "包含小寫字母",
|
"lowercase": "包含小寫字母",
|
||||||
"uppercase": "包含大寫字母",
|
"uppercase": "包含大寫字母",
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ export const CustomPasswordInput = ({ withPasswordRequirements, ...props }: Cust
|
|||||||
};
|
};
|
||||||
|
|
||||||
const WithPasswordRequirements = (props: PasswordInputProps) => {
|
const WithPasswordRequirements = (props: PasswordInputProps) => {
|
||||||
const [value, setValue] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
setValue(event.currentTarget.value);
|
setPassword(event.currentTarget.value);
|
||||||
props.onChange?.(event);
|
props.onChange?.(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PasswordRequirementsPopover password={value}>
|
<PasswordRequirementsPopover password={password}>
|
||||||
<PasswordInput {...props} onChange={onChange} />
|
<PasswordInput {...props} onChange={onChange} />
|
||||||
</PasswordRequirementsPopover>
|
</PasswordRequirementsPopover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import { rem, Text } from "@mantine/core";
|
import { rem, Text } from "@mantine/core";
|
||||||
import { IconCheck, IconX } from "@tabler/icons-react";
|
import { IconCheck, IconPoint } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
const getRequirementDisplay = (meets: boolean) =>
|
||||||
|
({
|
||||||
|
true: { color: "teal", Icon: IconCheck },
|
||||||
|
false: { color: "dimmed", Icon: IconPoint },
|
||||||
|
})[String(meets) as "true" | "false"];
|
||||||
|
|
||||||
export function PasswordRequirement({ meets, label }: { meets: boolean; label: string }) {
|
export function PasswordRequirement({ meets, label }: { meets: boolean; label: string }) {
|
||||||
|
const display = getRequirementDisplay(meets);
|
||||||
|
const Icon = display.Icon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text c={meets ? "teal" : "red"} display="flex" style={{ alignItems: "center" }} size="sm">
|
<Text c={display.color} display="flex" style={{ alignItems: "center" }} size="sm">
|
||||||
{meets ? (
|
<Icon style={{ width: rem(14), height: rem(14) }} />
|
||||||
<IconCheck style={{ width: rem(14), height: rem(14) }} />
|
|
||||||
) : (
|
|
||||||
<IconX style={{ width: rem(14), height: rem(14) }} />
|
|
||||||
)}
|
|
||||||
<Text span ml={10}>
|
<Text span ml={10}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Popover, Progress } from "@mantine/core";
|
import { Popover, Progress, Text } from "@mantine/core";
|
||||||
|
|
||||||
import { useScopedI18n } from "@homarr/translation/client";
|
import { useScopedI18n } from "@homarr/translation/client";
|
||||||
import { passwordRequirements } from "@homarr/validation/user";
|
import { passwordRequirements } from "@homarr/validation/user";
|
||||||
|
|
||||||
import { PasswordRequirement } from "./password-requirement";
|
import { PasswordRequirement } from "./password-requirement";
|
||||||
|
|
||||||
|
const strengthColorMap = [
|
||||||
|
{ min: 100, color: "teal" },
|
||||||
|
{ min: 51, color: "yellow" },
|
||||||
|
{ min: 0, color: "dimmed" },
|
||||||
|
] as const;
|
||||||
|
|
||||||
export const PasswordRequirementsPopover = ({ password, children }: PropsWithChildren<{ password: string }>) => {
|
export const PasswordRequirementsPopover = ({ password, children }: PropsWithChildren<{ password: string }>) => {
|
||||||
|
const tPassword = useScopedI18n("user.field.password");
|
||||||
const requirements = useRequirements();
|
const requirements = useRequirements();
|
||||||
const strength = useStrength(password);
|
const strength = useStrength(password);
|
||||||
const [popoverOpened, setPopoverOpened] = useState(false);
|
const [popoverOpened, setPopoverOpened] = useState(false);
|
||||||
|
const strengthColor = strengthColorMap.find(({ min }) => strength >= min)?.color ?? "dimmed";
|
||||||
const checks = (
|
const checks = (
|
||||||
<>
|
<>
|
||||||
{requirements.map((requirement) => (
|
{requirements.map((requirement) => (
|
||||||
@@ -19,8 +27,6 @@ export const PasswordRequirementsPopover = ({ password, children }: PropsWithChi
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = strength === 100 ? "teal" : strength > 50 ? "yellow" : "red";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover opened={popoverOpened} position="bottom" width="target" transitionProps={{ transition: "pop" }}>
|
<Popover opened={popoverOpened} position="bottom" width="target" transitionProps={{ transition: "pop" }}>
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
@@ -29,7 +35,10 @@ export const PasswordRequirementsPopover = ({ password, children }: PropsWithChi
|
|||||||
</div>
|
</div>
|
||||||
</Popover.Target>
|
</Popover.Target>
|
||||||
<Popover.Dropdown>
|
<Popover.Dropdown>
|
||||||
<Progress color={color} value={strength} size={5} mb="xs" />
|
<Progress color={strengthColor} value={strength} size={5} mb="xs" />
|
||||||
|
<Text size="sm" c="dimmed" mb="xs">
|
||||||
|
{tPassword("suggestionsTitle")}
|
||||||
|
</Text>
|
||||||
{checks}
|
{checks}
|
||||||
</Popover.Dropdown>
|
</Popover.Dropdown>
|
||||||
</Popover>
|
</Popover>
|
||||||
@@ -37,7 +46,7 @@ export const PasswordRequirementsPopover = ({ password, children }: PropsWithChi
|
|||||||
};
|
};
|
||||||
|
|
||||||
const useRequirements = () => {
|
const useRequirements = () => {
|
||||||
const t = useScopedI18n("user.field.password.requirement");
|
const t = useScopedI18n("user.field.password.suggestion");
|
||||||
|
|
||||||
return passwordRequirements.map(({ check, value }) => ({ check, label: t(value) }));
|
return passwordRequirements.map(({ check, value }) => ({ check, label: t(value) }));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,24 +19,10 @@ export const passwordRequirements = [
|
|||||||
{ check: regexCheck(/[$&+,:;=?@#|'<>.^*()%!\-~`"_/\\[\]{}]/), value: "special" },
|
{ check: regexCheck(/[$&+,:;=?@#|'<>.^*()%!\-~`"_/\\[\]{}]/), value: "special" },
|
||||||
] satisfies {
|
] satisfies {
|
||||||
check: (value: string) => boolean;
|
check: (value: string) => boolean;
|
||||||
value: keyof TranslationObject["user"]["field"]["password"]["requirement"];
|
value: keyof TranslationObject["user"]["field"]["password"]["suggestion"];
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export const userPasswordSchema = z
|
export const userPasswordSchema = z.string().min(8).max(255);
|
||||||
.string()
|
|
||||||
.min(8)
|
|
||||||
.max(255)
|
|
||||||
.refine(
|
|
||||||
(value) => {
|
|
||||||
return passwordRequirements.every((requirement) => requirement.check(value));
|
|
||||||
},
|
|
||||||
{
|
|
||||||
params: createCustomErrorParams({
|
|
||||||
key: "passwordRequirements",
|
|
||||||
params: {},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const addConfirmPasswordRefinement = <
|
const addConfirmPasswordRefinement = <
|
||||||
TSchema extends z.ZodObject<{ password: z.core.$ZodString; confirmPassword: z.core.$ZodString }, z.core.$strip>,
|
TSchema extends z.ZodObject<{ password: z.core.$ZodString; confirmPassword: z.core.$ZodString }, z.core.$strip>,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default function BookmarksWidget({ options, itemId }: WidgetComponentProp
|
|||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
const [data] = clientApi.app.byIds.useSuspenseQuery(options.items, {
|
const [data] = clientApi.app.byIds.useSuspenseQuery(options.items, {
|
||||||
select(data) {
|
select(data) {
|
||||||
return data.sort((appA, appB) => options.items.indexOf(appA.id) - options.items.indexOf(appB.id));
|
return data.toSorted((appA, appB) => options.items.indexOf(appA.id) - options.items.indexOf(appB.id));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ export default function BookmarksWidget({ options, itemId }: WidgetComponentProp
|
|||||||
hideIcon={options.hideIcon}
|
hideIcon={options.hideIcon}
|
||||||
hideHostname={options.hideHostname}
|
hideHostname={options.hideHostname}
|
||||||
openNewTab={options.openNewTab}
|
openNewTab={options.openNewTab}
|
||||||
|
withBorder={options.withBorder}
|
||||||
hasIconColor={board.iconColor !== null}
|
hasIconColor={board.iconColor !== null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -67,6 +68,7 @@ export default function BookmarksWidget({ options, itemId }: WidgetComponentProp
|
|||||||
hideIcon={options.hideIcon}
|
hideIcon={options.hideIcon}
|
||||||
hideHostname={options.hideHostname}
|
hideHostname={options.hideHostname}
|
||||||
openNewTab={options.openNewTab}
|
openNewTab={options.openNewTab}
|
||||||
|
withBorder={options.withBorder}
|
||||||
hasIconColor={board.iconColor !== null}
|
hasIconColor={board.iconColor !== null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -81,6 +83,7 @@ interface FlexLayoutProps {
|
|||||||
hideIcon: boolean;
|
hideIcon: boolean;
|
||||||
hideHostname: boolean;
|
hideHostname: boolean;
|
||||||
openNewTab: boolean;
|
openNewTab: boolean;
|
||||||
|
withBorder: boolean;
|
||||||
hasIconColor: boolean;
|
hasIconColor: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +94,7 @@ const FlexLayout = ({
|
|||||||
hideIcon,
|
hideIcon,
|
||||||
hideHostname,
|
hideHostname,
|
||||||
openNewTab,
|
openNewTab,
|
||||||
|
withBorder,
|
||||||
hasIconColor,
|
hasIconColor,
|
||||||
}: FlexLayoutProps) => {
|
}: FlexLayoutProps) => {
|
||||||
const board = useRequiredBoard();
|
const board = useRequiredBoard();
|
||||||
@@ -106,7 +110,15 @@ const FlexLayout = ({
|
|||||||
key={app.id}
|
key={app.id}
|
||||||
w="100%"
|
w="100%"
|
||||||
>
|
>
|
||||||
<Card radius={board.itemRadius} className={classes.card} w="100%" display="flex" p={4} h="100%">
|
<Card
|
||||||
|
radius={board.itemRadius}
|
||||||
|
className={classes.card}
|
||||||
|
w="100%"
|
||||||
|
display="flex"
|
||||||
|
p={4}
|
||||||
|
h="100%"
|
||||||
|
withBorder={withBorder}
|
||||||
|
>
|
||||||
{direction === "row" ? (
|
{direction === "row" ? (
|
||||||
<VerticalItem
|
<VerticalItem
|
||||||
app={app}
|
app={app}
|
||||||
@@ -138,6 +150,7 @@ interface GridLayoutProps {
|
|||||||
hideIcon: boolean;
|
hideIcon: boolean;
|
||||||
hideHostname: boolean;
|
hideHostname: boolean;
|
||||||
openNewTab: boolean;
|
openNewTab: boolean;
|
||||||
|
withBorder: boolean;
|
||||||
itemDirection: "horizontal" | "vertical";
|
itemDirection: "horizontal" | "vertical";
|
||||||
hasIconColor: boolean;
|
hasIconColor: boolean;
|
||||||
}
|
}
|
||||||
@@ -148,6 +161,7 @@ const GridLayout = ({
|
|||||||
hideIcon,
|
hideIcon,
|
||||||
hideHostname,
|
hideHostname,
|
||||||
openNewTab,
|
openNewTab,
|
||||||
|
withBorder,
|
||||||
itemDirection,
|
itemDirection,
|
||||||
hasIconColor,
|
hasIconColor,
|
||||||
}: GridLayoutProps) => {
|
}: GridLayoutProps) => {
|
||||||
@@ -168,6 +182,7 @@ const GridLayout = ({
|
|||||||
h="100%"
|
h="100%"
|
||||||
className={combineClasses(classes.card, classes["card-grid"])}
|
className={combineClasses(classes.card, classes["card-grid"])}
|
||||||
radius={board.itemRadius}
|
radius={board.itemRadius}
|
||||||
|
withBorder={withBorder}
|
||||||
p="xs"
|
p="xs"
|
||||||
>
|
>
|
||||||
{itemDirection === "horizontal" ? (
|
{itemDirection === "horizontal" ? (
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const { definition, componentLoader } = createWidgetDefinition("bookmarks
|
|||||||
hideIcon: factory.switch({ defaultValue: false }),
|
hideIcon: factory.switch({ defaultValue: false }),
|
||||||
hideHostname: factory.switch({ defaultValue: false }),
|
hideHostname: factory.switch({ defaultValue: false }),
|
||||||
openNewTab: factory.switch({ defaultValue: true }),
|
openNewTab: factory.switch({ defaultValue: true }),
|
||||||
|
withBorder: factory.switch({ defaultValue: false }),
|
||||||
items: factory.sortableItemList<RouterOutputs["app"]["all"][number], string>({
|
items: factory.sortableItemList<RouterOutputs["app"]["all"][number], string>({
|
||||||
ItemComponent: ({ item, handle: Handle, removeItem, rootAttributes }) => {
|
ItemComponent: ({ item, handle: Handle, removeItem, rootAttributes }) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,20 +1,10 @@
|
|||||||
.statItem {
|
.statItem {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: var(--mantine-radius-md);
|
border-radius: var(--mantine-radius-md);
|
||||||
background-color: var(--mantine-color-gray-0);
|
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--mantine-color-gray-1);
|
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.statItem {
|
|
||||||
background-color: var(--mantine-color-dark-6);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--mantine-color-dark-5);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+798
-830
File diff suppressed because it is too large.
Load diff
+46
-46
@@ -6,7 +6,7 @@ catalog:
|
|||||||
"@auth/core": ^0.41.2
|
"@auth/core": ^0.41.2
|
||||||
"@auth/drizzle-adapter": ^1.11.2
|
"@auth/drizzle-adapter": ^1.11.2
|
||||||
"@ctrl/deluge": ^8.0.0
|
"@ctrl/deluge": ^8.0.0
|
||||||
"@ctrl/qbittorrent": ^9.12.1
|
"@ctrl/qbittorrent": ^9.12.2
|
||||||
"@ctrl/transmission": ^7.4.0
|
"@ctrl/transmission": ^7.4.0
|
||||||
"@dnd-kit/core": ^6.3.1
|
"@dnd-kit/core": ^6.3.1
|
||||||
"@dnd-kit/modifiers": ^9.0.0
|
"@dnd-kit/modifiers": ^9.0.0
|
||||||
@@ -20,17 +20,17 @@ catalog:
|
|||||||
"@immich/sdk": ^2.7.5
|
"@immich/sdk": ^2.7.5
|
||||||
"@jellyfin/sdk": ^0.13.0
|
"@jellyfin/sdk": ^0.13.0
|
||||||
"@kubernetes/client-node": ^1.4.0
|
"@kubernetes/client-node": ^1.4.0
|
||||||
"@mantine/charts": ^9.2.1
|
"@mantine/charts": ^9.2.2
|
||||||
"@mantine/colors-generator": ^9.2.1
|
"@mantine/colors-generator": ^9.2.2
|
||||||
"@mantine/core": ^9.2.1
|
"@mantine/core": ^9.2.2
|
||||||
"@mantine/dates": ^9.2.1
|
"@mantine/dates": ^9.2.2
|
||||||
"@mantine/dropzone": ^9.2.1
|
"@mantine/dropzone": ^9.2.2
|
||||||
"@mantine/form": ^9.2.1
|
"@mantine/form": ^9.2.2
|
||||||
"@mantine/hooks": ^9.2.1
|
"@mantine/hooks": ^9.2.2
|
||||||
"@mantine/modals": ^9.2.1
|
"@mantine/modals": ^9.2.2
|
||||||
"@mantine/notifications": ^9.2.1
|
"@mantine/notifications": ^9.2.2
|
||||||
"@mantine/spotlight": ^9.2.1
|
"@mantine/spotlight": ^9.2.2
|
||||||
"@mantine/tiptap": ^9.2.1
|
"@mantine/tiptap": ^9.2.2
|
||||||
"@next/eslint-plugin-next": 16.2.6
|
"@next/eslint-plugin-next": 16.2.6
|
||||||
"@octokit/auth-app": ^8.2.0
|
"@octokit/auth-app": ^8.2.0
|
||||||
"@paralleldrive/cuid2": ^3.3.0
|
"@paralleldrive/cuid2": ^3.3.0
|
||||||
@@ -43,34 +43,34 @@ catalog:
|
|||||||
"@semantic-release/release-notes-generator": ^14.1.1
|
"@semantic-release/release-notes-generator": ^14.1.1
|
||||||
"@t3-oss/env-nextjs": ^0.13.11
|
"@t3-oss/env-nextjs": ^0.13.11
|
||||||
"@tabler/icons-react": ^3.44.0
|
"@tabler/icons-react": ^3.44.0
|
||||||
"@tanstack/react-query": ^5.100.13
|
"@tanstack/react-query": ^5.100.14
|
||||||
"@tanstack/react-query-devtools": ^5.100.13
|
"@tanstack/react-query-devtools": ^5.100.14
|
||||||
"@tanstack/react-query-next-experimental": ^5.100.13
|
"@tanstack/react-query-next-experimental": ^5.100.14
|
||||||
"@testcontainers/mysql": ^12.0.0
|
"@testcontainers/mysql": ^12.0.1
|
||||||
"@testcontainers/postgresql": ^12.0.0
|
"@testcontainers/postgresql": ^12.0.1
|
||||||
"@testcontainers/redis": ^12.0.0
|
"@testcontainers/redis": ^12.0.1
|
||||||
"@tiptap/extension-color": 3.23.6
|
"@tiptap/extension-color": 3.24.0
|
||||||
"@tiptap/extension-highlight": 3.23.6
|
"@tiptap/extension-highlight": 3.24.0
|
||||||
"@tiptap/extension-image": 3.23.6
|
"@tiptap/extension-image": 3.24.0
|
||||||
"@tiptap/extension-link": ^3.23.6
|
"@tiptap/extension-link": ^3.24.0
|
||||||
"@tiptap/extension-placeholder": ^3.23.6
|
"@tiptap/extension-placeholder": ^3.24.0
|
||||||
"@tiptap/extension-table": 3.23.6
|
"@tiptap/extension-table": 3.24.0
|
||||||
"@tiptap/extension-table-cell": 3.23.6
|
"@tiptap/extension-table-cell": 3.24.0
|
||||||
"@tiptap/extension-table-header": 3.23.6
|
"@tiptap/extension-table-header": 3.24.0
|
||||||
"@tiptap/extension-table-row": 3.23.6
|
"@tiptap/extension-table-row": 3.24.0
|
||||||
"@tiptap/extension-task-item": 3.23.6
|
"@tiptap/extension-task-item": 3.24.0
|
||||||
"@tiptap/extension-task-list": 3.23.6
|
"@tiptap/extension-task-list": 3.24.0
|
||||||
"@tiptap/extension-text-align": 3.23.6
|
"@tiptap/extension-text-align": 3.24.0
|
||||||
"@tiptap/extension-text-style": 3.23.6
|
"@tiptap/extension-text-style": 3.24.0
|
||||||
"@tiptap/extension-underline": 3.23.6
|
"@tiptap/extension-underline": 3.24.0
|
||||||
"@tiptap/react": ^3.23.6
|
"@tiptap/react": ^3.24.0
|
||||||
"@tiptap/starter-kit": ^3.23.6
|
"@tiptap/starter-kit": ^3.24.0
|
||||||
"@trpc/client": ^11.17.0
|
"@trpc/client": ^11.17.0
|
||||||
"@trpc/next": ^11.17.0
|
"@trpc/next": ^11.17.0
|
||||||
"@trpc/react-query": ^11.17.0
|
"@trpc/react-query": ^11.17.0
|
||||||
"@trpc/server": ^11.17.0
|
"@trpc/server": ^11.17.0
|
||||||
"@trpc/tanstack-react-query": ^11.17.0
|
"@trpc/tanstack-react-query": ^11.17.0
|
||||||
"@turbo/gen": ^2.9.14
|
"@turbo/gen": ^2.9.16
|
||||||
"@types/adm-zip": 0.5.8
|
"@types/adm-zip": 0.5.8
|
||||||
"@types/bcrypt": 6.0.0
|
"@types/bcrypt": 6.0.0
|
||||||
"@types/better-sqlite3": 7.6.13
|
"@types/better-sqlite3": 7.6.13
|
||||||
@@ -109,7 +109,7 @@ catalog:
|
|||||||
croner: 10.0.1
|
croner: 10.0.1
|
||||||
cross-env: ^10.1.0
|
cross-env: ^10.1.0
|
||||||
crypto-js: ^4.2.0
|
crypto-js: ^4.2.0
|
||||||
dayjs: ^1.11.20
|
dayjs: ^1.11.21
|
||||||
deepmerge: 4.3.1
|
deepmerge: 4.3.1
|
||||||
dns-caching: ^0.2.9
|
dns-caching: ^0.2.9
|
||||||
dockerode: ^5.0.0
|
dockerode: ^5.0.0
|
||||||
@@ -124,30 +124,30 @@ catalog:
|
|||||||
flag-icons: ^7.5.0
|
flag-icons: ^7.5.0
|
||||||
glob: ^13.0.6
|
glob: ^13.0.6
|
||||||
ical.js: ^2.2.1
|
ical.js: ^2.2.1
|
||||||
ioredis: 5.10.1
|
ioredis: 5.11.0
|
||||||
isomorphic-dompurify: ^3.14.0
|
isomorphic-dompurify: ^3.15.0
|
||||||
jotai: ^2.20.0
|
jotai: ^2.20.0
|
||||||
jsdom: ^29.1.1
|
jsdom: ^29.1.1
|
||||||
json5: ^2.2.3
|
json5: ^2.2.3
|
||||||
ldapts: 8.1.8
|
ldapts: 8.1.8
|
||||||
lodash.clonedeep: ^4.5.0
|
lodash.clonedeep: ^4.5.0
|
||||||
mantine-react-table: 2.0.0-beta.9
|
mantine-react-table: npm:mantine-react-table-open@9.0.3
|
||||||
maria2: ^0.4.1
|
maria2: ^0.4.1
|
||||||
mysql2: 3.22.3
|
mysql2: 3.22.4
|
||||||
next: 16.2.6
|
next: 16.2.6
|
||||||
next-auth: 5.0.0-beta.31
|
next-auth: 5.0.0-beta.31
|
||||||
next-intl: 4.12.0
|
next-intl: 4.13.0
|
||||||
node-loader: ^2.1.0
|
node-loader: ^2.1.0
|
||||||
octokit: ^5.0.5
|
octokit: ^5.0.5
|
||||||
oxfmt: ^0.51.0
|
oxfmt: ^0.52.0
|
||||||
oxlint: ^1.66.0
|
oxlint: ^1.67.0
|
||||||
pg: ^8.21.0
|
pg: ^8.21.0
|
||||||
postcss-preset-mantine: ^1.18.0
|
postcss-preset-mantine: ^1.18.0
|
||||||
prismjs: ^1.30.0
|
prismjs: ^1.30.0
|
||||||
proxmox-api: 1.1.1
|
proxmox-api: 1.1.1
|
||||||
react: 19.2.6
|
react: 19.2.6
|
||||||
react-dom: 19.2.6
|
react-dom: 19.2.6
|
||||||
react-error-boundary: ^6.1.1
|
react-error-boundary: ^6.1.2
|
||||||
react-markdown: ^10.1.0
|
react-markdown: ^10.1.0
|
||||||
react-quill-new: ^3.8.3
|
react-quill-new: ^3.8.3
|
||||||
react-simple-code-editor: ^0.14.1
|
react-simple-code-editor: ^0.14.1
|
||||||
@@ -158,12 +158,12 @@ catalog:
|
|||||||
superjson: 2.2.6
|
superjson: 2.2.6
|
||||||
svgson: ^5.3.1
|
svgson: ^5.3.1
|
||||||
swagger-ui-react: ^5.32.6
|
swagger-ui-react: ^5.32.6
|
||||||
testcontainers: ^12.0.0
|
testcontainers: ^12.0.1
|
||||||
timezones-ical-library: ^2.2.0
|
timezones-ical-library: ^2.2.0
|
||||||
trpc-to-openapi: ^3.3.0
|
trpc-to-openapi: ^3.3.0
|
||||||
tsdav: ^2.2.2
|
tsdav: ^2.2.2
|
||||||
tsx: 4.20.4
|
tsx: 4.20.4
|
||||||
turbo: ^2.9.14
|
turbo: ^2.9.16
|
||||||
typescript: ^5.9.3
|
typescript: ^5.9.3
|
||||||
undici: 7.24.6
|
undici: 7.24.6
|
||||||
use-deep-compare-effect: ^1.8.1
|
use-deep-compare-effect: ^1.8.1
|
||||||
|
|||||||
Reference in new issue
Block a user