fix(BC: NextUI): fix BC from NextUI, resolve ESLint warnings and optimize hooks dependencies (#6404)

This commit is contained in:
Pablo Lara
2025-01-09 17:37:33 +01:00
committed by GitHub
parent bb417587ae
commit 8f3df7e45d
37 changed files with 765 additions and 378 deletions
+9 -1
View File
@@ -12,6 +12,7 @@ module.exports = {
"plugin:jsx-a11y/recommended",
"eslint-config-prettier",
"prettier",
"next/core-web-vitals",
],
parserOptions: {
ecmaVersion: "latest",
@@ -37,7 +38,14 @@ module.exports = {
"eol-last": ["error", "always"],
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"jsx-a11y/anchor-is-valid": "error",
"jsx-a11y/anchor-is-valid": [
"error",
{
components: ["Link"],
specialLink: ["hrefLeft", "hrefRight"],
aspects: ["invalidHref", "preferButton"],
},
],
"jsx-a11y/alt-text": "error",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
},
+1
View File
@@ -42,6 +42,7 @@ export const getProviders = async ({
revalidatePath("/providers");
return parsedData;
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error fetching providers:", error);
return undefined;
}
+2 -4
View File
@@ -1,13 +1,10 @@
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { parse } from "path";
import { auth } from "@/auth.config";
import { getErrorMessage, parseStringify } from "@/lib";
export const getServices = async ({}) => {
export const getServices = async () => {
const session = await auth();
const keyServer = process.env.API_BASE_URL;
@@ -90,6 +87,7 @@ export const getServices = async ({}) => {
fail_findings: failFindings,
});
} catch (error) {
// eslint-disable-next-line no-console
console.error(`Error fetching data for service ${service.id}:`, error);
}
}
+2
View File
@@ -42,6 +42,7 @@ export const getUsers = async ({
revalidatePath("/users");
return parsedData;
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error fetching users:", error);
return undefined;
}
@@ -200,6 +201,7 @@ export const getProfileInfo = async () => {
revalidatePath("/profile");
return parsedData;
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error fetching profile:", error);
return undefined;
}
+2
View File
@@ -1,3 +1,5 @@
export const dynamic = "force-dynamic";
import { Spacer } from "@nextui-org/react";
import { Suspense } from "react";
+3 -4
View File
@@ -24,11 +24,10 @@ export default function Error({
<RocketIcon className="h-5 w-5" />
<AlertTitle className="text-lg">An unexpected error occurred</AlertTitle>
<AlertDescription className="mb-5">
We're sorry for the inconvenience. Please try again or contact support
if the problem persists.
We&apos;re sorry for the inconvenience. Please try again or contact
support if the problem persists.
</AlertDescription>
<Link href={"/"} className="font-bold">
{" "}
<Link href="/" className="font-bold">
Go to the homepage
</Link>
</Alert>
+8 -8
View File
@@ -72,15 +72,15 @@ const SSRDataTable = async ({
return acc;
}, {}) || {};
const enrichedProviders = providersData?.data.map(
(provider: ProviderProps) => {
const groupNames = provider.relationships.provider_groups.data.map(
(group: { id: string }) =>
providerGroupDict[group.id] || "Unknown Group",
);
const enrichedProviders =
providersData?.data?.map((provider: ProviderProps) => {
const groupNames =
provider.relationships?.provider_groups?.data?.map(
(group: { id: string }) =>
providerGroupDict[group.id] || "Unknown Group",
) || [];
return { ...provider, groupNames };
},
);
}) || [];
return (
<DataTable
+3 -8
View File
@@ -23,19 +23,14 @@ export default async function Services({
<FilterControls />
<Spacer y={4} />
<Suspense key={searchParamsKey} fallback={<ServiceSkeletonGrid />}>
<SSRServiceGrid searchParams={searchParams} />
<SSRServiceGrid />
</Suspense>
</>
);
}
const SSRServiceGrid = async ({
searchParams,
}: {
searchParams: SearchParamsProps;
}) => {
const servicesData = await getServices(searchParams);
const [services] = await Promise.all([servicesData]);
const SSRServiceGrid = async () => {
const services = await getServices();
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
@@ -15,6 +15,9 @@ export const SelectScanComplianceData = ({
<Select
aria-label="Select a Scan"
placeholder="Select a scan"
classNames={{
selectorIcon: "right-2",
}}
labelPlacement="outside"
size="md"
selectedKeys={new Set([selectedScanId])}
@@ -20,6 +20,9 @@ export const CustomAccountSelection = () => {
label="Account"
aria-label="Select an Account"
placeholder="Select an account"
classNames={{
selectorIcon: "right-2",
}}
selectionMode="multiple"
className="w-full"
size="sm"
@@ -4,7 +4,10 @@ import React from "react";
export const CustomCheckboxMutedFindings = () => {
return (
<Checkbox
className="xl:-mt-8"
classNames={{
label: "text-small",
wrapper: "checkbox-update xl:-mt-8",
}}
size="md"
color="danger"
aria-label="Include Muted Findings"
@@ -34,7 +34,9 @@ export const CustomRegionSelection: React.FC = () => {
label="Region"
aria-label="Select a Region"
placeholder="Select a region"
selectionMode="multiple"
classNames={{
selectorIcon: "right-2",
}}
className="w-full"
size="sm"
selectedKeys={selectedKeys}
@@ -23,7 +23,12 @@ export const CustomSearchInput: React.FC = () => {
[router, searchParams],
);
const debouncedChangeHandler = useCallback(debounce(applySearch, 300), []);
const debouncedChangeHandler = useCallback(
(value: string) => {
debounce((val) => applySearch(val), 300)(value);
},
[applySearch],
);
const clearIconSearch = () => {
setSearchQuery("");
@@ -66,6 +66,9 @@ export const CustomSelectProvider: React.FC = () => {
items={dataInputsProvider}
aria-label="Select a Provider"
placeholder="Select a provider"
classNames={{
selectorIcon: "right-2",
}}
label="Provider"
labelPlacement="inside"
size="sm"
@@ -52,30 +52,33 @@ const getProviderData = (
// );
// };
const FindingDetailsCell = ({ row }: { row: any }) => {
const searchParams = useSearchParams();
const findingId = searchParams.get("id");
const isOpen = findingId === row.original.id;
return (
<div className="flex justify-center">
<TriggerSheet
triggerComponent={<InfoIcon className="text-primary" size={16} />}
title="Finding Details"
description="View the finding details"
defaultOpen={isOpen}
>
<DataTableRowDetails
entityId={row.original.id}
findingDetails={row.original}
/>
</TriggerSheet>
</div>
);
};
export const ColumnFindings: ColumnDef<FindingProps>[] = [
{
id: "moreInfo",
header: "Details",
cell: ({ row }) => {
const searchParams = useSearchParams();
const findingId = searchParams.get("id");
const isOpen = findingId === row.original.id;
return (
<div className="flex justify-center">
<TriggerSheet
triggerComponent={<InfoIcon className="text-primary" size={16} />}
title="Finding Details"
description="View the finding details"
defaultOpen={isOpen}
>
<DataTableRowDetails
entityId={row.original.id}
findingDetails={row.original}
/>
</TriggerSheet>
</div>
);
},
cell: ({ row }) => <FindingDetailsCell row={row} />,
},
{
accessorKey: "check",
@@ -133,6 +133,9 @@ export const EditForm = ({
{...field}
label="Role"
placeholder="Select a role"
classNames={{
selectorIcon: "right-2",
}}
variant="bordered"
selectedKeys={[field.value || ""]}
onSelectionChange={(selected) =>
@@ -115,6 +115,9 @@ export const SendInvitationForm = ({
{...field}
label="Role"
placeholder="Select a role"
classNames={{
selectorIcon: "right-2",
}}
variant="bordered"
isDisabled={isSelectorDisabled}
selectedKeys={[field.value]}
@@ -63,18 +63,21 @@ export const FindingsByStatusChart: React.FC<FindingsByStatusChartProps> = ({
pass_new = 0,
fail_new = 0,
} = findingsByStatus?.data?.attributes || {};
const chartData = [
{
findings: "Success",
number: pass,
fill: "var(--color-success)",
},
{
findings: "Fail",
number: fail,
fill: "var(--color-fail)",
},
];
const chartData = useMemo(
() => [
{
findings: "Success",
number: pass,
fill: "var(--color-success)",
},
{
findings: "Fail",
number: fail,
fill: "var(--color-fail)",
},
],
[pass, fail],
);
const updatedChartData = calculatePercent(chartData);
@@ -38,30 +38,33 @@ const getProviderData = (
);
};
const FindingDetailsCell = ({ row }: { row: any }) => {
const searchParams = useSearchParams();
const findingId = searchParams.get("id");
const isOpen = findingId === row.original.id;
return (
<div className="flex justify-center">
<TriggerSheet
triggerComponent={<InfoIcon className="text-primary" size={16} />}
title="Finding Details"
description="View the finding details"
defaultOpen={isOpen}
>
<DataTableRowDetails
entityId={row.original.id}
findingDetails={row.original}
/>
</TriggerSheet>
</div>
);
};
export const ColumnNewFindingsToDate: ColumnDef<FindingProps>[] = [
{
id: "moreInfo",
header: "Details",
cell: ({ row }) => {
const searchParams = useSearchParams();
const findingId = searchParams.get("id");
const isOpen = findingId === row.original.id;
return (
<div className="flex justify-center">
<TriggerSheet
triggerComponent={<InfoIcon className="text-primary" size={16} />}
title="Finding Details"
description="View the finding details"
defaultOpen={isOpen}
>
<DataTableRowDetails
entityId={row.original.id}
findingDetails={row.original}
/>
</TriggerSheet>
</div>
);
},
cell: ({ row }) => <FindingDetailsCell row={row} />,
},
{
accessorKey: "check",
@@ -97,6 +97,7 @@ export const ConnectAccountForm = () => {
router.push(`/providers/add-credentials?type=${providerType}&id=${id}`);
}
} catch (error: any) {
// eslint-disable-next-line no-console
console.error("Error during submission:", error);
toast({
variant: "destructive",
@@ -173,6 +173,7 @@ export const AddRoleForm = ({
onChange={(e) => onSelectAllChange(e.target.checked)}
classNames={{
label: "text-small",
wrapper: "checkbox-update",
}}
>
Grant all admin permissions
@@ -187,6 +188,7 @@ export const AddRoleForm = ({
isSelected={!!form.watch(field as keyof FormValues)}
classNames={{
label: "text-small",
wrapper: "checkbox-update",
}}
>
{label}
@@ -196,6 +196,7 @@ export const EditRoleForm = ({
onChange={(e) => onSelectAllChange(e.target.checked)}
classNames={{
label: "text-small",
wrapper: "checkbox-update",
}}
>
Grant all admin permissions
@@ -210,6 +211,7 @@ export const EditRoleForm = ({
isSelected={!!form.watch(field as keyof FormValues)}
classNames={{
label: "text-small",
wrapper: "checkbox-update",
}}
>
{label}
@@ -58,6 +58,9 @@ export const SelectScanProvider = <
aria-label="Select a scan job"
placeholder="Choose a scan job"
labelPlacement="outside"
classNames={{
selectorIcon: "right-2",
}}
size="md"
selectedKeys={field.value ? new Set([field.value]) : new Set()}
onSelectionChange={(keys) => {
+2 -2
View File
@@ -11,8 +11,8 @@ export const NoProvidersAdded = () => {
<CardBody className="space-y-6 p-6 text-center">
<h2 className="text-xl font-bold">No Cloud Accounts Configured</h2>
<p className="text-md text-gray-600">
You don't have any cloud accounts configured yet. This is the first
step to get started.
You don&apos;t have any cloud accounts configured yet. This is the
first step to get started.
</p>
<CustomButton
asLink="/providers/connect-account"
@@ -17,28 +17,30 @@ const getScanData = (row: { original: ScanProps }) => {
return row.original;
};
const ScanDetailsCell = ({ row }: { row: any }) => {
const searchParams = useSearchParams();
const scanId = searchParams.get("scanId");
const isOpen = scanId === row.original.id;
return (
<div className="flex w-9 items-center justify-center">
<TriggerSheet
triggerComponent={<InfoIcon className="text-primary" size={16} />}
title="Scan Details"
description="View the scan details"
defaultOpen={isOpen}
>
<DataTableRowDetails entityId={row.original.id} />
</TriggerSheet>
</div>
);
};
export const ColumnGetScans: ColumnDef<ScanProps>[] = [
{
id: "moreInfo",
header: "Details",
cell: ({ row }) => {
const searchParams = useSearchParams();
const scanId = searchParams.get("scanId");
const isOpen = scanId === row.original.id;
return (
<div className="flex w-9 items-center justify-center">
<TriggerSheet
triggerComponent={<InfoIcon className="text-primary" size={16} />}
title="Scan Details"
description="View the scan details"
defaultOpen={isOpen}
>
<DataTableRowDetails entityId={row.original.id} />
</TriggerSheet>
</div>
);
},
cell: ({ row }) => <ScanDetailsCell row={row} />,
},
{
accessorKey: "cloudProvider",
@@ -50,6 +50,7 @@ export const DataTableRowDetails = ({ entityId }: { entityId: string }) => {
setScanDetails(result.data);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error in fetchScanDetails:", error);
} finally {
setIsLoading(false);
@@ -7,7 +7,6 @@ interface CustomAlertModalProps {
title?: string;
description?: string;
children: ReactNode;
className?: string;
}
export const CustomAlertModal: React.FC<CustomAlertModalProps> = ({
@@ -16,14 +15,16 @@ export const CustomAlertModal: React.FC<CustomAlertModalProps> = ({
title,
description,
children,
className,
}) => {
return (
<Modal
isOpen={isOpen}
onOpenChange={onOpenChange}
size="xl"
className={`dark:bg-prowler-blue-800 ${className || ""}`}
classNames={{
base: "dark:bg-prowler-blue-800",
closeButton: "right-0",
}}
backdrop="blur"
>
<ModalContent className="py-4">
@@ -31,7 +31,7 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
null,
);
const allFilterKeys = filter?.values || [];
const allFilterKeys = useMemo(() => filter?.values || [], [filter?.values]);
const getActiveFilter = useMemo(() => {
const currentFilters: Record<string, string> = {};
@@ -63,7 +63,7 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
} else {
setGroupSelected(new Set());
}
}, [getActiveFilter, filter?.key, memoizedFilterValues]);
}, [getActiveFilter, filter?.key, memoizedFilterValues, filter]);
const onSelectionChange = useCallback(
(keys: string[]) => {
@@ -127,10 +127,7 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
<Button
isIconOnly
variant="light"
onClick={(e) => {
e.stopPropagation();
onClearFilter(filter.key);
}}
onPress={() => onClearFilter(filter.key)}
className={`absolute right-2 top-1/2 z-40 -translate-y-1/2 ${
groupSelected.size === 0 ? "hidden" : ""
}`}
@@ -181,7 +178,10 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
className="font-bold"
>
<Checkbox
className="font-normal"
classNames={{
label: "text-small font-normal",
wrapper: "checkbox-update",
}}
value="all"
isSelected={groupSelected.has("all")}
onClick={handleSelectAllClick}
@@ -194,7 +194,14 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
className="flex max-h-96 max-w-56 flex-col gap-y-2 py-2"
>
{memoizedFilterValues.map((value) => (
<Checkbox className="font-normal" key={value} value={value}>
<Checkbox
classNames={{
label: "text-small font-normal",
wrapper: "checkbox-update",
}}
key={value}
value={value}
>
{value}
</Checkbox>
))}
@@ -47,7 +47,7 @@ export const CustomDropdownSelection: React.FC<
}
setSelectedValues(newSelection);
}
}, [selectedKeys]);
}, [selectedKeys, allValues.length, selectedValues]);
const onSelectionChange = useCallback(
(keys: string[]) => {
@@ -114,7 +114,10 @@ export const CustomDropdownSelection: React.FC<
className="font-bold"
>
<Checkbox
className="font-normal"
classNames={{
label: "text-small font-normal",
wrapper: "checkbox-update",
}}
value="all"
onClick={handleSelectAllClick}
>
@@ -126,7 +129,14 @@ export const CustomDropdownSelection: React.FC<
className="flex max-h-96 max-w-56 flex-col gap-y-2 py-2"
>
{memoizedValues.map(({ id, name }) => (
<Checkbox className="font-normal" key={id} value={id}>
<Checkbox
classNames={{
label: "text-small font-normal",
wrapper: "checkbox-update",
}}
key={id}
value={id}
>
{name}
</Checkbox>
))}
+1 -1
View File
@@ -37,7 +37,7 @@ export const SidebarWrap = () => {
const onToggle = useCallback(() => {
if (!isCollapsed) openSideMenu();
if (isCollapsed) closeSideMenu();
}, [isCollapsed]);
}, [isCollapsed, openSideMenu, closeSideMenu]);
const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1];
+122 -120
View File
@@ -83,130 +83,132 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
}),
};
const renderNestItem = React.useCallback(
(item: SidebarItem) => {
const isNestType =
item.items &&
item.items?.length > 0 &&
item?.type === SidebarItemType.Nest;
const renderNestItem: (item: SidebarItem) => JSX.Element =
React.useCallback(
(item) => {
const isNestType =
item.items &&
item.items?.length > 0 &&
item?.type === SidebarItemType.Nest;
if (isNestType) {
// Is a nest type item , so we need to remove the href
delete item.href;
}
if (isNestType) {
// Is a nest type item , so we need to remove the href
delete item.href;
}
return (
<ListboxItem
{...item}
key={item.key}
aria-label={item.title}
classNames={{
base: clsx(
{
"h-auto p-0": !isCompact && isNestType,
},
{
"inline-block w-11": isCompact && isNestType,
},
),
}}
endContent={
isCompact || isNestType || hideEndContent
? null
: (item.endContent ?? null)
}
startContent={
isCompact || isNestType ? null : item.icon ? (
<Icon
className={clsx(
"text-default-500 group-data-[selected=true]:text-foreground",
iconClassName,
)}
icon={item.icon}
aria-hidden={"true"}
width={24}
/>
) : (
(item.startContent ?? null)
)
}
title={isCompact || isNestType ? null : item.title}
>
{isCompact ? (
<Tooltip content={item.title} placement="right">
<div className="flex w-full items-center justify-center">
{item.icon ? (
<Icon
className={clsx(
"text-default-500 group-data-[selected=true]:text-foreground",
iconClassName,
)}
aria-hidden={"true"}
icon={item.icon}
width={24}
/>
) : (
(item.startContent ?? null)
)}
</div>
</Tooltip>
) : null}
{!isCompact && isNestType ? (
<Accordion className={"p-0"}>
<AccordionItem
key={item.key}
aria-label={item.title}
classNames={{
heading: "pr-3",
trigger: "p-0",
content: "py-0 pl-4",
}}
title={
item.icon ? (
<div className={"flex items-center gap-2 px-2 py-1.5"}>
<Icon
className={clsx(
"text-default-500 group-data-[selected=true]:text-foreground",
iconClassName,
)}
aria-hidden="true"
icon={item.icon}
width={24}
/>
<span className="text-small font-medium text-default-500 group-data-[selected=true]:text-foreground">
{item.title}
</span>
</div>
return (
<ListboxItem
{...item}
key={item.key}
aria-label={item.title}
classNames={{
base: clsx(
{
"h-auto p-0": !isCompact && isNestType,
},
{
"inline-block w-11": isCompact && isNestType,
},
),
}}
endContent={
isCompact || isNestType || hideEndContent
? null
: (item.endContent ?? null)
}
startContent={
isCompact || isNestType ? null : item.icon ? (
<Icon
className={clsx(
"text-default-500 group-data-[selected=true]:text-foreground",
iconClassName,
)}
icon={item.icon}
aria-hidden={"true"}
width={24}
/>
) : (
(item.startContent ?? null)
)
}
title={isCompact || isNestType ? null : item.title}
>
{isCompact ? (
<Tooltip content={item.title} placement="right">
<div className="flex w-full items-center justify-center">
{item.icon ? (
<Icon
className={clsx(
"text-default-500 group-data-[selected=true]:text-foreground",
iconClassName,
)}
aria-hidden={"true"}
icon={item.icon}
width={24}
/>
) : (
(item.startContent ?? null)
)
}
>
{item.items && item.items?.length > 0 ? (
<Listbox
className={"mt-0.5"}
classNames={{
list: clsx("border-l border-default-200 pl-4"),
}}
items={item.items}
variant="flat"
>
{item.items.map(renderItem)}
</Listbox>
) : (
renderItem(item)
)}
</AccordionItem>
</Accordion>
) : null}
</ListboxItem>
);
},
[isCompact, hideEndContent, iconClassName, items],
);
)}
</div>
</Tooltip>
) : null}
{!isCompact && isNestType ? (
<Accordion className={"p-0"}>
<AccordionItem
key={item.key}
aria-label={item.title}
classNames={{
heading: "pr-3",
trigger: "p-0",
content: "py-0 pl-4",
}}
title={
item.icon ? (
<div className={"flex items-center gap-2 px-2 py-1.5"}>
<Icon
className={clsx(
"text-default-500 group-data-[selected=true]:text-foreground",
iconClassName,
)}
aria-hidden="true"
icon={item.icon}
width={24}
/>
<span className="text-small font-medium text-default-500 group-data-[selected=true]:text-foreground">
{item.title}
</span>
</div>
) : (
(item.startContent ?? null)
)
}
>
{item.items && item.items?.length > 0 ? (
<Listbox
className={"mt-0.5"}
classNames={{
list: clsx("border-l border-default-200 pl-4"),
}}
items={item.items}
variant="flat"
>
{item.items.map(renderItem)}
</Listbox>
) : (
renderItem(item)
)}
</AccordionItem>
</Accordion>
) : null}
</ListboxItem>
);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[isCompact, hideEndContent, iconClassName],
);
const renderItem = React.useCallback(
(item: SidebarItem) => {
const renderItem: (item: SidebarItem) => JSX.Element = React.useCallback(
(item) => {
const isNestType =
item.items &&
item.items?.length > 0 &&
@@ -261,7 +263,7 @@ const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
</ListboxItem>
);
},
[isCompact, hideEndContent, iconClassName, itemClasses?.base],
[isCompact, hideEndContent, iconClassName, renderNestItem],
);
return (
@@ -18,10 +18,11 @@ interface DataTablePaginationProps {
}
export function DataTablePagination({ metadata }: DataTablePaginationProps) {
if (!metadata) return null;
const pathname = usePathname();
const searchParams = useSearchParams();
if (!metadata) return null;
const { currentPage, totalPages, totalEntries } = getPaginationInfo(metadata);
const createPageUrl = (pageNumber: number | string) => {
+3
View File
@@ -178,6 +178,9 @@ export const EditForm = ({
label="Role"
labelPlacement="outside"
placeholder="Select a role"
classNames={{
selectorIcon: "right-2",
}}
variant="bordered"
selectedKeys={[field.value || ""]}
onSelectionChange={(selected) => {
+2
View File
@@ -21,6 +21,7 @@ export const useLocalStorage = (
setState(JSON.parse(value));
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
}, [key]);
@@ -38,6 +39,7 @@ export const useLocalStorage = (
window.localStorage.setItem(key, JSON.stringify(valueToStore));
setState(valueToStore);
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
+447 -141
View File
@@ -44,6 +44,7 @@
"recharts": "^2.13.0-alpha.4",
"server-only": "^0.0.1",
"shadcn-ui": "^0.2.3",
"sharp": "^0.33.5",
"tailwind-merge": "^2.5.3",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0",
@@ -61,7 +62,7 @@
"@typescript-eslint/parser": "^7.10.0",
"autoprefixer": "10.4.19",
"eslint": "^8.56.0",
"eslint-config-next": "14.2.1",
"eslint-config-next": "^14.2.23",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.9.0",
@@ -247,6 +248,16 @@
"node": ">=6.9.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
"integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -489,6 +500,367 @@
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
"dev": true
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"darwin"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"cpu": [
"arm"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"cpu": [
"s390x"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"cpu": [
"arm64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"cpu": [
"x64"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"cpu": [
"arm"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"cpu": [
"s390x"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"cpu": [
"arm64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"cpu": [
"x64"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.2.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"cpu": [
"ia32"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"cpu": [
"x64"
],
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@internationalized/date": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz",
@@ -615,6 +987,16 @@
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.22.tgz",
"integrity": "sha512-EQ6y1QeNQglNmNIXvwP/Bb+lf7n9WtgcWvtoFsHquVLCJUuxRs+6SfZ5EK0/EqkkLex4RrDySvKgKNN7PXip7Q=="
},
"node_modules/@next/eslint-plugin-next": {
"version": "14.2.23",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.23.tgz",
"integrity": "sha512-efRC7m39GoiU1fXZRgGySqYbQi6ZyLkuGlvGst7IwkTTczehQTJA/7PoMg4MMjUZvZEGpiSEu+oJBAjPawiC3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"glob": "10.3.10"
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.2.22",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.22.tgz",
@@ -7242,9 +7624,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001640",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz",
"integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==",
"version": "1.0.30001692",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001692.tgz",
"integrity": "sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==",
"funding": [
{
"type": "opencollective",
@@ -7258,7 +7640,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "4.1.2",
@@ -8252,6 +8635,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -8605,14 +8997,16 @@
}
},
"node_modules/eslint-config-next": {
"version": "14.2.1",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.1.tgz",
"integrity": "sha512-BgD0kPCWMlqoItRf3xe9fG0MqwObKfVch+f2ccwDpZiCJA8ghkz2wrASH+bI6nLZzGcOJOpMm1v1Q1euhfpt4Q==",
"version": "14.2.23",
"resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.23.tgz",
"integrity": "sha512-qtWJzOsDZxnLtXLNtnVjbutHmnEp6QTTSZBTlTCge/Wy0AsUaq8nwR91dBcZZvFg3eY3zKFPBhUkLMHu3Qpauw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@next/eslint-plugin-next": "14.2.1",
"@next/eslint-plugin-next": "14.2.23",
"@rushstack/eslint-patch": "^1.3.3",
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0",
"@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^3.5.2",
"eslint-plugin-import": "^2.28.1",
@@ -8630,133 +9024,6 @@
}
}
},
"node_modules/eslint-config-next/node_modules/@next/eslint-plugin-next": {
"version": "14.2.1",
"resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.1.tgz",
"integrity": "sha512-Fp+mthEBjkn8r9qd6o4JgxKp0IDEzW0VYHD8ZC05xS5/lFNwHKuOdr2kVhWG7BQCO9L6eeepshM1Wbs2T+LgSg==",
"dev": true,
"dependencies": {
"glob": "10.3.10"
}
},
"node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
"integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/typescript-estree": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz",
"integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-config-next/node_modules/@typescript-eslint/types": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz",
"integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz",
"integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz",
"integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.2.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-config-next/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/eslint-config-prettier": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
@@ -12737,10 +13004,10 @@
}
},
"node_modules/semver": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
@@ -12898,6 +13165,45 @@
"node": ">=6"
}
},
"node_modules/sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.3",
"semver": "^7.6.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.5",
"@img/sharp-darwin-x64": "0.33.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4",
"@img/sharp-libvips-darwin-x64": "1.0.4",
"@img/sharp-libvips-linux-arm": "1.0.5",
"@img/sharp-libvips-linux-arm64": "1.0.4",
"@img/sharp-libvips-linux-s390x": "1.0.4",
"@img/sharp-libvips-linux-x64": "1.0.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
"@img/sharp-linux-arm": "0.33.5",
"@img/sharp-linux-arm64": "0.33.5",
"@img/sharp-linux-s390x": "0.33.5",
"@img/sharp-linux-x64": "0.33.5",
"@img/sharp-linuxmusl-arm64": "0.33.5",
"@img/sharp-linuxmusl-x64": "0.33.5",
"@img/sharp-wasm32": "0.33.5",
"@img/sharp-win32-ia32": "0.33.5",
"@img/sharp-win32-x64": "0.33.5"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+2 -1
View File
@@ -36,6 +36,7 @@
"recharts": "^2.13.0-alpha.4",
"server-only": "^0.0.1",
"shadcn-ui": "^0.2.3",
"sharp": "^0.33.5",
"tailwind-merge": "^2.5.3",
"tailwindcss-animate": "^1.0.7",
"uuid": "^10.0.0",
@@ -53,7 +54,7 @@
"@typescript-eslint/parser": "^7.10.0",
"autoprefixer": "10.4.19",
"eslint": "^8.56.0",
"eslint-config-next": "14.2.1",
"eslint-config-next": "^14.2.23",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.9.0",
+4
View File
@@ -36,4 +36,8 @@
.no-scrollbar {
scrollbar-width: none;
}
.checkbox-update {
@apply mr-2 bg-background;
}
}