apps/web: add files upload feature

This commit is contained in:
vchikalkin 2023-11-27 15:00:23 +03:00
parent 71d7a696f1
commit ca521cbc22
15 changed files with 254 additions and 123 deletions

View File

@ -67,3 +67,29 @@ export async function retract({ pageUrlParams, payload }: Input) {
.then((res) => res)
.catch((error: WretchError) => error.json as t.HttpValidationError);
}
export async function getDocumentTypes({ pageUrlParams }: Input) {
const url = createUrl({ ...pageUrlParams, route: '/documenttypes' });
return api
.get(url)
.res<t.ResponseDocumentTypes>((res) => res.json())
.then((res) => res);
}
export async function uploadDocument({
pageUrlParams,
document,
formData,
}: Input & { document: Pick<t.Document, 'documentTypeId'>; formData: FormData }) {
const url = createUrl({
...pageUrlParams,
route: '/document',
urlSearchParams: { ...pageUrlParams.urlSearchParams, ...document },
});
return fetch(urls.URL_IUS + url, {
body: formData,
method: 'POST',
});
}

View File

@ -11,10 +11,16 @@ export type MetaObject = {
type Value = any;
export type Document = {
documentTypeId: string;
name: string;
};
export type ResponseGetData = Record<string, Value>;
export type ResponseMetaData = Record<string, MetaObject>;
export type ResponseConfig = { title: string };
export type ResponseConditions = string;
export type ResponseDocumentTypes = Document[];
export type HttpError = {
errors: string[];

View File

@ -29,8 +29,9 @@ export default async function Page(pageProps: PageProps) {
apiIUS.getData({ pageUrlParams }),
apiIUS.getMetaData({ pageUrlParams }),
apiIUS.getConfig({ pageUrlParams }),
]).then(([data, metaData, { title }]) => {
const props = { data, metaData, pageUrlParams, title };
apiIUS.getDocumentTypes({ pageUrlParams }),
]).then(([data, metaData, { title }, documentTypes]) => {
const props = { data, documentTypes, metaData, pageUrlParams, title };
return <Form {...props} />;
});

View File

@ -1,16 +1,85 @@
/* eslint-disable react/jsx-curly-newline */
/* eslint-disable no-negated-condition */
import { FormContext } from './context/form-context';
import * as apiIus from '@/api/ius/query';
import { useFormStore } from '@/store/ius/form';
import { useRouter } from 'next/navigation';
import { pick } from 'radash';
import { useCallback } from 'react';
import { useContext } from 'react';
import { Button } from 'ui';
const ERROR_UPLOAD_DOCUMENT = 'Произошла ошибка при загрузке документов';
export function Buttons() {
const { reset, resetValidation, setValidation, values } = useFormStore();
const { pageUrlParams, setFormStatus } = useContext(FormContext);
const { formFiles, pageUrlParams, setFormState } = useContext(FormContext);
const router = useRouter();
const handleSave = useCallback(() => {
apiIus.save({ pageUrlParams, payload: values }).then((res) => {
if (typeof res !== 'boolean') {
setTimeout(() => {
setFormState({ status: 'edit' });
}, 300);
Object.keys(res.errors).forEach((name) => {
const elementValidation = res?.errors?.[name];
if (elementValidation)
setValidation({ message: elementValidation[0] ?? '', name, valid: false });
});
} else {
setFormState({ status: 'success' });
setTimeout(() => {
router.refresh();
}, 500);
}
});
}, [pageUrlParams, router, setFormState, setValidation, values]);
const handleUploadFiles = useCallback(() => {
setFormState({ status: 'pending' });
resetValidation();
const uploadFiles = formFiles.map((formFile) => {
const formData = new FormData();
formData.append('file', formFile.file);
const document = pick(formFile, ['documentTypeId']);
return apiIus.uploadDocument({
document,
formData,
pageUrlParams,
});
});
return Promise.allSettled(uploadFiles).catch(() => {
setFormState({ status: 'error', text: ERROR_UPLOAD_DOCUMENT });
throw new Error(ERROR_UPLOAD_DOCUMENT);
});
}, [formFiles, pageUrlParams, resetValidation, setFormState]);
const handleRetract = useCallback(() => {
setFormState({ status: 'pending' });
resetValidation();
apiIus.retract({ pageUrlParams, payload: values }).then((res) => {
if (typeof res !== 'boolean') {
setTimeout(() => {
setFormState({ status: 'edit' });
}, 300);
Object.keys(res.errors).forEach((name) => {
const elementValidation = res?.errors?.[name];
if (elementValidation)
setValidation({ message: elementValidation[0] ?? '', name, valid: false });
});
} else {
setFormState({ status: 'success' });
setTimeout(() => {
router.refresh();
}, 500);
}
});
}, [pageUrlParams, resetValidation, router, setFormState, setValidation, values]);
return (
<div className="grid grid-cols-1 gap-2 gap-x-4 md:grid-cols-3">
<Button
@ -21,57 +90,10 @@ export function Buttons() {
>
Отмена
</Button>
<Button
intent="outline-secondary"
onClick={() => {
setFormStatus('pending');
resetValidation();
apiIus.retract({ pageUrlParams, payload: values }).then((res) => {
if (typeof res !== 'boolean') {
setTimeout(() => {
setFormStatus('edit');
}, 300);
Object.keys(res.errors).forEach((name) => {
const elementValidation = res?.errors?.[name];
if (elementValidation)
setValidation({ message: elementValidation[0] ?? '', name, valid: false });
});
} else {
setFormStatus('success');
setTimeout(() => {
router.refresh();
}, 600);
}
});
}}
>
<Button intent="outline-secondary" onClick={() => handleRetract()}>
Возврат на доработку
</Button>
<Button
onClick={() => {
setFormStatus('pending');
resetValidation();
apiIus.save({ pageUrlParams, payload: values }).then((res) => {
if (typeof res !== 'boolean') {
setTimeout(() => {
setFormStatus('edit');
}, 300);
Object.keys(res.errors).forEach((name) => {
const elementValidation = res?.errors?.[name];
if (elementValidation)
setValidation({ message: elementValidation[0] ?? '', name, valid: false });
});
} else {
setFormStatus('success');
setTimeout(() => {
router.refresh();
}, 600);
}
});
}}
>
Сохранить
</Button>
<Button onClick={() => handleUploadFiles().then(() => handleSave())}>Сохранить</Button>
</div>
);
}

View File

@ -0,0 +1,38 @@
import { FormContext } from './context/form-context';
import type { Props } from './types';
import type { Document } from '@/api/ius/types';
import { useContext } from 'react';
import { Heading, InputFile } from 'ui';
function File({ documentTypeId, name }: Document) {
const { formFiles, setFormFiles } = useContext(FormContext);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
setFormFiles([
...formFiles.filter((x) => x.documentTypeId !== documentTypeId),
{ documentTypeId, file: event.target.files[0], name },
]);
}
};
return (
<div key={documentTypeId} className="flex flex-col">
<label className="mb-2 block text-sm font-normal text-gray-900">{name}:</label>
<InputFile onChange={handleFileChange} />
</div>
);
}
export function Files({ documentTypes }: Props) {
return (
<div className="grid gap-4">
<Heading className="text-sms">Документы</Heading>
<div className="flex flex-col gap-2">
{documentTypes.map((document) => (
<File key={document.documentTypeId} {...document} />
))}
</div>
</div>
);
}

View File

@ -1,6 +1,6 @@
'use client';
import { FormContext } from './context/form-context';
import { CheckCircleIcon } from '@heroicons/react/24/solid';
import { CheckCircleIcon, XCircleIcon } from '@heroicons/react/24/solid';
import type { PropsWithChildren } from 'react';
import { useContext } from 'react';
import { Background, LoadingSpinner } from 'ui';
@ -15,21 +15,30 @@ function OverlayWrapper({ children }: PropsWithChildren) {
);
}
export function Overlay() {
const { formStatus } = useContext(FormContext);
const { formState } = useContext(FormContext);
const { status, text } = formState;
if (formStatus === 'pending')
if (status === 'pending')
return (
<OverlayWrapper>
{LoadingSpinner} <p className="font-medium">Загрузка...</p>
</OverlayWrapper>
);
if (formStatus === 'success')
if (status === 'success')
return (
<OverlayWrapper>
<CheckCircleIcon className="h-10 w-10 fill-green-500" title="OK" />{' '}
<p className="font-medium">Данные сохранены</p>
</OverlayWrapper>
);
if (status === 'error') {
return (
<OverlayWrapper>
<XCircleIcon className="h-10 w-10 fill-red-500" title="Error" />{' '}
<p className="font-medium">{text}</p>
</OverlayWrapper>
);
}
return false;
}

View File

@ -1,13 +1,19 @@
import type { Document } from '@/api/ius/types';
import type { PageUrlParams } from '@/utils/url';
import type { PropsWithChildren } from 'react';
import { createContext, useMemo, useState } from 'react';
type FormStatus = 'pending' | 'edit' | 'success';
type FormStatus = 'pending' | 'edit' | 'success' | 'error';
type FormState = { status: FormStatus; text?: string };
type FormFile = Document & { file: File };
type ContextType = {
readonly formStatus: FormStatus;
readonly formFiles: FormFile[];
readonly formState: FormState;
readonly pageUrlParams: PageUrlParams;
readonly setFormStatus: (status: FormStatus) => void;
readonly setFormFiles: (files: FormFile[]) => void;
readonly setFormState: (formState: FormState) => void;
};
export const FormContext = createContext<ContextType>({} as ContextType);
@ -16,10 +22,11 @@ export function FormContextProvider({
children,
...initialData
}: PropsWithChildren & Pick<ContextType, 'pageUrlParams'>) {
const [formStatus, setFormStatus] = useState<FormStatus>('edit');
const [formState, setFormState] = useState<FormState>({ status: 'edit' });
const [formFiles, setFormFiles] = useState<FormFile[]>([]);
const value = useMemo(
() => ({ ...initialData, formStatus, setFormStatus }),
[formStatus, initialData]
() => ({ ...initialData, formFiles, formState, setFormFiles, setFormState }),
[formFiles, formState, initialData]
);
return <FormContext.Provider value={value}>{children}</FormContext.Provider>;

View File

@ -2,6 +2,7 @@
import { Buttons } from './Buttons';
import { FormContext, FormContextProvider } from './context/form-context';
import { Elements } from './Elements';
import { Files } from './Files';
import { Header } from './Header';
import { Overlay } from './Overlay';
import type { Props } from './types';
@ -20,6 +21,8 @@ function Content(props: Props) {
<Header title={title} link={'/ius' + createUrl({ ...pageUrlParams, route: '/conditions' })} />
<Elements {...props} />
<Divider />
<Files {...props} />
<Divider />
<Buttons />
</Background>
);

View File

@ -1,9 +1,10 @@
import type { ResponseGetData, ResponseMetaData } from '@/api/ius/types';
import type * as IUS from '@/api/ius/types';
import type { PageUrlParams } from '@/utils/url';
export type Props = {
readonly data: ResponseGetData;
readonly metaData: ResponseMetaData;
readonly data: IUS.ResponseGetData;
documentTypes: IUS.ResponseDocumentTypes;
readonly metaData: IUS.ResponseMetaData;
readonly pageUrlParams: PageUrlParams;
readonly title: string;
};

View File

@ -24,7 +24,7 @@ const nextConfig = {
outputFileTracingRoot: path.join(__dirname, '../../'),
},
reactStrictMode: true,
transpilePackages: ['ui'],
transpilePackages: ['ui', 'radash'],
async rewrites() {
return [
{

View File

@ -10,7 +10,8 @@
},
"dependencies": {
"@heroicons/react": "^2.0.18",
"next": "^14.0.1",
"next": "^14.0.3",
"radash": "^11.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsconfig": "*",

View File

@ -1,4 +1,4 @@
export type PageProps = {
params: { slug: string };
searchParams: string | string[][] | Record<string, string>;
searchParams: Record<string, string>;
};

View File

@ -1,7 +1,7 @@
import type { PageProps } from '@/types/page';
export function getPageUrlParams({ params, searchParams }: PageProps) {
return { path: `/${params.slug}`, urlSearchParams: new URLSearchParams(searchParams) };
return { path: `/${params.slug}`, urlSearchParams: searchParams };
}
export type PageUrlParams = ReturnType<typeof getPageUrlParams>;

View File

@ -21,3 +21,15 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
export const InputNumber = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<Input ref={ref} type="number" {...props} />
));
export const InputFile = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
<input
{...props}
ref={ref}
type="file"
className="file:bg-primary-50 file:text-primary-500 hover:file:bg-primary-100 block w-full text-sm
text-slate-500 file:mr-4 file:rounded-sm file:border-0
file:px-4 file:py-2 file:text-sm
file:font-semibold hover:file:cursor-pointer"
/>
));

113
yarn.lock
View File

@ -574,10 +574,10 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@next/env@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.1.tgz#7d03c9042c205a320aef2ea3f83c2d16b6825563"
integrity sha512-Ms8ZswqY65/YfcjrlcIwMPD7Rg/dVjdLapMcSHG26W6O67EJDF435ShW4H4LXi1xKO1oRc97tLXUpx8jpLe86A==
"@next/env@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.3.tgz#9a58b296e7ae04ffebce8a4e5bd0f87f71de86bd"
integrity sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==
"@next/eslint-plugin-next@^13.5.4":
version "13.5.6"
@ -586,50 +586,50 @@
dependencies:
glob "7.1.7"
"@next/swc-darwin-arm64@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.1.tgz#75a5f872c4077ecd536d7496bc24f3d312d5dcd0"
integrity sha512-JyxnGCS4qT67hdOKQ0CkgFTp+PXub5W1wsGvIq98TNbF3YEIN7iDekYhYsZzc8Ov0pWEsghQt+tANdidITCLaw==
"@next/swc-darwin-arm64@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz#b1a0440ffbf69056451947c4aea5b6d887e9fbbc"
integrity sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==
"@next/swc-darwin-x64@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.1.tgz#7d8498fc680cc8b4d5181bee336818c63779bc5e"
integrity sha512-625Z7bb5AyIzswF9hvfZWa+HTwFZw+Jn3lOBNZB87lUS0iuCYDHqk3ujuHCkiyPtSC0xFBtYDLcrZ11mF/ap3w==
"@next/swc-darwin-x64@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz#48b527ef7eb5dbdcaf62fd107bc3a78371f36f09"
integrity sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==
"@next/swc-linux-arm64-gnu@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.1.tgz#184286794e67bed192de7dbb10d7f040c996f965"
integrity sha512-iVpn3KG3DprFXzVHM09kvb//4CNNXBQ9NB/pTm8LO+vnnnaObnzFdS5KM+w1okwa32xH0g8EvZIhoB3fI3mS1g==
"@next/swc-linux-arm64-gnu@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz#0a36475a38b2855ab8ea0fe8b56899bc90184c0f"
integrity sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==
"@next/swc-linux-arm64-musl@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.1.tgz#e8121b860ebc97a8d2a9113e5a42878430e749d5"
integrity sha512-mVsGyMxTLWZXyD5sen6kGOTYVOO67lZjLApIj/JsTEEohDDt1im2nkspzfV5MvhfS7diDw6Rp/xvAQaWZTv1Ww==
"@next/swc-linux-arm64-musl@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz#25328a9f55baa09fde6364e7e47ade65c655034f"
integrity sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==
"@next/swc-linux-x64-gnu@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.1.tgz#cdc4276b11a10c892fd1cb7dd31e024064db9c3b"
integrity sha512-wMqf90uDWN001NqCM/auRl3+qVVeKfjJdT9XW+RMIOf+rhUzadmYJu++tp2y+hUbb6GTRhT+VjQzcgg/QTD9NQ==
"@next/swc-linux-x64-gnu@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.3.tgz#594b747e3c8896b2da67bba54fcf8a6b5a410e5e"
integrity sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==
"@next/swc-linux-x64-musl@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.1.tgz#4a194a484ceb34fd370e8d1af571493859fb2542"
integrity sha512-ol1X1e24w4j4QwdeNjfX0f+Nza25n+ymY0T2frTyalVczUmzkVD7QGgPTZMHfR1aLrO69hBs0G3QBYaj22J5GQ==
"@next/swc-linux-x64-musl@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.3.tgz#a02da58fc6ecad8cf5c5a2a96a7f6030ec7f6215"
integrity sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==
"@next/swc-win32-arm64-msvc@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.1.tgz#71923debee50f98ef166b28cdb3ad7e7463e6598"
integrity sha512-WEmTEeWs6yRUEnUlahTgvZteh5RJc4sEjCQIodJlZZ5/VJwVP8p2L7l6VhzQhT4h7KvLx/Ed4UViBdne6zpIsw==
"@next/swc-win32-arm64-msvc@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz#bf2be23d3ba2ebd0d4a9376a31f783efdb677b48"
integrity sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==
"@next/swc-win32-ia32-msvc@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.1.tgz#b8f46da899c279fd65db76f0951849290c480ef9"
integrity sha512-oFpHphN4ygAgZUKjzga7SoH2VGbEJXZa/KL8bHCAwCjDWle6R1SpiGOdUdA8EJ9YsG1TYWpzY6FTbUA+iAJeww==
"@next/swc-win32-ia32-msvc@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz#839f8de85a4bf2c3c69242483ab87cb916427551"
integrity sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==
"@next/swc-win32-x64-msvc@14.0.1":
version "14.0.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.1.tgz#be3dd8b3729ec51c99ff04b51e2b235756d02b6e"
integrity sha512-FFp3nOJ/5qSpeWT0BZQ+YE1pSMk4IMpkME/1DwKBwhg4mJLB9L+6EXuJi4JEwaJdl5iN+UUlmUD3IsR1kx5fAg==
"@next/swc-win32-x64-msvc@14.0.3":
version "14.0.3"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz#27b623612b1d0cea6efe0a0d31aa1a335fc99647"
integrity sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
@ -4100,12 +4100,12 @@ netmask@^2.0.2:
resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
next@^14.0.1:
version "14.0.1"
resolved "https://registry.yarnpkg.com/next/-/next-14.0.1.tgz#1375d94c5dc7af730234af48401be270e975cb22"
integrity sha512-s4YaLpE4b0gmb3ggtmpmV+wt+lPRuGtANzojMQ2+gmBpgX9w5fTbjsy6dXByBuENsdCX5pukZH/GxdFgO62+pA==
next@^14.0.3:
version "14.0.3"
resolved "https://registry.yarnpkg.com/next/-/next-14.0.3.tgz#8d801a08eaefe5974203d71092fccc463103a03f"
integrity sha512-AbYdRNfImBr3XGtvnwOxq8ekVCwbFTv/UJoLwmaX89nk9i051AEY4/HAWzU0YpaTDw8IofUpmuIlvzWF13jxIw==
dependencies:
"@next/env" "14.0.1"
"@next/env" "14.0.3"
"@swc/helpers" "0.5.2"
busboy "1.6.0"
caniuse-lite "^1.0.30001406"
@ -4113,15 +4113,15 @@ next@^14.0.1:
styled-jsx "5.1.1"
watchpack "2.4.0"
optionalDependencies:
"@next/swc-darwin-arm64" "14.0.1"
"@next/swc-darwin-x64" "14.0.1"
"@next/swc-linux-arm64-gnu" "14.0.1"
"@next/swc-linux-arm64-musl" "14.0.1"
"@next/swc-linux-x64-gnu" "14.0.1"
"@next/swc-linux-x64-musl" "14.0.1"
"@next/swc-win32-arm64-msvc" "14.0.1"
"@next/swc-win32-ia32-msvc" "14.0.1"
"@next/swc-win32-x64-msvc" "14.0.1"
"@next/swc-darwin-arm64" "14.0.3"
"@next/swc-darwin-x64" "14.0.3"
"@next/swc-linux-arm64-gnu" "14.0.3"
"@next/swc-linux-arm64-musl" "14.0.3"
"@next/swc-linux-x64-gnu" "14.0.3"
"@next/swc-linux-x64-musl" "14.0.3"
"@next/swc-win32-arm64-msvc" "14.0.3"
"@next/swc-win32-ia32-msvc" "14.0.3"
"@next/swc-win32-x64-msvc" "14.0.3"
no-case@^2.2.0, no-case@^2.3.2:
version "2.3.2"
@ -4669,6 +4669,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
radash@^11.0.0:
version "11.0.0"
resolved "https://registry.yarnpkg.com/radash/-/radash-11.0.0.tgz#925698e94b554336fc159c253ac33a9a49881835"
integrity sha512-CRWxTFTDff0IELGJ/zz58yY4BDgyI14qSM5OLNKbCItJrff7m7dXbVF0kWYVCXQtPb3SXIVhXvAImH6eT7VLSg==
rambda@^7.4.0:
version "7.5.0"
resolved "https://registry.yarnpkg.com/rambda/-/rambda-7.5.0.tgz#1865044c59bc0b16f63026c6e5a97e4b1bbe98fe"