Compare commits
No commits in common. "main" and "experimental/server-actions" have entirely different histories.
main
...
experiment
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,14 +1,9 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# Logs
|
# dependencies
|
||||||
logs
|
node_modules
|
||||||
*.log
|
.pnp
|
||||||
npm-debug.log*
|
.pnp.js
|
||||||
|
|
||||||
# Dependency directory
|
|
||||||
**/node_modules/**
|
|
||||||
_node_modules
|
|
||||||
.pnp.cjs
|
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
coverage
|
coverage
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -31,6 +31,5 @@
|
|||||||
"tailwindCSS.experimental.classRegex": [
|
"tailwindCSS.experimental.classRegex": [
|
||||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||||
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||||
],
|
]
|
||||||
"editor.inlineSuggest.showToolbar": "always"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,9 @@ This Turborepo includes the following packages/apps:
|
|||||||
|
|
||||||
- `docs`: a [Next.js](https://nextjs.org/) app
|
- `docs`: a [Next.js](https://nextjs.org/) app
|
||||||
- `web`: another [Next.js](https://nextjs.org/) app
|
- `web`: another [Next.js](https://nextjs.org/) app
|
||||||
- `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
|
- `ui`: a stub React component library shared by both `web` and `docs` applications
|
||||||
- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
|
- `eslint-config-custom`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
|
||||||
- `@repo/tsconfig`: `tsconfig.json`s used throughout the monorepo
|
- `tsconfig`: `tsconfig.json`s used throughout the monorepo
|
||||||
|
|
||||||
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
|
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
FROM node:alpine AS builder
|
FROM node:alpine AS builder
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
||||||
ENV PNPM_HOME=/usr/local/bin
|
|
||||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
RUN apk update
|
RUN apk update
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN pnpm add -g turbo
|
RUN yarn global add turbo
|
||||||
|
RUN yarn global add dotenv-cli
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN turbo prune --scope=web --docker
|
RUN turbo prune --scope=web --docker
|
||||||
|
|
||||||
# Add lockfile and package.json's of isolated subworkspace
|
# Add lockfile and package.json's of isolated subworkspace
|
||||||
FROM node:alpine AS installer
|
FROM node:alpine AS installer
|
||||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
||||||
ENV PNPM_HOME=/usr/local/bin
|
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
RUN apk update
|
RUN apk update
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@ -21,16 +18,14 @@ WORKDIR /app
|
|||||||
# First install the dependencies (as they change less often)
|
# First install the dependencies (as they change less often)
|
||||||
COPY .gitignore .gitignore
|
COPY .gitignore .gitignore
|
||||||
COPY --from=builder /app/out/json/ .
|
COPY --from=builder /app/out/json/ .
|
||||||
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
COPY --from=builder /app/out/yarn.lock ./yarn.lock
|
||||||
COPY --from=builder /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
RUN yarn install
|
||||||
RUN pnpm install
|
|
||||||
|
|
||||||
# Build the project
|
# Build the project
|
||||||
COPY --from=builder /app/out/full/ .
|
COPY --from=builder /app/out/full/ .
|
||||||
COPY turbo.json turbo.json
|
COPY turbo.json turbo.json
|
||||||
ARG URL_IUS_DIRECT
|
COPY .env .env
|
||||||
ARG NEXT_PUBLIC_USE_DEV_COLORS
|
RUN yarn dotenv -e .env turbo run build --filter=web...
|
||||||
RUN pnpm turbo run build --filter=web...
|
|
||||||
|
|
||||||
FROM node:alpine AS runner
|
FROM node:alpine AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
'use server';
|
||||||
import type * as t from './types';
|
import type * as t from './types';
|
||||||
import { getUrls } from '@/config/urls';
|
import { getUrls } from '@/config/urls';
|
||||||
import { createUrl, type PageUrlParams } from '@/utils/url';
|
import { createUrl, type PageUrlParams } from '@/utils/url';
|
||||||
@ -5,116 +6,55 @@ import type { WretchError } from 'wretch';
|
|||||||
import wretch from 'wretch';
|
import wretch from 'wretch';
|
||||||
|
|
||||||
const urls = getUrls();
|
const urls = getUrls();
|
||||||
const api = wretch(urls.URL_IUS).options({ cache: 'no-store' }).errorType('json');
|
const api = wretch(urls.URL_UIS).errorType('json');
|
||||||
|
|
||||||
type Input = { cookie?: string; pageUrlParams: PageUrlParams; payload?: unknown };
|
type Input = { pageUrlParams: PageUrlParams; payload?: unknown };
|
||||||
|
|
||||||
export async function getData({ pageUrlParams, cookie = '' }: Input) {
|
export async function getData({ pageUrlParams }: Input) {
|
||||||
const url = createUrl({
|
const url = createUrl({
|
||||||
...pageUrlParams,
|
...pageUrlParams,
|
||||||
route: '',
|
route: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.headers({ cookie })
|
|
||||||
.get(url)
|
.get(url)
|
||||||
.res<t.ResponseGetData>((cb) => cb.json())
|
.res<t.ResponseGetData>((cb) => cb.json())
|
||||||
.then((res) => res);
|
.then((res) => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMetaData({ pageUrlParams, cookie = '' }: Input) {
|
export async function getMetaData({ pageUrlParams }: Input) {
|
||||||
const url = createUrl({ ...pageUrlParams, route: '/meta' });
|
const url = createUrl({ ...pageUrlParams, route: '/meta' });
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.headers({ cookie })
|
|
||||||
.get(url)
|
.get(url)
|
||||||
.res<t.ResponseMetaData>((res) => res.json())
|
.res<t.ResponseMetaData>((res) => res.json())
|
||||||
.then((res) => res);
|
.then((res) => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConfig({ pageUrlParams, cookie = '' }: Input) {
|
export async function getConfig({ pageUrlParams }: Input) {
|
||||||
const url = createUrl({ ...pageUrlParams, route: '/config' });
|
const url = createUrl({ ...pageUrlParams, route: '/config' });
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.headers({ cookie })
|
|
||||||
.get(url)
|
.get(url)
|
||||||
.res<t.ResponseConfig>((res) => res.json())
|
.res<t.ResponseConfig>((res) => res.json())
|
||||||
.then((res) => res);
|
.then((res) => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConditions({ pageUrlParams, cookie = '' }: Input) {
|
export async function getConditions({ pageUrlParams }: Input) {
|
||||||
const url = createUrl({ ...pageUrlParams, route: '/conditions' });
|
const url = createUrl({ ...pageUrlParams, route: '/conditions' });
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.headers({ cookie })
|
|
||||||
.get(url)
|
.get(url)
|
||||||
.res<t.ResponseConditions>((res) => res.text())
|
.res<t.ResponseConditions>((res) => res.text())
|
||||||
.then((res) => res);
|
.then((res) => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save({ pageUrlParams, payload }: Input) {
|
export async function validate({ pageUrlParams, payload }: Input) {
|
||||||
const url = createUrl({ ...pageUrlParams, route: '/transfer' });
|
const url = createUrl({ ...pageUrlParams, route: '/validate' });
|
||||||
|
|
||||||
return api
|
return api
|
||||||
.post(payload, url)
|
.post(payload, url)
|
||||||
.res<boolean>((res) => res.ok)
|
.res<boolean>((res) => res.ok)
|
||||||
.then((res) => res)
|
.then((res) => res)
|
||||||
.catch((error: WretchError) => error.json as t.HttpValidationError | t.HttpError);
|
.catch((error: WretchError) => error.json as t.HttpValidationError);
|
||||||
}
|
|
||||||
|
|
||||||
export async function retract({ pageUrlParams, payload }: Input) {
|
|
||||||
const url = createUrl({ ...pageUrlParams, route: '/return' });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.post(payload, url)
|
|
||||||
.res<boolean>((res) => res.ok)
|
|
||||||
.then((res) => res)
|
|
||||||
.catch((error: WretchError) => error.json as t.HttpValidationError | t.HttpError);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDocumentTypes({ pageUrlParams, cookie = '' }: Input) {
|
|
||||||
const url = createUrl({ ...pageUrlParams, route: '/documenttypes' });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.headers({ cookie })
|
|
||||||
.get(url)
|
|
||||||
.res<t.ResponseDocumentTypes>((res) => res.json())
|
|
||||||
.then((res) => res);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getDocuments({ pageUrlParams, cookie = '' }: Input) {
|
|
||||||
const url = createUrl({ ...pageUrlParams, route: '/documents' });
|
|
||||||
|
|
||||||
return api
|
|
||||||
.headers({ cookie })
|
|
||||||
.get(url)
|
|
||||||
.res<t.ResponseDocuments>((res) => res.json())
|
|
||||||
.then((res) => res);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function uploadDocument({
|
|
||||||
pageUrlParams,
|
|
||||||
document,
|
|
||||||
formData,
|
|
||||||
}: Input & { document: Pick<t.DocumentType, 'documentTypeId'>; formData: FormData }) {
|
|
||||||
const url = createUrl({
|
|
||||||
...pageUrlParams,
|
|
||||||
route: '/document',
|
|
||||||
urlSearchParams: { ...pageUrlParams.urlSearchParams, ...document },
|
|
||||||
});
|
|
||||||
|
|
||||||
return fetch(urls.URL_IUS + url, {
|
|
||||||
body: formData,
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res.ok) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.json();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw error as t.HttpError;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
import type { Document, DocumentType } from './types';
|
|
||||||
|
|
||||||
export function combineDocuments({
|
|
||||||
documentTypes,
|
|
||||||
documents,
|
|
||||||
}: {
|
|
||||||
documentTypes: DocumentType[];
|
|
||||||
documents: Document[];
|
|
||||||
}) {
|
|
||||||
if (!documents.length) {
|
|
||||||
return documentTypes.map((x) => ({ ...(x as Document), canUpload: true }));
|
|
||||||
}
|
|
||||||
|
|
||||||
const nonUploadableDocuments = documents
|
|
||||||
.filter(
|
|
||||||
(document) =>
|
|
||||||
!documentTypes.some(
|
|
||||||
(documentType) => documentType.documentTypeId === document.documentTypeId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map((document) => ({ ...document, canUpload: false }));
|
|
||||||
|
|
||||||
return documentTypes
|
|
||||||
.map((documentType) => {
|
|
||||||
const targetDocument = documents.find(
|
|
||||||
(document) => document.documentTypeId === documentType.documentTypeId
|
|
||||||
);
|
|
||||||
|
|
||||||
return { ...documentType, ...targetDocument, canUpload: true };
|
|
||||||
})
|
|
||||||
.concat(nonUploadableDocuments);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CombinedDocuments = ReturnType<typeof combineDocuments>;
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
export type MetaObject = {
|
export type MetaObject = {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
fieldType: 'CHECKBOX' | 'DECIMAL' | 'INT' | 'STRING' | 'BIGSTRING';
|
fieldType: 'CHECKBOX' | 'DECIMAL' | 'INT' | 'STRING';
|
||||||
label: string;
|
label: string;
|
||||||
max: number;
|
max: number;
|
||||||
min: number;
|
min: number;
|
||||||
@ -11,22 +11,10 @@ export type MetaObject = {
|
|||||||
|
|
||||||
type Value = any;
|
type Value = any;
|
||||||
|
|
||||||
export type DocumentType = {
|
|
||||||
documentTypeId: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Document = DocumentType & {
|
|
||||||
documentId?: string;
|
|
||||||
href?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ResponseGetData = Record<string, Value>;
|
export type ResponseGetData = Record<string, Value>;
|
||||||
export type ResponseMetaData = Record<string, MetaObject>;
|
export type ResponseMetaData = Record<string, MetaObject>;
|
||||||
export type ResponseConfig = { title: string };
|
export type ResponseConfig = { title: string };
|
||||||
export type ResponseConditions = string;
|
export type ResponseConditions = string;
|
||||||
export type ResponseDocumentTypes = DocumentType[];
|
|
||||||
export type ResponseDocuments = Document[];
|
|
||||||
|
|
||||||
export type HttpError = {
|
export type HttpError = {
|
||||||
errors: string[];
|
errors: string[];
|
||||||
|
|||||||
@ -18,24 +18,3 @@
|
|||||||
body {
|
body {
|
||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scroll bar stylings */
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Track */
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle */
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba(0, 0, 0, 0.3);
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle on hover */
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,14 +4,10 @@ import type { PageProps } from '@/types/page';
|
|||||||
import { withError } from '@/utils/error';
|
import { withError } from '@/utils/error';
|
||||||
import { getPageUrlParams } from '@/utils/url';
|
import { getPageUrlParams } from '@/utils/url';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { headers } from 'next/headers';
|
|
||||||
|
|
||||||
export async function generateMetadata(pageProps: PageProps): Promise<Metadata> {
|
export async function generateMetadata(pageProps: PageProps): Promise<Metadata> {
|
||||||
const pageUrlParams = getPageUrlParams(pageProps);
|
const pageUrlParams = getPageUrlParams(pageProps);
|
||||||
const headersList = headers();
|
const { title } = await apiIUS.getConfig({ pageUrlParams });
|
||||||
const cookie = headersList.get('cookie') ?? '';
|
|
||||||
|
|
||||||
const { title } = await apiIUS.getConfig({ cookie, pageUrlParams });
|
|
||||||
const text = `Условия: ${title} | Эволюция`;
|
const text = `Условия: ${title} | Эволюция`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -28,10 +24,7 @@ export default async function Page(pageProps: PageProps) {
|
|||||||
return withError({
|
return withError({
|
||||||
render: async () => {
|
render: async () => {
|
||||||
const pageUrlParams = getPageUrlParams(pageProps);
|
const pageUrlParams = getPageUrlParams(pageProps);
|
||||||
const headersList = headers();
|
const conditions = await apiIUS.getConditions({ pageUrlParams });
|
||||||
const cookie = headersList.get('cookie') ?? '';
|
|
||||||
|
|
||||||
const conditions = await apiIUS.getConditions({ cookie, pageUrlParams });
|
|
||||||
|
|
||||||
return <Conditions html={conditions} />;
|
return <Conditions html={conditions} />;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,19 +1,13 @@
|
|||||||
import * as apiIUS from '@/api/ius/query';
|
import * as apiIUS from '@/api/ius/query';
|
||||||
import { combineDocuments } from '@/api/ius/tools';
|
|
||||||
import { Form } from '@/components/Form';
|
import { Form } from '@/components/Form';
|
||||||
import type { FormComponentProps } from '@/components/Form/types';
|
|
||||||
import type { PageProps } from '@/types/page';
|
import type { PageProps } from '@/types/page';
|
||||||
import { withError } from '@/utils/error';
|
import { withError } from '@/utils/error';
|
||||||
import { getPageUrlParams } from '@/utils/url';
|
import { getPageUrlParams } from '@/utils/url';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { headers } from 'next/headers';
|
|
||||||
|
|
||||||
export async function generateMetadata(pageProps: PageProps): Promise<Metadata> {
|
export async function generateMetadata(pageProps: PageProps): Promise<Metadata> {
|
||||||
const pageUrlParams = getPageUrlParams(pageProps);
|
const pageUrlParams = getPageUrlParams(pageProps);
|
||||||
const headersList = headers();
|
const { title } = await apiIUS.getConfig({ pageUrlParams });
|
||||||
const cookie = headersList.get('cookie') ?? '';
|
|
||||||
|
|
||||||
const { title } = await apiIUS.getConfig({ cookie, pageUrlParams });
|
|
||||||
const text = `${title} | Эволюция`;
|
const text = `${title} | Эволюция`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -31,24 +25,12 @@ export default async function Page(pageProps: PageProps) {
|
|||||||
render: async () => {
|
render: async () => {
|
||||||
const pageUrlParams = getPageUrlParams(pageProps);
|
const pageUrlParams = getPageUrlParams(pageProps);
|
||||||
|
|
||||||
const headersList = headers();
|
|
||||||
const cookie = headersList.get('cookie') ?? '';
|
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
apiIUS.getData({ cookie, pageUrlParams }),
|
apiIUS.getData({ pageUrlParams }),
|
||||||
apiIUS.getMetaData({ cookie, pageUrlParams }),
|
apiIUS.getMetaData({ pageUrlParams }),
|
||||||
apiIUS.getConfig({ cookie, pageUrlParams }),
|
apiIUS.getConfig({ pageUrlParams }),
|
||||||
apiIUS.getDocumentTypes({ cookie, pageUrlParams }),
|
]).then(([data, metaData, { title }]) => {
|
||||||
apiIUS.getDocuments({ cookie, pageUrlParams }),
|
const props = { data, metaData, pageUrlParams, title };
|
||||||
]).then(([data, metaData, { title }, documentTypes, documents]) => {
|
|
||||||
const combinedDocuments = combineDocuments({ documentTypes, documents });
|
|
||||||
const props: FormComponentProps = {
|
|
||||||
combinedDocuments,
|
|
||||||
data,
|
|
||||||
metaData,
|
|
||||||
pageUrlParams,
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Form {...props} />;
|
return <Form {...props} />;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import './globals.css';
|
import './globals.css';
|
||||||
import { Auth, Logo } from '@/components/layout';
|
import { Auth, Logo } from '@/components/layout';
|
||||||
import { Controls } from '@/components/layout/controls';
|
import { Controls } from '@/components/layout/controls';
|
||||||
import { Content, Header } from '@repo/ui';
|
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import localFont from 'next/font/local';
|
import localFont from 'next/font/local';
|
||||||
|
import { Content, Header } from 'ui';
|
||||||
|
|
||||||
const inter = localFont({
|
const inter = localFont({
|
||||||
src: [
|
src: [
|
||||||
@ -46,17 +46,16 @@ export default function RootLayout({ children }: { readonly children: React.Reac
|
|||||||
<head>
|
<head>
|
||||||
<meta charSet="utf-8" />
|
<meta charSet="utf-8" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" crossOrigin="use-credentials" />
|
|
||||||
</head>
|
</head>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<Header>
|
<Header>
|
||||||
<Logo />
|
<Logo />
|
||||||
<Auth />
|
<Auth />
|
||||||
</Header>
|
</Header>
|
||||||
<Content>
|
<div className="flex flex-col items-center ">
|
||||||
<Controls />
|
<Controls />
|
||||||
{children}
|
<Content>{children}</Content>
|
||||||
</Content>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
25
apps/web/app/manifest.ts
Normal file
25
apps/web/app/manifest.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { MetadataRoute } from 'next';
|
||||||
|
|
||||||
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
|
return {
|
||||||
|
background_color: '#fff',
|
||||||
|
description: 'External | Эволюция',
|
||||||
|
display: 'standalone',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
sizes: '192x192',
|
||||||
|
src: '/android-chrome-192x192.png',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sizes: '512x512',
|
||||||
|
src: '/android-chrome-512x512.png',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: 'External | Эволюция',
|
||||||
|
short_name: 'External | Эволюция',
|
||||||
|
start_url: '/',
|
||||||
|
theme_color: '#fff',
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { HttpError } from '@repo/ui';
|
import { HttpError } from 'ui';
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return <HttpError code="404" title="Страница не найдена" />;
|
return <HttpError code="404" title="Страница не найдена" />;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Background } from '@repo/ui';
|
import { Background } from 'ui';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
readonly html: string;
|
readonly html: string;
|
||||||
@ -8,7 +8,7 @@ export function Conditions({ html }: Props) {
|
|||||||
return (
|
return (
|
||||||
<Background
|
<Background
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
className="grid w-full justify-center p-5 font-[calibri]"
|
className="lg:w-standard grid w-full justify-center p-5 font-[calibri]"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
46
apps/web/components/Form/Buttons.tsx
Normal file
46
apps/web/components/Form/Buttons.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { FormContext } from './context/form-context';
|
||||||
|
import * as apiIus from '@/api/ius/query';
|
||||||
|
import { useFormStore } from '@/store/ius/form';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { Button } from 'ui';
|
||||||
|
|
||||||
|
export function Buttons() {
|
||||||
|
const { reset, setValidation, values } = useFormStore();
|
||||||
|
const { pageUrlParams, setFormStatus } = useContext(FormContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 gap-2 gap-x-4 md:grid-cols-3">
|
||||||
|
<Button
|
||||||
|
intent="outline-danger"
|
||||||
|
onClick={() => {
|
||||||
|
reset();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
setFormStatus('pending');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Возврат на доработку
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
apiIus.validate({ pageUrlParams, payload: values }).then((res) => {
|
||||||
|
if (typeof res !== 'boolean') {
|
||||||
|
Object.keys(res.errors).forEach((name) => {
|
||||||
|
const elementValidation = res?.errors?.[name];
|
||||||
|
if (elementValidation)
|
||||||
|
setValidation({ message: elementValidation[0] ?? '', name, valid: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,95 +0,0 @@
|
|||||||
/* 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 { Button } from '@repo/ui';
|
|
||||||
import { useCallback, useContext } from 'react';
|
|
||||||
|
|
||||||
const ERROR_RETRACT = 'Произошла ошибка при возврате на доработку';
|
|
||||||
const ERROR_SAVE = 'Произошла ошибка при сохранении';
|
|
||||||
|
|
||||||
export function Buttons() {
|
|
||||||
const { reset, resetValidation, setValidation, status, values } = useFormStore();
|
|
||||||
const { pageUrlParams, setFormState } = useContext(FormContext);
|
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
|
||||||
setFormState({ status: 'pending' });
|
|
||||||
resetValidation();
|
|
||||||
apiIus.save({ pageUrlParams, payload: values }).then((res) => {
|
|
||||||
if (typeof res !== 'boolean') {
|
|
||||||
const { errors } = res;
|
|
||||||
|
|
||||||
if (Array.isArray(errors)) {
|
|
||||||
setFormState({ status: 'error', text: errors?.at(0) || ERROR_SAVE });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(errors).forEach((name) => {
|
|
||||||
const elementValidation = errors?.[name];
|
|
||||||
if (elementValidation)
|
|
||||||
setValidation({ message: elementValidation[0] ?? '', name, valid: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setFormState({ status: 'edit' });
|
|
||||||
}, 300);
|
|
||||||
} else {
|
|
||||||
setFormState({ status: 'success' });
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [pageUrlParams, resetValidation, setFormState, setValidation, values]);
|
|
||||||
|
|
||||||
const handleRetract = useCallback(() => {
|
|
||||||
setFormState({ status: 'pending' });
|
|
||||||
resetValidation();
|
|
||||||
apiIus.retract({ pageUrlParams, payload: values }).then((res) => {
|
|
||||||
if (typeof res !== 'boolean') {
|
|
||||||
const { errors } = res;
|
|
||||||
|
|
||||||
if (Array.isArray(errors)) {
|
|
||||||
setFormState({ status: 'error', text: errors?.at(0) || ERROR_RETRACT });
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setFormState({ status: 'edit' });
|
|
||||||
}, 300);
|
|
||||||
Object.keys(errors).forEach((name) => {
|
|
||||||
const elementValidation = errors?.[name];
|
|
||||||
if (elementValidation)
|
|
||||||
setValidation({ message: elementValidation[0] ?? '', name, valid: false });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setFormState({ status: 'success' });
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, [pageUrlParams, resetValidation, setFormState, setValidation, values]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 gap-2 gap-x-4 md:grid-cols-3">
|
|
||||||
<Button
|
|
||||||
intent="outline-danger"
|
|
||||||
onClick={() => {
|
|
||||||
reset();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
<Button intent="outline-secondary" onClick={handleRetract}>
|
|
||||||
Возврат на доработку
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleSave} disabled={status !== 'edited'}>
|
|
||||||
Сохранить
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
import type { FormComponentProps } from '../types';
|
|
||||||
import type { MetaObject } from '@/api/ius/types';
|
|
||||||
import { mapFieldTypeElement } from '@/config/elements';
|
|
||||||
import { useFormStore } from '@/store/ius/form';
|
|
||||||
import { ElementContainer } from '@repo/ui';
|
|
||||||
import { get } from 'radash';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
function RenderElement({
|
|
||||||
fieldType,
|
|
||||||
label,
|
|
||||||
max,
|
|
||||||
min = 0,
|
|
||||||
name,
|
|
||||||
visible,
|
|
||||||
...props
|
|
||||||
}: MetaObject & { readonly name: string }) {
|
|
||||||
const { setValue, validation, values } = useFormStore();
|
|
||||||
|
|
||||||
if (!visible) return false;
|
|
||||||
|
|
||||||
const Element = mapFieldTypeElement[fieldType];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ElementContainer
|
|
||||||
intent={validation[name] ? 'danger' : 'default'}
|
|
||||||
message={validation[name]?.message}
|
|
||||||
key={name}
|
|
||||||
id={name}
|
|
||||||
title={label}
|
|
||||||
>
|
|
||||||
<Element
|
|
||||||
loading={!Object.keys(values).length}
|
|
||||||
checked={fieldType === 'CHECKBOX' ? Boolean(values[name]) ?? false : false}
|
|
||||||
id={name}
|
|
||||||
value={values[name] ?? ''}
|
|
||||||
min={min}
|
|
||||||
max={max}
|
|
||||||
onChange={(e) => {
|
|
||||||
setValue({
|
|
||||||
name,
|
|
||||||
value:
|
|
||||||
fieldType === 'CHECKBOX' ? (e.target as HTMLInputElement).checked : e.target.value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</ElementContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Elements({ data, metaData }: FormComponentProps) {
|
|
||||||
const { init } = useFormStore();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
init(data);
|
|
||||||
}, [data, init]);
|
|
||||||
|
|
||||||
const defaultElements = Object.keys(metaData).filter(
|
|
||||||
(x) => metaData[x]?.fieldType !== 'BIGSTRING'
|
|
||||||
);
|
|
||||||
|
|
||||||
const bigStringElements = Object.keys(metaData).filter(
|
|
||||||
(x) => metaData[x]?.fieldType === 'BIGSTRING'
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mt-2 grid gap-2 gap-x-4 md:grid md:grid-cols-2 lg:grid-cols-3">
|
|
||||||
{defaultElements.map((name) => (
|
|
||||||
<RenderElement key={name} {...get(metaData, name)} name={name} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{bigStringElements.map((name) => (
|
|
||||||
<RenderElement key={name} {...get(metaData, name)} name={name} fieldType="BIGSTRING" />
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from './Buttons';
|
|
||||||
export * from './Elements';
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
/* 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 type { HttpError } from '@/api/ius/types';
|
|
||||||
import { useFormStore } from '@/store/ius/form';
|
|
||||||
import { Button } from '@repo/ui';
|
|
||||||
import { pick } from 'radash';
|
|
||||||
import { useCallback, useContext } from 'react';
|
|
||||||
|
|
||||||
const ERROR_UPLOAD_DOCUMENT = 'Произошла ошибка при загрузке документов';
|
|
||||||
const SUCCESS_UPLOAD_DOCUMENTS = 'Файлы успешно загружены';
|
|
||||||
|
|
||||||
export function Buttons() {
|
|
||||||
const { resetValidation } = useFormStore();
|
|
||||||
const { formFiles, pageUrlParams, setFormFiles, setFormState } = useContext(FormContext);
|
|
||||||
|
|
||||||
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.all(uploadFiles)
|
|
||||||
.then(async (res) => {
|
|
||||||
const errors = res.filter((x) => typeof x !== 'boolean') as HttpError[];
|
|
||||||
|
|
||||||
if (!errors.length) {
|
|
||||||
setFormState({
|
|
||||||
status: 'success',
|
|
||||||
text: SUCCESS_UPLOAD_DOCUMENTS,
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const error = errors.find((x) => x.errors.length)?.errors.at(0) || ERROR_UPLOAD_DOCUMENT;
|
|
||||||
|
|
||||||
setFormState({ status: 'error', text: error });
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setFormState({
|
|
||||||
status: 'error',
|
|
||||||
text: error ? JSON.stringify(error) : ERROR_UPLOAD_DOCUMENT,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [formFiles, pageUrlParams, resetValidation, setFormState]);
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
setFormFiles([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="grid grid-cols-1 gap-2 gap-x-4 md:grid-cols-3">
|
|
||||||
<Button intent="outline-danger" onClick={handleCancel} disabled={!formFiles.length}>
|
|
||||||
Отмена
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleUploadFiles} disabled={!formFiles.length}>
|
|
||||||
Загрузить файлы
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import { FormContext } from '../context/form-context';
|
|
||||||
import type { FormComponentProps } from '../types';
|
|
||||||
import { ArrowDownTrayIcon } from '@heroicons/react/24/solid';
|
|
||||||
import { Heading, InputFile } from '@repo/ui';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useContext, useEffect, useRef } from 'react';
|
|
||||||
|
|
||||||
type DownloadDocumentProps = Pick<FileProps, 'document'>;
|
|
||||||
|
|
||||||
function DownloadDocument({ document }: DownloadDocumentProps) {
|
|
||||||
return document?.href ? (
|
|
||||||
<Link
|
|
||||||
href={'/api/ius' + document.href}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-primary-600 h-10 px-5 py-2.5 text-sm font-medium hover:underline"
|
|
||||||
>
|
|
||||||
<div className="flex flex-row items-center gap-1">
|
|
||||||
<ArrowDownTrayIcon className="h-4 w-4" />
|
|
||||||
Скачать
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileProps = {
|
|
||||||
readonly document: FormComponentProps['combinedDocuments'][number];
|
|
||||||
};
|
|
||||||
|
|
||||||
function File({ document }: FileProps) {
|
|
||||||
const { formFiles, setFormFiles } = useContext(FormContext);
|
|
||||||
|
|
||||||
const { canUpload, documentTypeId, name } = document;
|
|
||||||
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (event.target.files !== null) {
|
|
||||||
const file = event.target.files.item(0);
|
|
||||||
if (file)
|
|
||||||
setFormFiles([
|
|
||||||
...formFiles.filter((x) => x.documentTypeId !== documentTypeId),
|
|
||||||
{ documentTypeId, file, name },
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const uploadableFile = formFiles.some((x) => x.documentTypeId === document.documentTypeId);
|
|
||||||
if (!uploadableFile && inputRef.current) inputRef.current.value = '';
|
|
||||||
}, [document.documentTypeId, formFiles]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={documentTypeId} className="flex flex-col gap-1">
|
|
||||||
<label className="mb-2 block text-sm font-normal text-gray-900">{name}:</label>
|
|
||||||
<InputFile ref={inputRef} onChange={handleFileChange} disabled={!canUpload} />
|
|
||||||
<DownloadDocument document={document} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Files({ combinedDocuments }: FormComponentProps) {
|
|
||||||
return (
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<Heading className="text-sm">Документы</Heading>
|
|
||||||
<div className="grid gap-2 md:grid-cols-2">
|
|
||||||
{combinedDocuments.map((document) => (
|
|
||||||
<File key={document.documentTypeId} document={document} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from './Buttons';
|
|
||||||
export * from './Files';
|
|
||||||
51
apps/web/components/Form/Elements.tsx
Normal file
51
apps/web/components/Form/Elements.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import type { Props } from './types';
|
||||||
|
import { mapFieldTypeElement } from '@/config/elements';
|
||||||
|
import { useFormStore } from '@/store/ius/form';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { ElementContainer } from 'ui';
|
||||||
|
|
||||||
|
export function Elements({ data, metaData }: Props) {
|
||||||
|
const { init, setValue, validation, values } = useFormStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
init(data);
|
||||||
|
}, [data, init]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-2 grid auto-rows-auto grid-cols-1 gap-2 gap-x-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{Object.keys(metaData).map((name) => {
|
||||||
|
const { fieldType, label, max, min = 0, visible, ...props } = metaData[name];
|
||||||
|
|
||||||
|
if (!visible) return false;
|
||||||
|
|
||||||
|
const Element = mapFieldTypeElement[fieldType];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ElementContainer
|
||||||
|
intent={validation[name] ? 'danger' : 'default'}
|
||||||
|
message={validation[name]?.message}
|
||||||
|
key={name}
|
||||||
|
id={name}
|
||||||
|
title={label}
|
||||||
|
>
|
||||||
|
<Element
|
||||||
|
loading={!Object.keys(values).length}
|
||||||
|
checked={fieldType === 'CHECKBOX' ? Boolean(values[name]) || false : false}
|
||||||
|
id={name}
|
||||||
|
value={values[name] || ''}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue({
|
||||||
|
name,
|
||||||
|
value: fieldType === 'CHECKBOX' ? e.target.checked : e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</ElementContainer>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { Heading } from '@repo/ui';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { Heading } from 'ui';
|
||||||
|
|
||||||
export function Header({ link, title }: { readonly link: string; readonly title: string }) {
|
export function Header({ link, title }: { readonly link: string; readonly title: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-between md:flex-row">
|
<div className="flex justify-between">
|
||||||
<Heading size={2}>{title}</Heading>
|
<Heading size={2}>{title}</Heading>
|
||||||
<Link href={link} className="text-primary-600 text-sm font-medium hover:underline" prefetch>
|
<Link href={link} className="text-primary-600 text-sm font-medium hover:underline" prefetch>
|
||||||
Посмотреть условия
|
Посмотреть условия
|
||||||
|
|||||||
@ -1,61 +1,33 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { FormContext } from './context/form-context';
|
import { FormContext } from './context/form-context';
|
||||||
import { CheckCircleIcon, XCircleIcon } from '@heroicons/react/24/solid';
|
import { CheckCircleIcon } from '@heroicons/react/24/solid';
|
||||||
import { Background, Button, LoadingSpinner } from '@repo/ui';
|
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
import { Background, LoadingSpinner } from 'ui';
|
||||||
|
|
||||||
function OverlayWrapper({ children }: PropsWithChildren) {
|
function OverlayWrapper({ children }: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<Background className="absolute left-0 top-0 grid h-full w-full place-items-center bg-opacity-80 backdrop-blur-sm">
|
<Background className="lg:w-standard absolute grid h-full w-full items-center justify-center border-none bg-opacity-80 backdrop-blur-sm">
|
||||||
<div className="absolute bottom-[50vh] flex flex-col items-center gap-2 md:relative md:bottom-0">
|
<div className="flex flex-row items-center gap-2">{children} </div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</Background>
|
</Background>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function StateContentWrapper({ children }: PropsWithChildren) {
|
|
||||||
return <div className="flex flex-row items-center gap-2">{children}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Overlay() {
|
export function Overlay() {
|
||||||
const { formState, setFormState } = useContext(FormContext);
|
const { formStatus } = useContext(FormContext);
|
||||||
const { status, text } = formState;
|
|
||||||
|
|
||||||
let stateContent: JSX.Element | false = false;
|
if (formStatus === 'pending')
|
||||||
|
return (
|
||||||
if (status === 'pending') {
|
<OverlayWrapper>
|
||||||
stateContent = (
|
|
||||||
<StateContentWrapper>
|
|
||||||
{LoadingSpinner} <p className="font-medium">Загрузка...</p>
|
{LoadingSpinner} <p className="font-medium">Загрузка...</p>
|
||||||
</StateContentWrapper>
|
</OverlayWrapper>
|
||||||
);
|
);
|
||||||
}
|
if (formStatus === 'success')
|
||||||
if (status === 'success') {
|
return (
|
||||||
stateContent = (
|
<OverlayWrapper>
|
||||||
<StateContentWrapper>
|
<CheckCircleIcon className="h-10 w-10 fill-green-500" title="OK" />{' '}
|
||||||
<CheckCircleIcon className="h-10 w-10 fill-green-500" title="OK" />
|
|
||||||
<p className="font-medium">Данные сохранены</p>
|
<p className="font-medium">Данные сохранены</p>
|
||||||
</StateContentWrapper>
|
</OverlayWrapper>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
if (status === 'error') {
|
return false;
|
||||||
stateContent = (
|
|
||||||
<>
|
|
||||||
<StateContentWrapper>
|
|
||||||
<XCircleIcon className="h-10 w-10 fill-red-500" title="Error" />
|
|
||||||
<p className="font-medium">{text}</p>
|
|
||||||
</StateContentWrapper>{' '}
|
|
||||||
<Button type="button" intent="text" onClick={() => setFormState({ status: 'edit' })}>
|
|
||||||
Закрыть
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stateContent) return false;
|
|
||||||
|
|
||||||
return <OverlayWrapper>{stateContent}</OverlayWrapper>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,13 @@
|
|||||||
import type { DocumentType } from '@/api/ius/types';
|
|
||||||
import type { PageUrlParams } from '@/utils/url';
|
import type { PageUrlParams } from '@/utils/url';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { createContext, useMemo, useState } from 'react';
|
import { createContext, useMemo, useState } from 'react';
|
||||||
|
|
||||||
type FormStatus = 'pending' | 'edit' | 'success' | 'error';
|
type FormStatus = 'pending' | 'edit' | 'success';
|
||||||
type FormState = { status: FormStatus; text?: string };
|
|
||||||
|
|
||||||
type FormFile = DocumentType & { file: File };
|
|
||||||
|
|
||||||
type ContextType = {
|
type ContextType = {
|
||||||
readonly formFiles: FormFile[];
|
readonly formStatus: FormStatus;
|
||||||
readonly formState: FormState;
|
|
||||||
readonly pageUrlParams: PageUrlParams;
|
readonly pageUrlParams: PageUrlParams;
|
||||||
readonly setFormFiles: (files: FormFile[]) => void;
|
readonly setFormStatus: (status: FormStatus) => void;
|
||||||
readonly setFormState: (formState: FormState) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FormContext = createContext<ContextType>({} as ContextType);
|
export const FormContext = createContext<ContextType>({} as ContextType);
|
||||||
@ -22,11 +16,10 @@ export function FormContextProvider({
|
|||||||
children,
|
children,
|
||||||
...initialData
|
...initialData
|
||||||
}: PropsWithChildren & Pick<ContextType, 'pageUrlParams'>) {
|
}: PropsWithChildren & Pick<ContextType, 'pageUrlParams'>) {
|
||||||
const [formState, setFormState] = useState<FormState>({ status: 'edit' });
|
const [formStatus, setFormStatus] = useState<FormStatus>('edit');
|
||||||
const [formFiles, setFormFiles] = useState<FormFile[]>([]);
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({ ...initialData, formFiles, formState, setFormFiles, setFormState }),
|
() => ({ ...initialData, formStatus, setFormStatus }),
|
||||||
[formFiles, formState, initialData]
|
[formStatus, initialData]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <FormContext.Provider value={value}>{children}</FormContext.Provider>;
|
return <FormContext.Provider value={value}>{children}</FormContext.Provider>;
|
||||||
|
|||||||
@ -1,34 +1,31 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import * as Common from './Common';
|
import { Buttons } from './Buttons';
|
||||||
import { FormContext, FormContextProvider } from './context/form-context';
|
import { FormContext, FormContextProvider } from './context/form-context';
|
||||||
import * as Documents from './Documents';
|
import { Elements } from './Elements';
|
||||||
import { Header } from './Header';
|
import { Header } from './Header';
|
||||||
import { Overlay } from './Overlay';
|
import { Overlay } from './Overlay';
|
||||||
import type { FormComponentProps } from './types';
|
import type { Props } from './types';
|
||||||
import { createUrl } from '@/utils/url';
|
import { createUrl } from '@/utils/url';
|
||||||
import { Background, Divider } from '@repo/ui';
|
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
import { Background, Divider } from 'ui';
|
||||||
|
|
||||||
function Content(props: FormComponentProps) {
|
function Content(props: Props) {
|
||||||
const { title } = props;
|
const { title } = props;
|
||||||
const { pageUrlParams } = useContext(FormContext);
|
const { pageUrlParams } = useContext(FormContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Background className="relative flex w-full flex-col gap-2 p-5">
|
<Background className="lg:w-standard relative grid w-full gap-2 p-5">
|
||||||
<Overlay />
|
<Overlay />
|
||||||
<Header title={title} link={'/ius' + createUrl({ ...pageUrlParams, route: '/conditions' })} />
|
<Header title={title} link={'/ius' + createUrl({ ...pageUrlParams, route: '/conditions' })} />
|
||||||
<Common.Elements {...props} />
|
<Elements {...props} />
|
||||||
<Common.Buttons />
|
|
||||||
<Divider />
|
|
||||||
<Documents.Files {...props} />
|
|
||||||
<Documents.Buttons />
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Buttons />
|
||||||
</Background>
|
</Background>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function withContext<T extends FormComponentProps>(Component: FC<T>) {
|
function withContext<T extends Props>(Component: FC<T>) {
|
||||||
return (props: T) => {
|
return (props: T) => {
|
||||||
const { pageUrlParams } = props;
|
const { pageUrlParams } = props;
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import type { CombinedDocuments } from '@/api/ius/tools';
|
import type { ResponseGetData, ResponseMetaData } from '@/api/ius/types';
|
||||||
import type * as IUS from '@/api/ius/types';
|
|
||||||
import type { PageUrlParams } from '@/utils/url';
|
import type { PageUrlParams } from '@/utils/url';
|
||||||
|
|
||||||
export type FormComponentProps = {
|
export type Props = {
|
||||||
readonly combinedDocuments: CombinedDocuments;
|
readonly data: ResponseGetData;
|
||||||
readonly data: IUS.ResponseGetData;
|
readonly metaData: ResponseMetaData;
|
||||||
readonly metaData: IUS.ResponseMetaData;
|
|
||||||
readonly pageUrlParams: PageUrlParams;
|
readonly pageUrlParams: PageUrlParams;
|
||||||
readonly title: string;
|
readonly title: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export function Controls() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-row px-5 pt-5">
|
<div className="lg:w-standard flex w-full flex-row px-5 pt-5">
|
||||||
<button
|
<button
|
||||||
className="flex flex-row items-center gap-2 text-sm"
|
className="flex flex-row items-center gap-2 text-sm"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { getClientEnv } from '@/config/env';
|
import { getEnv } from '@/config/env';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import logo from 'public/assets/images/logo-primary.svg';
|
import logo from 'public/assets/images/logo-primary.svg';
|
||||||
import logoDev from 'public/assets/images/logo-primary-dev.svg';
|
import logoDev from 'public/assets/images/logo-primary-dev.svg';
|
||||||
|
|
||||||
const env = getClientEnv();
|
const env = getEnv();
|
||||||
|
|
||||||
export function Logo() {
|
export function Logo() {
|
||||||
return (
|
return <Image priority alt="logo" src={env.USE_DEV_COLORS ? logoDev : logo} height="24" />;
|
||||||
<Image priority alt="logo" src={env.NEXT_PUBLIC_USE_DEV_COLORS ? logoDev : logo} height="24" />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import type { MetaObject } from '@/api/ius/types';
|
import type { MetaObject } from '@/api/ius/types';
|
||||||
import { Checkbox, Input, InputNumber, Textarea } from '@repo/ui';
|
import { Checkbox, Input, InputNumber } from 'ui';
|
||||||
|
|
||||||
function wrapMap<C, T extends Record<MetaObject['fieldType'], C>>(arg: T) {
|
function wrapMap<C, T extends Record<MetaObject['fieldType'], C>>(arg: T) {
|
||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapFieldTypeElement = wrapMap({
|
export const mapFieldTypeElement = wrapMap({
|
||||||
BIGSTRING: Textarea,
|
|
||||||
CHECKBOX: Checkbox,
|
CHECKBOX: Checkbox,
|
||||||
DECIMAL: InputNumber,
|
DECIMAL: InputNumber,
|
||||||
INT: InputNumber,
|
INT: InputNumber,
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
const { clientEnvSchema, serverEnvSchema } = require('./schema/env');
|
const envSchema = require('./schema/env.js');
|
||||||
|
|
||||||
const getClientEnv = () => clientEnvSchema.parse(process.env);
|
const getEnv = () => envSchema.parse(process.env);
|
||||||
const getServerEnv = () => serverEnvSchema.parse(process.env);
|
|
||||||
|
|
||||||
module.exports = { getClientEnv, getServerEnv };
|
module.exports = { getEnv };
|
||||||
|
|||||||
@ -2,15 +2,7 @@ const { z } = require('zod');
|
|||||||
|
|
||||||
const envSchema = z.object({
|
const envSchema = z.object({
|
||||||
URL_IUS_DIRECT: z.string(),
|
URL_IUS_DIRECT: z.string(),
|
||||||
NEXT_PUBLIC_USE_DEV_COLORS: z.string().optional(),
|
USE_DEV_COLORS: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const serverEnvSchema = envSchema.pick({
|
module.exports = envSchema;
|
||||||
URL_IUS_DIRECT: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const clientEnvSchema = envSchema.pick({
|
|
||||||
NEXT_PUBLIC_USE_DEV_COLORS: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = { envSchema, serverEnvSchema, clientEnvSchema };
|
|
||||||
|
|||||||
@ -1,18 +1,9 @@
|
|||||||
import { getServerEnv } from './env';
|
import { getEnv } from './env';
|
||||||
import proxyUrls from '@/constants/urls';
|
|
||||||
import { isServer } from '@/utils/common';
|
const { URL_IUS_DIRECT } = getEnv();
|
||||||
|
|
||||||
export function getUrls() {
|
export function getUrls() {
|
||||||
if (isServer()) {
|
|
||||||
const env = getServerEnv();
|
|
||||||
const { URL_IUS_DIRECT } = env;
|
|
||||||
|
|
||||||
return {
|
|
||||||
URL_IUS: URL_IUS_DIRECT,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
URL_IUS: proxyUrls.URL_IUS_PROXY,
|
URL_UIS: URL_IUS_DIRECT,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { getClientEnv } from '../config/env';
|
import { getEnv } from '../config/env';
|
||||||
|
|
||||||
export const COLORS_PROD = {
|
export const COLORS_PROD = {
|
||||||
COLOR_DANGER: '#B20004',
|
COLOR_DANGER: '#B20004',
|
||||||
@ -12,5 +12,5 @@ export const COLORS_DEV = {
|
|||||||
COLOR_SECONDARY: '#c54a84',
|
COLOR_SECONDARY: '#c54a84',
|
||||||
COLOR_TERTIARTY: '#FF9112',
|
COLOR_TERTIARTY: '#FF9112',
|
||||||
};
|
};
|
||||||
const env = getClientEnv();
|
const env = getEnv();
|
||||||
export const COLORS = env.NEXT_PUBLIC_USE_DEV_COLORS ? COLORS_DEV : COLORS_PROD;
|
export const COLORS = env.USE_DEV_COLORS ? COLORS_DEV : COLORS_PROD;
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
URL_IUS_PROXY: '/api/ius',
|
|
||||||
};
|
|
||||||
@ -1,12 +1,11 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { envSchema } = require('./config/schema/env');
|
const envSchema = require('./config/schema/env');
|
||||||
const urls = require('./constants/urls');
|
|
||||||
|
|
||||||
const env = envSchema.parse(process.env);
|
const env = envSchema.parse(process.env);
|
||||||
|
|
||||||
const favicons = fs.readdirSync('./public/favicon/prod');
|
const favicons = fs.readdirSync('./public/favicon/prod');
|
||||||
const faviconSubPath = env.NEXT_PUBLIC_USE_DEV_COLORS ? '/favicon/dev' : '/favicon/prod';
|
const faviconSubPath = env.USE_DEV_COLORS ? '/favicon/dev' : '/favicon/prod';
|
||||||
function buildFaviconRewrite(source) {
|
function buildFaviconRewrite(source) {
|
||||||
return {
|
return {
|
||||||
destination: String.prototype.concat(faviconSubPath, source),
|
destination: String.prototype.concat(faviconSubPath, source),
|
||||||
@ -24,17 +23,10 @@ const nextConfig = {
|
|||||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||||
},
|
},
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
transpilePackages: ['@repo/ui', 'radash'],
|
transpilePackages: ['ui'],
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [...favicons.map((fileName) => buildFaviconRewrite(`/${fileName}`))];
|
||||||
{
|
|
||||||
destination: env.URL_IUS_DIRECT + '/:path*',
|
|
||||||
source: urls.URL_IUS_PROXY + '/:path*',
|
|
||||||
},
|
|
||||||
...favicons.map((fileName) => buildFaviconRewrite(`/${fileName}`)),
|
|
||||||
];
|
|
||||||
},
|
},
|
||||||
env,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
|||||||
@ -10,21 +10,20 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@repo/tsconfig": "workspace:*",
|
"next": "^14.0.1",
|
||||||
"@repo/ui": "workspace:*",
|
|
||||||
"next": "^14.1.0",
|
|
||||||
"radash": "^11.0.0",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"tsconfig": "*",
|
||||||
|
"ui": "*",
|
||||||
"wretch": "^2.7.0",
|
"wretch": "^2.7.0",
|
||||||
"zod": "^3.22.4",
|
"zod": "^3.22.4",
|
||||||
"zustand": "^4.5.0"
|
"zustand": "^4.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.6",
|
"@tailwindcss/forms": "^0.5.6",
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.9.0",
|
||||||
"@types/react": "^18.2.39",
|
"@types/react": "^18.2.37",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.15",
|
||||||
"@vchikalkin/eslint-config-awesome": "^1.1.5",
|
"@vchikalkin/eslint-config-awesome": "^1.1.5",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.53.0",
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"background_color": "#fff",
|
|
||||||
"description": "External | Эволюция",
|
|
||||||
"display": "standalone",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"sizes": "192x192",
|
|
||||||
"src": "/android-chrome-192x192.png",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"sizes": "512x512",
|
|
||||||
"src": "/android-chrome-512x512.png",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "External | Эволюция",
|
|
||||||
"short_name": "External | Эволюция",
|
|
||||||
"start_url": "/",
|
|
||||||
"theme_color": "#fff"
|
|
||||||
}
|
|
||||||
@ -12,10 +12,8 @@ type FormState = {
|
|||||||
defaultValues: Values;
|
defaultValues: Values;
|
||||||
init: (values: Values) => void;
|
init: (values: Values) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
resetValidation: () => void;
|
|
||||||
setValidation: (input: { name: string } & ElementValidation) => void;
|
setValidation: (input: { name: string } & ElementValidation) => void;
|
||||||
setValue: ({ name, value }: { name: string; value: Values[number] }) => void;
|
setValue: ({ name, value }: { name: string; value: Values[number] }) => void;
|
||||||
status?: 'init' | 'edited';
|
|
||||||
validation: Record<string, ElementValidation | undefined>;
|
validation: Record<string, ElementValidation | undefined>;
|
||||||
values: Values;
|
values: Values;
|
||||||
};
|
};
|
||||||
@ -25,29 +23,24 @@ export const useFormStore = create<FormState>((set) => ({
|
|||||||
init: (values) =>
|
init: (values) =>
|
||||||
set(() => ({
|
set(() => ({
|
||||||
defaultValues: values,
|
defaultValues: values,
|
||||||
status: 'init',
|
|
||||||
values,
|
values,
|
||||||
})),
|
})),
|
||||||
reset: () =>
|
reset: () => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
status: 'init',
|
|
||||||
validation: {},
|
validation: {},
|
||||||
values: state.defaultValues,
|
values: state.defaultValues,
|
||||||
})),
|
}));
|
||||||
resetValidation: () =>
|
},
|
||||||
set(() => ({
|
setValidation: ({ message, name, valid }) => {
|
||||||
validation: {},
|
|
||||||
})),
|
|
||||||
setValidation: ({ message, name, valid }) =>
|
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
validation: {
|
validation: {
|
||||||
...state.validation,
|
...state.validation,
|
||||||
[name]: { message, valid },
|
[name]: { message, valid },
|
||||||
},
|
},
|
||||||
})),
|
}));
|
||||||
|
},
|
||||||
setValue: ({ name, value }) =>
|
setValue: ({ name, value }) =>
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
status: 'edited',
|
|
||||||
validation: {
|
validation: {
|
||||||
...state.validation,
|
...state.validation,
|
||||||
[name]: undefined,
|
[name]: undefined,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "@repo/tsconfig/nextjs.json",
|
"extends": "tsconfig/nextjs.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"plugins": [{ "name": "next" }],
|
"plugins": [{ "name": "next" }],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
export type PageProps = {
|
export type PageProps = {
|
||||||
params: { slug: string };
|
params: { slug: string };
|
||||||
searchParams: Record<string, string>;
|
searchParams: string | string[][] | Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type * as t from '@/api/ius/types';
|
import type * as t from '@/api/ius/types';
|
||||||
import { HttpError } from '@repo/ui';
|
import { HttpError } from 'ui';
|
||||||
import type { WretchError } from 'wretch/types';
|
import type { WretchError } from 'wretch/types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { PageProps } from '@/types/page';
|
import type { PageProps } from '@/types/page';
|
||||||
|
|
||||||
export function getPageUrlParams({ params, searchParams }: PageProps) {
|
export function getPageUrlParams({ params, searchParams }: PageProps) {
|
||||||
return { path: `/${params.slug}`, urlSearchParams: searchParams };
|
return { path: `/${params.slug}`, urlSearchParams: new URLSearchParams(searchParams) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PageUrlParams = ReturnType<typeof getPageUrlParams>;
|
export type PageUrlParams = ReturnType<typeof getPageUrlParams>;
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
version: '3.3'
|
|
||||||
services:
|
|
||||||
external_client:
|
|
||||||
environment:
|
|
||||||
- URL_IUS_DIRECT=${URL_IUS_DIRECT}
|
|
||||||
- NEXT_PUBLIC_USE_DEV_COLORS=${NEXT_PUBLIC_USE_DEV_COLORS}
|
|
||||||
build:
|
|
||||||
args:
|
|
||||||
- URL_IUS_DIRECT=${URL_IUS_DIRECT}
|
|
||||||
- NEXT_PUBLIC_USE_DEV_COLORS=${NEXT_PUBLIC_USE_DEV_COLORS}
|
|
||||||
context: .
|
|
||||||
dockerfile: ./apps/web/Dockerfile
|
|
||||||
networks:
|
|
||||||
- external_network
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
networks:
|
|
||||||
external_network:
|
|
||||||
external:
|
|
||||||
name: external_network
|
|
||||||
13
package.json
13
package.json
@ -7,7 +7,7 @@
|
|||||||
"lint:fix": "dotenv -e .env.local turbo run lint:fix",
|
"lint:fix": "dotenv -e .env.local turbo run lint:fix",
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"precommit": "pnpm format && pnpm lint:fix",
|
"precommit": "yarn format && yarn lint:fix",
|
||||||
"clean": "turbo run clean"
|
"clean": "turbo run clean"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -17,14 +17,15 @@
|
|||||||
"lint-staged": "^15.0.2",
|
"lint-staged": "^15.0.2",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||||
"@repo/tsconfig": "workspace:*",
|
"tsconfig": "*",
|
||||||
"turbo": "latest"
|
"turbo": "latest"
|
||||||
},
|
},
|
||||||
"name": "Evo.External.App",
|
"name": "Evo.External.App",
|
||||||
"packageManager": "pnpm@8.9.0",
|
"packageManager": "yarn@1.22.19",
|
||||||
"engines": {
|
"workspaces": [
|
||||||
"node": ">=18"
|
"apps/*",
|
||||||
},
|
"packages/*"
|
||||||
|
],
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,jsx,ts,tsx}": [
|
"*.{js,jsx,ts,tsx}": [
|
||||||
"eslint --ignore-pattern **/graphql/* --ext js,jsx,ts,tsx --quiet --fix --"
|
"eslint --ignore-pattern **/graphql/* --ext js,jsx,ts,tsx --quiet --fix --"
|
||||||
|
|||||||
@ -2,19 +2,20 @@
|
|||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
"display": "Default",
|
"display": "Default",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"composite": false,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"incremental": false,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"inlineSources": false,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
"moduleResolution": "node",
|
||||||
"module": "NodeNext",
|
"noUnusedLocals": false,
|
||||||
"moduleDetection": "force",
|
"noUnusedParameters": false,
|
||||||
"moduleResolution": "NodeNext",
|
"preserveWatchOutput": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"target": "ES2022"
|
"strictNullChecks": true
|
||||||
}
|
},
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,18 @@
|
|||||||
"extends": "./base.json",
|
"extends": "./base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"plugins": [{ "name": "next" }],
|
"plugins": [{ "name": "next" }],
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
"declaration": false,
|
||||||
|
"declarationMap": false,
|
||||||
|
"incremental": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"noEmit": true
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
}
|
"module": "esnext",
|
||||||
|
"noEmit": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"strict": false,
|
||||||
|
"target": "es5"
|
||||||
|
},
|
||||||
|
"include": ["src", "next-env.d.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/tsconfig",
|
"name": "tsconfig",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
"display": "React Library",
|
"display": "React Library",
|
||||||
"extends": "./base.json",
|
"extends": "./base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx",
|
||||||
|
"lib": ["ES2015", "DOM"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "es6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { ButtonHTMLAttributes } from 'react';
|
|||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
const variants = cva(
|
const variants = cva(
|
||||||
'rounded-sm h-10 px-5 py-2.5 text-sm font-semibold text-white transition-all ease-in-out',
|
'rounded-sm h-10 px-5 py-2.5 text-sm font-medium text-white transition-all ease-in-out',
|
||||||
{
|
{
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
intent: 'default',
|
intent: 'default',
|
||||||
@ -13,16 +13,14 @@ const variants = cva(
|
|||||||
variants: {
|
variants: {
|
||||||
intent: {
|
intent: {
|
||||||
danger: 'bg-danger hover:bg-danger-700',
|
danger: 'bg-danger hover:bg-danger-700',
|
||||||
default: 'bg-primary hover:bg-primary-600',
|
default: 'bg-primary hover:bg-primary-700',
|
||||||
'outline-danger':
|
'outline-danger':
|
||||||
'bg-transparent text-danger border border-danger hover:bg-danger hover:text-white',
|
'bg-transparent text-danger border border-danger hover:bg-danger hover:text-white',
|
||||||
'outline-default':
|
'outline-default':
|
||||||
'bg-transparent text-primary border border-primary hover:bg-primary hover:text-white',
|
'bg-transparent text-primary border border-primary hover:bg-primary hover:text-white',
|
||||||
'outline-secondary':
|
'outline-secondary':
|
||||||
'border border-primary text-primary-500 hover:bg-primary-500 hover:text-white',
|
'opacity-70 bg-transparent text-secondary border border-secondary hover:bg-secondary hover:text-white',
|
||||||
secondary:
|
secondary: 'bg-secondary hover:bg-secondary-700',
|
||||||
'bg-primary-50 text-primary-500 border border-transparent hover:bg-primary-100 hover:text-primary-600',
|
|
||||||
text: 'bg-none text-primary-500 hover:bg-primary-50',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { forwardRef, type HTMLAttributes, type PropsWithChildren } from 'react';
|
|||||||
|
|
||||||
export type ContainerProps = HTMLAttributes<HTMLDivElement> & PropsWithChildren;
|
export type ContainerProps = HTMLAttributes<HTMLDivElement> & PropsWithChildren;
|
||||||
|
|
||||||
const variants = cva('flex min-h-[36px] items-center', {
|
const variants = cva('flex h-9 items-center', {
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
intent: 'default',
|
intent: 'default',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
export function Content({ children }: PropsWithChildren) {
|
export function Content({ children }: PropsWithChildren) {
|
||||||
return (
|
return <div className="flex w-full justify-center gap-2 py-5">{children}</div>;
|
||||||
<div className="grid w-full place-items-center">
|
|
||||||
<div className="xl:w-standard grid w-screen gap-5">{children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
const variants = cva('font-bold leading-7 text-gray-900 sm:truncate sm:tracking-tight', {
|
const variants = cva('font-bold leading-7 text-gray-900 sm:truncate sm:tracking-tight', {
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
size: 1,
|
size: 'default',
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
size: {
|
size: {
|
||||||
@ -19,6 +19,7 @@ const variants = cva('font-bold leading-7 text-gray-900 sm:truncate sm:tracking-
|
|||||||
7: 'text-6xl sm:text-7xl',
|
7: 'text-6xl sm:text-7xl',
|
||||||
8: 'text-7xl sm:text-8xl',
|
8: 'text-7xl sm:text-8xl',
|
||||||
9: 'text-8xl sm:text-9xl',
|
9: 'text-8xl sm:text-9xl',
|
||||||
|
default: 'text-lg',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,4 +12,3 @@ export * from './http-error';
|
|||||||
export * from './icons';
|
export * from './icons';
|
||||||
export * from './input';
|
export * from './input';
|
||||||
export * from './select';
|
export * from './select';
|
||||||
export * from './textarea';
|
|
||||||
|
|||||||
@ -21,15 +21,3 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
|||||||
export const InputNumber = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
|
export const InputNumber = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
|
||||||
<Input ref={ref} type="number" {...props} />
|
<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 disabled:cursor-not-allowed disabled:opacity-30 disabled:file:cursor-not-allowed"
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@repo/ui",
|
"name": "ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"main": "./index.tsx",
|
||||||
|
"types": "./index.tsx",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
@ -8,15 +10,15 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@turbo/gen": "^1.10.16",
|
"@turbo/gen": "^1.10.16",
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.8.10",
|
||||||
"@types/react": "^18.2.39",
|
"@types/react": "^18.2.34",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.14",
|
||||||
"@vchikalkin/eslint-config-awesome": "^1.1.5",
|
"@vchikalkin/eslint-config-awesome": "^1.1.5",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"tailwind-merge": "^2.0.0",
|
"tailwind-merge": "^2.0.0",
|
||||||
"@repo/tsconfig": "workspace:*",
|
"tsconfig": "*",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
import { cn } from './utils';
|
|
||||||
import type { VariantProps } from 'class-variance-authority';
|
|
||||||
import { cva } from 'class-variance-authority';
|
|
||||||
import { forwardRef } from 'react';
|
|
||||||
|
|
||||||
const variants = cva(
|
|
||||||
'focus:ring-0 min-h-[36px] resize-y h-auto hover:border-primary-500 focus:border-primary-500 w-full rounded-sm border disabled:hover:border-gray-300 border-gray-300 p-2 px-3 text-sm text-gray-900 outline-none transition-colors ease-in-out disabled:cursor-not-allowed disabled:text-opacity-30'
|
|
||||||
);
|
|
||||||
|
|
||||||
export type TextAreaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement> &
|
|
||||||
VariantProps<typeof variants> & { readonly loading?: boolean };
|
|
||||||
|
|
||||||
export const Textarea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
||||||
({ className, loading, ...props }, ref) => {
|
|
||||||
if (loading) return <div className="h-[98px] w-full animate-pulse rounded bg-gray-100" />;
|
|
||||||
|
|
||||||
return <textarea rows={4} ref={ref} className={cn(variants({ className }))} {...props} />;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"extends": "@repo/tsconfig/react-library.json",
|
"extends": "tsconfig/react-library.json",
|
||||||
"include": ["."],
|
"include": ["."],
|
||||||
"exclude": ["dist", "build", "node_modules"]
|
"exclude": ["dist", "build", "node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
7671
pnpm-lock.yaml
generated
7671
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
|||||||
packages:
|
|
||||||
- "apps/*"
|
|
||||||
- "packages/*"
|
|
||||||
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"extends": "@repo/tsconfig/base.json"
|
"extends": "tsconfig/base.json"
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user