mirror of
https://github.com/ajnart/homarr.git
synced 2026-03-01 01:40:55 +01:00
chore(release): automatic release v0.1.0
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM node:20.16.0-alpine AS base
|
||||
FROM node:20.17.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
"@mantine/tiptap": "^7.12.1",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@t3-oss/env-nextjs": "^0.11.0",
|
||||
"@tanstack/react-query": "^5.51.23",
|
||||
"@tanstack/react-query-devtools": "^5.51.23",
|
||||
"@tanstack/react-query-next-experimental": "5.51.23",
|
||||
"@tanstack/react-query": "^5.52.1",
|
||||
"@tanstack/react-query-devtools": "^5.52.1",
|
||||
"@tanstack/react-query-next-experimental": "5.52.1",
|
||||
"@tabler/icons-react": "^3.12.0",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/next": "next",
|
||||
@@ -50,15 +50,15 @@
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"chroma-js": "^2.6.0",
|
||||
"chroma-js": "^3.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.5",
|
||||
"flag-icons": "^7.2.3",
|
||||
"glob": "^11.0.0",
|
||||
"jotai": "^2.9.3",
|
||||
"mantine-react-table": "2.0.0-beta.6",
|
||||
"next": "^14.2.5",
|
||||
"next": "^14.2.6",
|
||||
"postcss-preset-mantine": "^1.17.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"react": "^18.3.1",
|
||||
@@ -74,9 +74,9 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "2.4.4",
|
||||
"@types/node": "^20.15.0",
|
||||
"@types/node": "^20.16.1",
|
||||
"@types/prismjs": "^1.26.4",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^9.9.0",
|
||||
|
||||
@@ -7,6 +7,7 @@ import { clientApi } from "@homarr/api/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { CustomPasswordInput } from "@homarr/ui";
|
||||
import type { z } from "@homarr/validation";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
@@ -64,7 +65,8 @@ export const RegistrationForm = ({ invite }: RegistrationFormProps) => {
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack gap="lg">
|
||||
<TextInput label={t("field.username.label")} autoComplete="off" {...form.getInputProps("username")} />
|
||||
<PasswordInput
|
||||
<CustomPasswordInput
|
||||
withPasswordRequirements
|
||||
label={t("field.password.label")}
|
||||
autoComplete="new-password"
|
||||
{...form.getInputProps("password")}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { Group, Menu } from "@mantine/core";
|
||||
import { useHotkeys } from "@mantine/hooks";
|
||||
import {
|
||||
IconBox,
|
||||
IconBoxAlignTop,
|
||||
IconChevronDown,
|
||||
IconPackageImport,
|
||||
IconPencil,
|
||||
IconPencilOff,
|
||||
IconPlus,
|
||||
@@ -95,7 +95,6 @@ const AddMenu = () => {
|
||||
<Menu.Item leftSection={<IconBox size={20} />} onClick={handleSelectItem}>
|
||||
{t("item.action.create")}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<IconPackageImport size={20} />}>{t("item.action.import")}</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
@@ -139,6 +138,8 @@ const EditModeMenu = () => {
|
||||
setEditMode(true);
|
||||
}, [board, isEditMode, saveBoard, setEditMode]);
|
||||
|
||||
useHotkeys([["mod+e", toggle]]);
|
||||
|
||||
return (
|
||||
<HeaderButton onClick={toggle} loading={isPending}>
|
||||
{isEditMode ? <IconPencilOff stroke={1.5} /> : <IconPencil stroke={1.5} />}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { clientApi } from "@homarr/api/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { CustomPasswordInput } from "@homarr/ui";
|
||||
import type { z } from "@homarr/validation";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
@@ -50,7 +51,11 @@ export const InitUserForm = () => {
|
||||
>
|
||||
<Stack gap="lg">
|
||||
<TextInput label={t("field.username.label")} {...form.getInputProps("username")} />
|
||||
<PasswordInput label={t("field.password.label")} {...form.getInputProps("password")} />
|
||||
<CustomPasswordInput
|
||||
withPasswordRequirements
|
||||
label={t("field.password.label")}
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
<PasswordInput label={t("field.passwordConfirm.label")} {...form.getInputProps("confirmPassword")} />
|
||||
<Button type="submit" fullWidth loading={isPending}>
|
||||
{t("action.create")}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useSession } from "@homarr/auth/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { CustomPasswordInput } from "@homarr/ui";
|
||||
import { validation } from "@homarr/validation";
|
||||
|
||||
import { revalidatePathActionAsync } from "~/app/revalidatePathAction";
|
||||
@@ -71,7 +72,12 @@ export const ChangePasswordForm = ({ user }: ChangePasswordFormProps) => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<PasswordInput withAsterisk label={t("user.field.password.label")} {...form.getInputProps("password")} />
|
||||
<CustomPasswordInput
|
||||
withPasswordRequirements
|
||||
withAsterisk
|
||||
label={t("user.field.password.label")}
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
withAsterisk
|
||||
|
||||
@@ -8,7 +8,7 @@ import { clientApi } from "@homarr/api/client";
|
||||
import { useZodForm } from "@homarr/form";
|
||||
import { showErrorNotification } from "@homarr/notifications";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { UserAvatar } from "@homarr/ui";
|
||||
import { CustomPasswordInput, UserAvatar } from "@homarr/ui";
|
||||
import { validation, z } from "@homarr/validation";
|
||||
import { createCustomErrorParams } from "@homarr/validation/form";
|
||||
|
||||
@@ -124,7 +124,8 @@ export const UserCreateStepperComponent = () => {
|
||||
<form>
|
||||
<Card p="xl">
|
||||
<Stack gap="md">
|
||||
<PasswordInput
|
||||
<CustomPasswordInput
|
||||
withPasswordRequirements
|
||||
label={tUserField("password.label")}
|
||||
variant="filled"
|
||||
withAsterisk
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useCallback, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core";
|
||||
import { useTimeout } from "@mantine/hooks";
|
||||
import { useHotkeys, useTimeout } from "@mantine/hooks";
|
||||
import {
|
||||
IconCheck,
|
||||
IconHome,
|
||||
@@ -33,6 +33,7 @@ interface UserAvatarMenuProps {
|
||||
export const UserAvatarMenu = ({ children }: UserAvatarMenuProps) => {
|
||||
const t = useScopedI18n("common.userAvatar.menu");
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
useHotkeys([["mod+J", toggleColorScheme]]);
|
||||
|
||||
const ColorSchemeIcon = colorScheme === "dark" ? IconSun : IconMoon;
|
||||
|
||||
|
||||
@@ -32,18 +32,18 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/cron-jobs-core": "workspace:^0.1.0",
|
||||
"@homarr/widgets": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "^1.11.13",
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/cron-job-runner": "workspace:^0.1.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"superjson": "2.2.1",
|
||||
"undici": "6.19.7"
|
||||
"undici": "6.19.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^20.15.0",
|
||||
"@types/node": "^20.16.1",
|
||||
"dotenv-cli": "^7.4.2",
|
||||
"eslint": "^9.9.0",
|
||||
"prettier": "^3.3.3",
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"name": "homarr",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20.16.0"
|
||||
"node": ">=20.17.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.7.1",
|
||||
"packageManager": "pnpm@9.8.0",
|
||||
"scripts": {
|
||||
"build": "turbo build",
|
||||
"clean": "git clean -xdf node_modules",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@umami/node": "^0.3.0",
|
||||
"@umami/node": "^0.4.0",
|
||||
"superjson": "2.2.1",
|
||||
"@homarr/db": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@trpc/server": "next",
|
||||
"dockerode": "^4.0.2",
|
||||
"superjson": "2.2.1",
|
||||
"next": "^14.2.5",
|
||||
"next": "^14.2.6",
|
||||
"react": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -39,8 +39,8 @@ describe("initUser should initialize the first user", () => {
|
||||
const actAsync = async () =>
|
||||
await caller.initUser({
|
||||
username: "test",
|
||||
password: "12345678",
|
||||
confirmPassword: "12345678",
|
||||
password: "123ABCdef+/-",
|
||||
confirmPassword: "123ABCdef+/-",
|
||||
});
|
||||
|
||||
await expect(actAsync()).rejects.toThrow("User already exists");
|
||||
@@ -55,8 +55,8 @@ describe("initUser should initialize the first user", () => {
|
||||
|
||||
await caller.initUser({
|
||||
username: "test",
|
||||
password: "12345678",
|
||||
confirmPassword: "12345678",
|
||||
password: "123ABCdef+/-",
|
||||
confirmPassword: "123ABCdef+/-",
|
||||
});
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
@@ -78,14 +78,20 @@ describe("initUser should initialize the first user", () => {
|
||||
const actAsync = async () =>
|
||||
await caller.initUser({
|
||||
username: "test",
|
||||
password: "12345678",
|
||||
confirmPassword: "12345679",
|
||||
password: "123ABCdef+/-",
|
||||
confirmPassword: "456ABCdef+/-",
|
||||
});
|
||||
|
||||
await expect(actAsync()).rejects.toThrow("passwordsDoNotMatch");
|
||||
});
|
||||
|
||||
it("should not create a user if the password is too short", async () => {
|
||||
it.each([
|
||||
["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 caller = userRouter.createCaller({
|
||||
db,
|
||||
@@ -95,11 +101,11 @@ describe("initUser should initialize the first user", () => {
|
||||
const actAsync = async () =>
|
||||
await caller.initUser({
|
||||
username: "test",
|
||||
password: "1234567",
|
||||
confirmPassword: "1234567",
|
||||
password,
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
await expect(actAsync()).rejects.toThrow("too_small");
|
||||
await expect(actAsync()).rejects.toThrow("passwordRequirements");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,8 +139,8 @@ describe("register should create a user with valid invitation", () => {
|
||||
inviteId,
|
||||
token: inviteToken,
|
||||
username: "test",
|
||||
password: "12345678",
|
||||
confirmPassword: "12345678",
|
||||
password: "123ABCdef+/-",
|
||||
confirmPassword: "123ABCdef+/-",
|
||||
});
|
||||
|
||||
// Assert
|
||||
@@ -189,8 +195,8 @@ describe("register should create a user with valid invitation", () => {
|
||||
inviteId,
|
||||
token: inviteToken,
|
||||
username: "test",
|
||||
password: "12345678",
|
||||
confirmPassword: "12345678",
|
||||
password: "123ABCdef+/-",
|
||||
confirmPassword: "123ABCdef+/-",
|
||||
...partialInput,
|
||||
});
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"bcrypt": "^5.1.1",
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "7.1.0",
|
||||
"next": "^14.2.5",
|
||||
"next": "^14.2.6",
|
||||
"next-auth": "5.0.0-beta.20",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.12",
|
||||
"next": "^14.2.5",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "^14.2.6",
|
||||
"react": "^18.3.1",
|
||||
"tldts": "^6.1.39"
|
||||
"tldts": "^6.1.41"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@auth/core": "^0.34.2",
|
||||
"better-sqlite3": "^11.1.2",
|
||||
"better-sqlite3": "^11.2.1",
|
||||
"drizzle-orm": "^0.33.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"mysql2": "3.11.0",
|
||||
"drizzle-kit": "^0.24.0"
|
||||
"drizzle-kit": "^0.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@mantine/spotlight": "^7.12.1",
|
||||
"@tabler/icons-react": "^3.12.0",
|
||||
"jotai": "^2.9.3",
|
||||
"next": "^14.2.5",
|
||||
"next": "^14.2.6",
|
||||
"react": "^18.3.1",
|
||||
"use-deep-compare-effect": "^1.8.1"
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "^1.11.13",
|
||||
"mantine-react-table": "2.0.0-beta.6",
|
||||
"next-international": "^1.2.4"
|
||||
},
|
||||
|
||||
@@ -30,6 +30,13 @@ export default {
|
||||
},
|
||||
password: {
|
||||
label: "Password",
|
||||
requirement: {
|
||||
length: "Includes at least 8 characters",
|
||||
lowercase: "Includes lowercase letter",
|
||||
uppercase: "Includes uppercase letter",
|
||||
number: "Includes number",
|
||||
special: "Includes special symbol",
|
||||
},
|
||||
},
|
||||
passwordConfirm: {
|
||||
label: "Confirm password",
|
||||
@@ -631,6 +638,7 @@ export default {
|
||||
},
|
||||
custom: {
|
||||
passwordsDoNotMatch: "Passwords do not match",
|
||||
passwordRequirements: "Password does not meet the requirements",
|
||||
boardAlreadyExists: "A board with this name already exists",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@mantine/core": "^7.12.1",
|
||||
@@ -31,7 +32,7 @@
|
||||
"@mantine/hooks": "^7.12.1",
|
||||
"@tabler/icons-react": "^3.12.0",
|
||||
"mantine-react-table": "2.0.0-beta.6",
|
||||
"next": "^14.2.5",
|
||||
"next": "^14.2.6",
|
||||
"react": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -7,3 +7,4 @@ export { TablePagination } from "./table-pagination";
|
||||
export { TextMultiSelect } from "./text-multi-select";
|
||||
export { UserAvatar } from "./user-avatar";
|
||||
export { UserAvatarGroup } from "./user-avatar-group";
|
||||
export { CustomPasswordInput } from "./password-input/password-input";
|
||||
|
||||
35
packages/ui/src/components/password-input/password-input.tsx
Normal file
35
packages/ui/src/components/password-input/password-input.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import type { ChangeEvent } from "react";
|
||||
import { useState } from "react";
|
||||
import { PasswordInput } from "@mantine/core";
|
||||
import type { PasswordInputProps } from "@mantine/core";
|
||||
|
||||
import { PasswordRequirementsPopover } from "./password-requirements-popover";
|
||||
|
||||
interface CustomPasswordInputProps extends PasswordInputProps {
|
||||
withPasswordRequirements?: boolean;
|
||||
}
|
||||
|
||||
export const CustomPasswordInput = ({ withPasswordRequirements, ...props }: CustomPasswordInputProps) => {
|
||||
if (withPasswordRequirements) {
|
||||
return <WithPasswordRequirements {...props} />;
|
||||
}
|
||||
|
||||
return <PasswordInput {...props} />;
|
||||
};
|
||||
|
||||
const WithPasswordRequirements = (props: PasswordInputProps) => {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(event.currentTarget.value);
|
||||
props.onChange?.(event);
|
||||
};
|
||||
|
||||
return (
|
||||
<PasswordRequirementsPopover password={value}>
|
||||
<PasswordInput {...props} onChange={onChange} />
|
||||
</PasswordRequirementsPopover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { rem, Text } from "@mantine/core";
|
||||
import { IconCheck, IconX } from "@tabler/icons-react";
|
||||
|
||||
export function PasswordRequirement({ meets, label }: { meets: boolean; label: string }) {
|
||||
return (
|
||||
<Text c={meets ? "teal" : "red"} display="flex" style={{ alignItems: "center" }} size="sm">
|
||||
{meets ? (
|
||||
<IconCheck style={{ width: rem(14), height: rem(14) }} />
|
||||
) : (
|
||||
<IconX style={{ width: rem(14), height: rem(14) }} />
|
||||
)}
|
||||
<Text span ml={10}>
|
||||
{label}
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { PropsWithChildren } from "react";
|
||||
import { useState } from "react";
|
||||
import { Popover, Progress } from "@mantine/core";
|
||||
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { passwordRequirements } from "@homarr/validation";
|
||||
|
||||
import { PasswordRequirement } from "./password-requirement";
|
||||
|
||||
export const PasswordRequirementsPopover = ({ password, children }: PropsWithChildren<{ password: string }>) => {
|
||||
const requirements = useRequirements();
|
||||
const strength = useStrength(password);
|
||||
const [popoverOpened, setPopoverOpened] = useState(false);
|
||||
const checks = (
|
||||
<>
|
||||
{requirements.map((requirement) => (
|
||||
<PasswordRequirement key={requirement.label} label={requirement.label} meets={requirement.check(password)} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
const color = strength === 100 ? "teal" : strength > 50 ? "yellow" : "red";
|
||||
|
||||
return (
|
||||
<Popover opened={popoverOpened} position="bottom" width="target" transitionProps={{ transition: "pop" }}>
|
||||
<Popover.Target>
|
||||
<div onFocusCapture={() => setPopoverOpened(true)} onBlurCapture={() => setPopoverOpened(false)}>
|
||||
{children}
|
||||
</div>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Progress color={color} value={strength} size={5} mb="xs" />
|
||||
{checks}
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
const useRequirements = () => {
|
||||
const t = useScopedI18n("user.field.password.requirement");
|
||||
|
||||
return passwordRequirements.map(({ check, value }) => ({ check, label: t(value) }));
|
||||
};
|
||||
|
||||
function useStrength(password: string) {
|
||||
const requirements = useRequirements();
|
||||
|
||||
return (100 / requirements.length) * requirements.filter(({ check }) => check(password)).length;
|
||||
}
|
||||
@@ -25,3 +25,4 @@ export {
|
||||
type BoardItemIntegration,
|
||||
type BoardItemAdvancedOptions,
|
||||
} from "./shared";
|
||||
export { passwordRequirements } from "./user";
|
||||
|
||||
@@ -1,9 +1,35 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
|
||||
import { createCustomErrorParams } from "./form/i18n";
|
||||
|
||||
const usernameSchema = z.string().min(3).max(255);
|
||||
const passwordSchema = z.string().min(8).max(255);
|
||||
|
||||
const regexCheck = (regex: RegExp) => (value: string) => regex.test(value);
|
||||
export const passwordRequirements = [
|
||||
{ check: (value) => value.length >= 8, value: "length" },
|
||||
{ check: regexCheck(/[a-z]/), value: "lowercase" },
|
||||
{ check: regexCheck(/[A-Z]/), value: "uppercase" },
|
||||
{ check: regexCheck(/\d/), value: "number" },
|
||||
{ check: regexCheck(/[$&+,:;=?@#|'<>.^*()%!-]/), value: "special" },
|
||||
] satisfies {
|
||||
check: (value: string) => boolean;
|
||||
value: keyof TranslationObject["user"]["field"]["password"]["requirement"];
|
||||
}[];
|
||||
|
||||
const passwordSchema = z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(255)
|
||||
.refine(
|
||||
(value) => {
|
||||
return passwordRequirements.every((requirement) => requirement.check(value));
|
||||
},
|
||||
{
|
||||
params: createCustomErrorParams("passwordRequirements"),
|
||||
},
|
||||
);
|
||||
|
||||
const confirmPasswordRefine = [
|
||||
(data: { password: string; confirmPassword: string }) => data.password === data.confirmPassword,
|
||||
|
||||
@@ -38,24 +38,24 @@
|
||||
"@mantine/hooks": "^7.12.1",
|
||||
"@mantine/core": "^7.12.1",
|
||||
"@tabler/icons-react": "^3.12.0",
|
||||
"@tiptap/extension-color": "2.6.4",
|
||||
"@tiptap/extension-highlight": "2.6.4",
|
||||
"@tiptap/extension-image": "2.6.4",
|
||||
"@tiptap/extension-link": "^2.6.4",
|
||||
"@tiptap/extension-table": "2.6.4",
|
||||
"@tiptap/extension-table-cell": "2.6.4",
|
||||
"@tiptap/extension-table-header": "2.6.4",
|
||||
"@tiptap/extension-table-row": "2.6.4",
|
||||
"@tiptap/extension-task-item": "2.6.4",
|
||||
"@tiptap/extension-task-list": "2.6.4",
|
||||
"@tiptap/extension-text-align": "2.6.4",
|
||||
"@tiptap/extension-text-style": "2.6.4",
|
||||
"@tiptap/extension-underline": "2.6.4",
|
||||
"@tiptap/react": "^2.6.4",
|
||||
"@tiptap/starter-kit": "^2.6.4",
|
||||
"@tiptap/extension-color": "2.6.6",
|
||||
"@tiptap/extension-highlight": "2.6.6",
|
||||
"@tiptap/extension-image": "2.6.6",
|
||||
"@tiptap/extension-link": "^2.6.6",
|
||||
"@tiptap/extension-table": "2.6.6",
|
||||
"@tiptap/extension-table-cell": "2.6.6",
|
||||
"@tiptap/extension-table-header": "2.6.6",
|
||||
"@tiptap/extension-table-row": "2.6.6",
|
||||
"@tiptap/extension-task-item": "2.6.6",
|
||||
"@tiptap/extension-task-list": "2.6.6",
|
||||
"@tiptap/extension-text-align": "2.6.6",
|
||||
"@tiptap/extension-text-style": "2.6.6",
|
||||
"@tiptap/extension-underline": "2.6.6",
|
||||
"@tiptap/react": "^2.6.6",
|
||||
"@tiptap/starter-kit": "^2.6.6",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.12",
|
||||
"next": "^14.2.5",
|
||||
"dayjs": "^1.11.13",
|
||||
"next": "^14.2.6",
|
||||
"mantine-react-table": "2.0.0-beta.6",
|
||||
"react": "^18.3.1",
|
||||
"video.js": "^8.17.3"
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
useMantineColorScheme,
|
||||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { getHotkeyHandler, useDisclosure } from "@mantine/hooks";
|
||||
import { Link, RichTextEditor, useRichTextEditorContext } from "@mantine/tiptap";
|
||||
import {
|
||||
IconCheck,
|
||||
@@ -231,6 +231,7 @@ export function Notebook({ options, isEditMode, boardId, itemId }: WidgetCompone
|
||||
p={0}
|
||||
mt={0}
|
||||
h="100%"
|
||||
onKeyDown={isEditing ? getHotkeyHandler([["mod+s", handleEditToggle]]) : undefined}
|
||||
editor={editor}
|
||||
styles={(theme) => ({
|
||||
root: {
|
||||
|
||||
1230
pnpm-lock.yaml
generated
1230
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -16,14 +16,14 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/eslint-plugin-next": "^14.2.5",
|
||||
"@next/eslint-plugin-next": "^14.2.6",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-turbo": "^2.0.14",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.9.0",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"typescript-eslint": "^8.1.0"
|
||||
"typescript-eslint": "^8.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
|
||||
Reference in New Issue
Block a user