feat: add user management (#134)

This commit is contained in:
Manuel
2024-02-20 21:18:47 +01:00
committed by GitHub
parent fde634d834
commit b4749e7091
12 changed files with 614 additions and 48 deletions

View File

@@ -47,9 +47,9 @@
"postcss-preset-mantine": "^1.13.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"sass": "^1.71.0",
"superjson": "2.2.1",
"use-deep-compare-effect": "^1.8.1",
"sass": "^1.71.0"
"use-deep-compare-effect": "^1.8.1"
},
"devDependencies": {
"@homarr/eslint-config": "workspace:^0.2.0",

View File

@@ -0,0 +1,36 @@
import { notFound } from "next/navigation";
import { getScopedI18n } from "@homarr/translation/server";
import { Title } from "@homarr/ui";
import { api } from "~/trpc/server";
interface Props {
params: {
userId: string;
};
}
export async function generateMetadata({ params }: Props) {
const user = await api.user.getById({
userId: params.userId,
});
const t = await getScopedI18n("management.page.user.edit");
const metaTitle = `${t("metaTitle", { username: user?.name })} • Homarr`;
return {
title: metaTitle,
};
}
export default async function EditUserPage({ params }: Props) {
const user = await api.user.getById({
userId: params.userId,
});
if (!user) {
notFound();
}
return <Title>Edit User {user.name}!</Title>;
}

View File

@@ -0,0 +1,106 @@
"use client";
import { useMemo } from "react";
import Link from "next/link";
import type { MRT_ColumnDef } from "mantine-react-table";
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { useScopedI18n } from "@homarr/translation/client";
import {
ActionIcon,
Button,
Flex,
Group,
IconCheck,
IconEdit,
IconTrash,
Text,
ThemeIcon,
Title,
Tooltip,
} from "@homarr/ui";
interface UserListComponentProps {
initialUserList: RouterOutputs["user"]["getAll"];
}
export const UserListComponent = ({
initialUserList,
}: UserListComponentProps) => {
const t = useScopedI18n("management.page.user.list");
const { data, isLoading } = clientApi.user.getAll.useQuery(undefined, {
initialData: initialUserList,
});
const columns = useMemo<
MRT_ColumnDef<RouterOutputs["user"]["getAll"][number]>[]
>(
() => [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "email",
header: "Email",
Cell: ({ renderedCellValue, row }) => (
<Group>
{row.original.email ? renderedCellValue : <Text>-</Text>}
{row.original.emailVerified && (
<ThemeIcon radius="xl" size="sm">
<IconCheck size="1rem" />
</ThemeIcon>
)}
</Group>
),
},
],
[],
);
const table = useMantineReactTable({
columns,
data,
enableRowSelection: true,
enableColumnOrdering: true,
enableGlobalFilter: false,
enableRowActions: true,
enableDensityToggle: false,
enableFullScreenToggle: false,
getRowId: (row) => row.id,
renderRowActions: ({ row }) => (
<Flex gap="md">
<Tooltip label="Edit">
<ActionIcon
component={Link}
href={`/manage/users/${row.original.id}`}
>
<IconEdit size="1rem" />
</ActionIcon>
</Tooltip>
<Tooltip label="Delete">
<ActionIcon color="red">
<IconTrash size="1rem" />
</ActionIcon>
</Tooltip>
</Flex>
),
renderTopToolbarCustomActions: () => (
<Button component={Link} href="/manage/users/create">
Create New User
</Button>
),
state: {
isLoading: isLoading,
},
});
return (
<>
<Title mb="md">{t("title")}</Title>
<MantineReactTable table={table} />
</>
);
};

View File

@@ -0,0 +1,74 @@
import Link from "next/link";
import { useI18n } from "@homarr/translation/client";
import {
Button,
Card,
Group,
IconArrowBackUp,
IconArrowLeft,
IconArrowRight,
IconRotate,
} from "@homarr/ui";
interface StepperNavigationComponentProps {
hasPrevious: boolean;
hasNext: boolean;
isComplete: boolean;
isLoadingNextStep: boolean;
prevStep: () => void;
nextStep: () => void;
reset: () => void;
}
export const StepperNavigationComponent = ({
hasNext,
hasPrevious,
isComplete,
isLoadingNextStep,
nextStep,
prevStep,
reset,
}: StepperNavigationComponentProps) => {
const t = useI18n();
return (
<Card shadow="md" withBorder>
{!isComplete ? (
<Group justify="space-between" wrap="nowrap">
<Button
leftSection={<IconArrowLeft size="1rem" />}
disabled={!hasPrevious || isLoadingNextStep}
onClick={prevStep}
>
{t("common.action.previous")}
</Button>
<Button
rightSection={<IconArrowRight size="1rem" />}
disabled={!hasNext || isLoadingNextStep}
loading={isLoadingNextStep}
onClick={nextStep}
>
{t("common.action.next")}
</Button>
</Group>
) : (
<Group justify="end" wrap="nowrap">
<Button
variant="light"
leftSection={<IconRotate size="1rem" />}
onClick={reset}
>
{t("management.page.user.create.buttons.createAnother")}
</Button>
<Button
leftSection={<IconArrowBackUp size="1rem" />}
component={Link}
href="/manage/users"
>
{t("management.page.user.create.buttons.return")}
</Button>
</Group>
)}
</Card>
);
};

View File

@@ -0,0 +1,156 @@
"use client";
import { useState } from "react";
import { clientApi } from "@homarr/api/client";
import { useForm, zodResolver } from "@homarr/form";
import { useScopedI18n } from "@homarr/translation/client";
import {
Avatar,
Card,
IconUserCheck,
Stack,
Stepper,
Text,
TextInput,
Title,
} from "@homarr/ui";
import { z } from "@homarr/validation";
import { StepperNavigationComponent } from "./stepper-navigation.component";
export const UserCreateStepperComponent = () => {
const t = useScopedI18n("management.page.user.create");
const stepperMax = 4;
const [active, setActive] = useState(0);
const nextStep = () =>
setActive((current) => (current < stepperMax ? current + 1 : current));
const prevStep = () =>
setActive((current) => (current > 0 ? current - 1 : current));
const hasNext = active < stepperMax;
const hasPrevious = active > 0;
const { mutateAsync, isPending } = clientApi.user.create.useMutation();
const generalForm = useForm({
initialValues: {
username: "",
email: undefined,
},
validate: zodResolver(
z.object({
username: z.string().min(1),
email: z.string().email().or(z.string().length(0).optional()),
}),
),
validateInputOnBlur: true,
validateInputOnChange: true,
});
const allForms = [generalForm];
const canNavigateToNextStep = allForms[active]?.isValid() ?? true;
const controlledGoToNextStep = async () => {
if (active + 1 === stepperMax) {
await mutateAsync({
name: generalForm.values.username,
email: generalForm.values.email,
});
}
nextStep();
};
const reset = () => {
setActive(0);
allForms.forEach((form) => {
form.reset();
});
};
return (
<>
<Title mb="md">{t("title")}</Title>
<Stepper
active={active}
onStepClick={setActive}
allowNextStepsSelect={false}
mb="md"
>
<Stepper.Step
label={t("step.personalInformation.label")}
allowStepSelect={false}
allowStepClick={false}
color={!generalForm.isValid() ? "red" : undefined}
>
<form>
<Card p="xl">
<Stack gap="md">
<TextInput
label={t("step.personalInformation.field.username.label")}
variant="filled"
withAsterisk
{...generalForm.getInputProps("username")}
/>
<TextInput
label={t("step.personalInformation.field.email.label")}
variant="filled"
{...generalForm.getInputProps("email")}
/>
</Stack>
</Card>
</form>
</Stepper.Step>
<Stepper.Step
label={t("step.preferences.label")}
description={t("step.preferences.description")}
allowStepSelect={false}
allowStepClick={false}
>
Step 2
</Stepper.Step>
<Stepper.Step
label={t("step.permissions.label")}
description={t("step.permissions.description")}
allowStepSelect={false}
allowStepClick={false}
>
3
</Stepper.Step>
<Stepper.Step
label={t("step.review.label")}
allowStepSelect={false}
allowStepClick={false}
>
<Card p="xl">
<Stack maw={300} align="center" mx="auto">
<Avatar size="xl">{generalForm.values.username}</Avatar>
<Text tt="uppercase" fw="bolder" size="xl">
{generalForm.values.username}
</Text>
</Stack>
</Card>
</Stepper.Step>
<Stepper.Completed>
<Card p="xl">
<Stack align="center" maw={300} mx="auto">
<IconUserCheck size="3rem" />
<Title order={2}>{t("step.completed.title")}</Title>
</Stack>
</Card>
</Stepper.Completed>
</Stepper>
<StepperNavigationComponent
hasNext={hasNext && canNavigateToNextStep}
hasPrevious={hasPrevious}
isComplete={active === stepperMax}
isLoadingNextStep={isPending}
nextStep={controlledGoToNextStep}
prevStep={prevStep}
reset={reset}
/>
</>
);
};

View File

@@ -0,0 +1,16 @@
import { getScopedI18n } from "@homarr/translation/server";
import { UserCreateStepperComponent } from "./_components/stepper.component";
export async function generateMetadata() {
const t = await getScopedI18n("management.page.user.create");
const metaTitle = `${t("metaTitle")} • Homarr`;
return {
title: metaTitle,
};
}
export default function CreateUserPage() {
return <UserCreateStepperComponent />;
}

View File

@@ -0,0 +1,18 @@
import { getScopedI18n } from "@homarr/translation/server";
import { api } from "~/trpc/server";
import { UserListComponent } from "./_components/user-list.component";
export async function generateMetadata() {
const t = await getScopedI18n("management.page.user.list");
const metaTitle = `${t("metaTitle")} • Homarr`;
return {
title: metaTitle,
};
}
export default async function UsersPage() {
const userList = await api.user.getAll();
return <UserListComponent initialUserList={userList} />;
}

View File

@@ -3,8 +3,9 @@ import "server-only";
import { TRPCError } from "@trpc/server";
import { createSalt, hashPassword } from "@homarr/auth";
import { createId, schema } from "@homarr/db";
import { validation } from "@homarr/validation";
import { createId, db, eq, schema } from "@homarr/db";
import { users } from "@homarr/db/schema/sqlite";
import { validation, z } from "@homarr/validation";
import { createTRPCRouter, publicProcedure } from "../trpc";
@@ -36,4 +37,43 @@ export const userRouter = createTRPCRouter({
salt,
});
}),
getAll: publicProcedure.query(async () => {
return db.query.users.findMany({
columns: {
id: true,
name: true,
email: true,
emailVerified: true,
image: true,
},
});
}),
getById: publicProcedure
.input(z.object({ userId: z.string() }))
.query(async ({ input }) => {
return db.query.users.findFirst({
columns: {
id: true,
name: true,
email: true,
emailVerified: true,
image: true,
},
where: eq(users.id, input.userId),
});
}),
create: publicProcedure
.input(
z.object({
name: z.string(),
email: z.string().email().or(z.string().length(0).optional()),
}),
)
.mutation(async ({ input }) => {
await db.insert(users).values({
id: createId(),
name: input.name,
email: input.email,
});
}),
});

View File

@@ -147,6 +147,8 @@ export default {
saveChanges: "Save changes",
cancel: "Cancel",
confirm: "Confirm",
previous: "Previous",
next: "Next",
},
multiSelect: {
placeholder: "Pick one or more values",
@@ -394,6 +396,50 @@ export default {
},
},
},
user: {
list: {
metaTitle: "Manage users",
title: "Users",
},
edit: {
metaTitle: "Edit user {username}",
},
create: {
metaTitle: "Create user",
title: "Create new user",
step: {
personalInformation: {
label: "Personal information",
field: {
username: {
label: "Username",
},
email: {
label: "E-Mail",
},
},
},
preferences: {
label: "Preferences",
description: "Coming soon",
},
permissions: {
label: "Permissions",
description: "Coming soon",
},
review: {
label: "Review",
},
completed: {
title: "User created",
},
},
buttons: {
createAnother: "Create another user",
return: "Return to the user list",
},
},
},
},
},
} as const;

View File

@@ -37,6 +37,7 @@
"dependencies": {
"@mantine/core": "^7.5.3",
"@mantine/dates": "^7.5.3",
"@tabler/icons-react": "^2.47.0"
"@tabler/icons-react": "^2.47.0",
"mantine-react-table": "2.0.0-alpha.17"
}
}

View File

@@ -1,2 +1,3 @@
@import "@mantine/core/styles.css";
@import "@mantine/dates/styles.css";
@import "mantine-react-table/styles.css";

158
pnpm-lock.yaml generated
View File

@@ -113,7 +113,7 @@ importers:
version: 5.21.7(@tanstack/react-query@5.21.7)(react@18.2.0)
'@tanstack/react-query-next-experimental':
specifier: 5.21.7
version: 5.21.7(@tanstack/react-query@5.21.7)(next@14.1.1-canary.62)(react@18.2.0)
version: 5.21.7(@tanstack/react-query@5.21.7)(next@14.1.1-canary.63)(react@18.2.0)
'@tiptap/extension-link':
specifier: ^2.2.3
version: 2.2.3(@tiptap/core@2.2.3)(@tiptap/pm@2.2.3)
@@ -128,7 +128,7 @@ importers:
version: 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
'@trpc/next':
specifier: next
version: 11.0.0-next-beta.289(@tanstack/react-query@5.21.7)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.1-canary.62)(react-dom@18.2.0)(react@18.2.0)
version: 11.0.0-next-beta.289(@tanstack/react-query@5.21.7)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.1-canary.63)(react-dom@18.2.0)(react@18.2.0)
'@trpc/react-query':
specifier: next
version: 11.0.0-next-beta.289(@tanstack/react-query@5.21.7)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
@@ -146,7 +146,7 @@ importers:
version: 7.5.3(@mantine/core@7.5.3)(@mantine/hooks@7.5.3)(react-dom@18.2.0)(react@18.2.0)
next:
specifier: ^14.1.1-canary.62
version: 14.1.1-canary.62(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
version: 14.1.1-canary.63(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
postcss-preset-mantine:
specifier: ^1.13.0
version: 1.13.0(postcss@8.4.35)
@@ -262,10 +262,10 @@ importers:
version: 0.9.1
next:
specifier: ^14.1.1-canary.62
version: 14.1.1-canary.62(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
version: 14.1.1-canary.63(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
next-auth:
specifier: 5.0.0-beta.13
version: 5.0.0-beta.13(next@14.1.1-canary.62)(react@18.2.0)
version: 5.0.0-beta.13(next@14.1.1-canary.63)(react@18.2.0)
react:
specifier: 18.2.0
version: 18.2.0
@@ -495,6 +495,9 @@ importers:
'@tabler/icons-react':
specifier: ^2.47.0
version: 2.47.0(react@18.2.0)
mantine-react-table:
specifier: 2.0.0-alpha.17
version: 2.0.0-alpha.17(@mantine/core@7.5.3)(@mantine/dates@7.5.3)(@mantine/hooks@7.5.3)(@tabler/icons-react@2.47.0)(clsx@2.0.0)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0)
devDependencies:
'@homarr/eslint-config':
specifier: workspace:^0.2.0
@@ -1670,8 +1673,8 @@ packages:
- supports-color
dev: false
/@next/env@14.1.1-canary.62:
resolution: {integrity: sha512-lW88VAxTGm6VeXWxHGCHnTQ3ndF9onShjpMibJVqRNsG49AAzzrk4LKb0+ILLWimIuRo8kYtX9/WNJMvzoMxgg==}
/@next/env@14.1.1-canary.63:
resolution: {integrity: sha512-yMhY/WajNMysbcdPkSNtGsQW6CBBqi/63C7nC404XVPTAVhsRQH6L23ls450aCxaUyoO9b5kvDUjmNKKiotdeQ==}
dev: false
/@next/eslint-plugin-next@14.1.0:
@@ -1680,8 +1683,8 @@ packages:
glob: 10.3.10
dev: false
/@next/swc-darwin-arm64@14.1.1-canary.62:
resolution: {integrity: sha512-o8yJK9TmweAP9tGm6sLyWD0sSB+n+3thRiHD0Chm+tsJN9vjHhACj11O6UIgPLI7vu+bbBPt3xrU+4wxIYexUQ==}
/@next/swc-darwin-arm64@14.1.1-canary.63:
resolution: {integrity: sha512-xoOv/29gn5SH3soiDOJWOkJJ98Z7rkKKNoPbzPfKvPgfcwAyeAl55SjraNfdlFiLE67Mp+AKauJiKtL6xJclOg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
@@ -1689,8 +1692,8 @@ packages:
dev: false
optional: true
/@next/swc-darwin-x64@14.1.1-canary.62:
resolution: {integrity: sha512-R7/opKdbsm1du+LnFNgJPYLvguMMlOF3R0d7xAurAg7MBUwWUh2f50tHcTDrVzNbdZEhk1jhlcmS/lrCxv3uYA==}
/@next/swc-darwin-x64@14.1.1-canary.63:
resolution: {integrity: sha512-Rv9WDLjk+L1X1nvBgkO2HK39ujoe6tohcX+KW2rS+XXPRMcmmxPdIa4D20PYSsYa7AX9riJhRibBgpHuNXR4rQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
@@ -1698,8 +1701,8 @@ packages:
dev: false
optional: true
/@next/swc-linux-arm64-gnu@14.1.1-canary.62:
resolution: {integrity: sha512-hKysZWKQ5uuUcxhJafPJFfFz/6nrH2t6fAZfxV4skB+FxikMLUNMETeu6A2gRtXwHhqttlinD2BNlxHlEx5UoQ==}
/@next/swc-linux-arm64-gnu@14.1.1-canary.63:
resolution: {integrity: sha512-6Vla/RHWgxmU+KQXcPevnHfE4f98mazNSLGg5e2U2JLbBXxbzTz6u5WpOw1oAsEe6uH6CxrpnxIpTlnEK06vKw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@@ -1707,8 +1710,8 @@ packages:
dev: false
optional: true
/@next/swc-linux-arm64-musl@14.1.1-canary.62:
resolution: {integrity: sha512-2nVWU4ihwVypYltvukvRXuoH2Y8l38etaOdwmN1LcllKJ5Hu1DmXDFq2bmFML6q6HBs9LsX0YEHxfeVLd6pLXg==}
/@next/swc-linux-arm64-musl@14.1.1-canary.63:
resolution: {integrity: sha512-F0d9UDjvOs4TEvQ4/puM/fNNvq9B3LKgTXPqrLx932EfX2Di+C50D7O6qcb20HhAn60nahjbvRKDzAEm4P7IJQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
@@ -1716,8 +1719,8 @@ packages:
dev: false
optional: true
/@next/swc-linux-x64-gnu@14.1.1-canary.62:
resolution: {integrity: sha512-SFqP1ZVUQqWmdX3PtNgel62sffV5wZHbtW/J4E4P/QrBaNbGQlziw34tFvlssjOGRG3ZzuDGCgqtVr0l4/QvfQ==}
/@next/swc-linux-x64-gnu@14.1.1-canary.63:
resolution: {integrity: sha512-Nwuz+gkZytJFKg6wnKPEBC0d4HsD7/QgVePSyibjtbYrq13yqk/dHNkdEr9sksGI7zr8TN8lfreb1VEgqN9zeQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@@ -1725,8 +1728,8 @@ packages:
dev: false
optional: true
/@next/swc-linux-x64-musl@14.1.1-canary.62:
resolution: {integrity: sha512-adlHzPGxVc9PuorODAWLPZtjjrJVFIEmMAVvwLJy9vz4T/c+CrTfgWxUy+uIx5Xr+zw0GU3+E4D0FqqTKS6FDw==}
/@next/swc-linux-x64-musl@14.1.1-canary.63:
resolution: {integrity: sha512-4ZaUnhzdTtU94OV6zldIRy9YinqIugpt38uKD5+GtpyBdj3OPZEdKzvbPkQTAlXuxnqNZwuM/k1D5LLS9A1ZAg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
@@ -1734,8 +1737,8 @@ packages:
dev: false
optional: true
/@next/swc-win32-arm64-msvc@14.1.1-canary.62:
resolution: {integrity: sha512-gm5GnQz9I2yWst7RZqHf5frtfmjp+SP+FRzuTbCwWo04vOZqt/nrDOFha3O8VHfH/H+R1mECV6MiCR3vW64S/g==}
/@next/swc-win32-arm64-msvc@14.1.1-canary.63:
resolution: {integrity: sha512-OQ7AwsOLuxlt7Wl+E1sYeXmljmQqOdYHA3cXc0vI1X7QC6bxhQ49yFS5VZzFN6cz+zx0uTXIA4IAZQz6VI+vdw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
@@ -1743,8 +1746,8 @@ packages:
dev: false
optional: true
/@next/swc-win32-ia32-msvc@14.1.1-canary.62:
resolution: {integrity: sha512-zB/UXYimoYSmqAq3o4EH4vOFeAFaXdrwqpt7ptow2kLRt2w3hahp6dd4om/qhopd2fraezxdMxvuUoKKDCtzXw==}
/@next/swc-win32-ia32-msvc@14.1.1-canary.63:
resolution: {integrity: sha512-ll67730wgehzZ4zoYayXg0TRRjGw+Oq+MLp7u6rodwUA93Lu9GxDibyh9dHT+mpGdwSroo7zVgvk4aXp30cmYA==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
@@ -1752,8 +1755,8 @@ packages:
dev: false
optional: true
/@next/swc-win32-x64-msvc@14.1.1-canary.62:
resolution: {integrity: sha512-wjDYA9PiricK8de+/xWQUc6q29KfA9sLUcxWfMzQQqBWKkx4AZsKJpQla8mf4ueJFKiWyy5wSjpUUqtQYObk4Q==}
/@next/swc-win32-x64-msvc@14.1.1-canary.63:
resolution: {integrity: sha512-Pvg+crGlW8QvT2r5y/Dom2M++5TxJ7pBiwHkL7kD9x21RNc8QwEEyAJjxFYGzecKBbZs3HtcRbKEkVDZsHrq4w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -1997,6 +2000,13 @@ packages:
resolution: {integrity: sha512-4w5evLh+7FUUiA1GucvGj2ReX2TvOjEr4ejXdwL/bsjoSkof6r1gQmzqI+VHrE2CpJpB3al7bCTulOkFa/RcyA==}
dev: false
/@tanstack/match-sorter-utils@8.11.8:
resolution: {integrity: sha512-3VPh0SYMGCa5dWQEqNab87UpCMk+ANWHDP4ALs5PeEW9EpfTAbrezzaOk/OiM52IESViefkoAOYuxdoa04p6aA==}
engines: {node: '>=12'}
dependencies:
remove-accents: 0.4.2
dev: false
/@tanstack/query-core@5.21.7:
resolution: {integrity: sha512-z0NSWFsM75esVmkxeuDWeyo9Wv4CZ/WsLMZgu1Zz164S6Oc/57NMia88dTu/d51wdVowMTAcDMQgRmiWmyPMxQ==}
dev: false
@@ -2016,7 +2026,7 @@ packages:
react: 18.2.0
dev: false
/@tanstack/react-query-next-experimental@5.21.7(@tanstack/react-query@5.21.7)(next@14.1.1-canary.62)(react@18.2.0):
/@tanstack/react-query-next-experimental@5.21.7(@tanstack/react-query@5.21.7)(next@14.1.1-canary.63)(react@18.2.0):
resolution: {integrity: sha512-5XgnH2JK0iH28eNmjYicX9b1lGq01XkZWQ0DFI8jCHypnXLbmD7Uq/Kh2IQ+OS6G1o3CK4RG5p8tPDNzD1vdAw==}
peerDependencies:
'@tanstack/react-query': ^5.21.7
@@ -2024,7 +2034,7 @@ packages:
react: ^18.0.0
dependencies:
'@tanstack/react-query': 5.21.7(react@18.2.0)
next: 14.1.1-canary.62(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
next: 14.1.1-canary.63(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
react: 18.2.0
dev: false
@@ -2037,6 +2047,38 @@ packages:
react: 18.2.0
dev: false
/@tanstack/react-table@8.11.8(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-NEwvIq4iSiDQozEyvbdiSdCOiLa+g5xHmdEnvwDb98FObcK6YkBOkRrs/CNqrKdDy+/lqoIllIWHk+M80GW6+g==}
engines: {node: '>=12'}
peerDependencies:
react: '>=16'
react-dom: '>=16'
dependencies:
'@tanstack/table-core': 8.11.8
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@tanstack/react-virtual@3.0.4(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-tiqKW/e2MJVCr7/pRUXulpkyxllaOclkHNfhKTo4pmHjJIqnhMfwIjc1Q1R0Un3PI3kQywywu/791c8z9u0qeA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
'@tanstack/virtual-core': 3.0.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@tanstack/table-core@8.11.8:
resolution: {integrity: sha512-DECHvtq4YW4U/gqg6etup7ydt/RB1Bi1pJaMpHUXl65ooW1d71Nv7BzD66rUdHrBSNdyiW3PLTPUQlpXjAgDeA==}
engines: {node: '>=12'}
dev: false
/@tanstack/virtual-core@3.0.0:
resolution: {integrity: sha512-SYXOBTjJb05rXa2vl55TTwO40A6wKu0R5i1qQwhJYNDIqaIGF7D0HsLw+pJAyi2OvntlEIVusx3xtbbgSUi6zg==}
dev: false
/@testing-library/react-hooks@8.0.1(react@17.0.2):
resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==}
engines: {node: '>=12'}
@@ -2330,7 +2372,7 @@ packages:
'@trpc/server': 11.0.0-next-beta.289
dev: false
/@trpc/next@11.0.0-next-beta.289(@tanstack/react-query@5.21.7)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.1-canary.62)(react-dom@18.2.0)(react@18.2.0):
/@trpc/next@11.0.0-next-beta.289(@tanstack/react-query@5.21.7)(@trpc/client@11.0.0-next-beta.289)(@trpc/react-query@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(next@14.1.1-canary.63)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-AKCrcbtHh/zFrld6lMG0RC37d/aac4ZisLDjJcViMnEmJXCo0J5nhoZa6f+G9N683NdMWZVmY2rmJidw9IX3QQ==}
peerDependencies:
'@tanstack/react-query': ^5.0.0
@@ -2350,7 +2392,7 @@ packages:
'@trpc/client': 11.0.0-next-beta.289(@trpc/server@11.0.0-next-beta.289)
'@trpc/react-query': 11.0.0-next-beta.289(@tanstack/react-query@5.21.7)(@trpc/client@11.0.0-next-beta.289)(@trpc/server@11.0.0-next-beta.289)(react-dom@18.2.0)(react@18.2.0)
'@trpc/server': 11.0.0-next-beta.289
next: 14.1.1-canary.62(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
next: 14.1.1-canary.63(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
@@ -5720,6 +5762,32 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/mantine-react-table@2.0.0-alpha.17(@mantine/core@7.5.3)(@mantine/dates@7.5.3)(@mantine/hooks@7.5.3)(@tabler/icons-react@2.47.0)(clsx@2.0.0)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-DnB1+c/IiTH2ptVqSEoaiBZ003wa8J5AlYK//apkhZE18M45lJ1iw12LCl1X5kRWx36P2D6O1yw8QW7SZZfQqQ==}
engines: {node: '>=16'}
peerDependencies:
'@mantine/core': ^7.3
'@mantine/dates': ^7.3
'@mantine/hooks': ^7.3
'@tabler/icons-react': '>=2.23.0'
clsx: '>=2'
dayjs: '>=1.11'
react: '>=18.0'
react-dom: '>=18.0'
dependencies:
'@mantine/core': 7.5.3(@mantine/hooks@7.5.3)(@types/react@18.2.57)(react-dom@18.2.0)(react@18.2.0)
'@mantine/dates': 7.5.3(@mantine/core@7.5.3)(@mantine/hooks@7.5.3)(dayjs@1.11.10)(react-dom@18.2.0)(react@18.2.0)
'@mantine/hooks': 7.5.3(react@18.2.0)
'@tabler/icons-react': 2.47.0(react@18.2.0)
'@tanstack/match-sorter-utils': 8.11.8
'@tanstack/react-table': 8.11.8(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-virtual': 3.0.4(react-dom@18.2.0)(react@18.2.0)
clsx: 2.0.0
dayjs: 1.11.10
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/markdown-it@14.0.0:
resolution: {integrity: sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==}
hasBin: true
@@ -5907,7 +5975,7 @@ packages:
engines: {node: '>= 0.4.0'}
dev: true
/next-auth@5.0.0-beta.13(next@14.1.1-canary.62)(react@18.2.0):
/next-auth@5.0.0-beta.13(next@14.1.1-canary.63)(react@18.2.0):
resolution: {integrity: sha512-2m2Gq69WQ0YXcHCCpHn2y5z1bxSlqD/XOuAgrdtz49/VIAdTFFeYZz97RYqf6xMF8VGmoG32VUnJ6LzaHk6Fwg==}
peerDependencies:
'@simplewebauthn/browser': ^9.0.1
@@ -5924,7 +5992,7 @@ packages:
optional: true
dependencies:
'@auth/core': 0.27.0
next: 14.1.1-canary.62(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
next: 14.1.1-canary.63(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0)
react: 18.2.0
dev: false
@@ -5940,8 +6008,8 @@ packages:
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
dev: true
/next@14.1.1-canary.62(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0):
resolution: {integrity: sha512-Qjb/nzmExeYmpOLnrR8LKJo8v9X2H7iWfPhS7fHPxpYC+Aj/XrbWplH6YllMaJR/VHRKeeXDqWEvCqr+q4+8zQ==}
/next@14.1.1-canary.63(@babel/core@7.23.9)(react-dom@18.2.0)(react@18.2.0)(sass@1.71.0):
resolution: {integrity: sha512-2gDV0kHY34eOsUJMQWV+B5SFPzdunNxRGNaPzlEIpvdz6fS23r7ozgeKI9XhtA6cHUF2OXEF6mjl40dxSG8EOw==}
engines: {node: '>=18.17.0'}
hasBin: true
peerDependencies:
@@ -5955,7 +6023,7 @@ packages:
sass:
optional: true
dependencies:
'@next/env': 14.1.1-canary.62
'@next/env': 14.1.1-canary.63
'@swc/helpers': 0.5.5
busboy: 1.6.0
caniuse-lite: 1.0.30001587
@@ -5966,15 +6034,15 @@ packages:
sass: 1.71.0
styled-jsx: 5.1.1(@babel/core@7.23.9)(react@18.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 14.1.1-canary.62
'@next/swc-darwin-x64': 14.1.1-canary.62
'@next/swc-linux-arm64-gnu': 14.1.1-canary.62
'@next/swc-linux-arm64-musl': 14.1.1-canary.62
'@next/swc-linux-x64-gnu': 14.1.1-canary.62
'@next/swc-linux-x64-musl': 14.1.1-canary.62
'@next/swc-win32-arm64-msvc': 14.1.1-canary.62
'@next/swc-win32-ia32-msvc': 14.1.1-canary.62
'@next/swc-win32-x64-msvc': 14.1.1-canary.62
'@next/swc-darwin-arm64': 14.1.1-canary.63
'@next/swc-darwin-x64': 14.1.1-canary.63
'@next/swc-linux-arm64-gnu': 14.1.1-canary.63
'@next/swc-linux-arm64-musl': 14.1.1-canary.63
'@next/swc-linux-x64-gnu': 14.1.1-canary.63
'@next/swc-linux-x64-musl': 14.1.1-canary.63
'@next/swc-win32-arm64-msvc': 14.1.1-canary.63
'@next/swc-win32-ia32-msvc': 14.1.1-canary.63
'@next/swc-win32-x64-msvc': 14.1.1-canary.63
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
@@ -6895,6 +6963,10 @@ packages:
rc: 1.2.8
dev: true
/remove-accents@0.4.2:
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
dev: false
/requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
dev: true