Compare commits
39 Commits
d15d761f1a
...
v0.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6705e74ece | ||
| 52bb3790c7 | |||
|
|
2cf60c7b16 | ||
|
|
d30202108c | ||
|
|
012de3c670 | ||
|
|
409c339b3c | ||
|
|
8611405edd | ||
|
|
407da721af | ||
|
|
409473413a | ||
|
|
823e192c51 | ||
|
|
d4965b7aba | ||
|
|
16b4627166 | ||
| 7025c1a31b | |||
| 2d55f26de5 | |||
| da244adee0 | |||
| 5d1a679c68 | |||
| a9738c7d8a | |||
| 6442f1f407 | |||
| 2130532dfd | |||
| 3b4f5a8486 | |||
| b5f3838536 | |||
| dee3a04310 | |||
| acd39b1e32 | |||
| c2fad8a3ff | |||
| 9eb0080d13 | |||
| 920f4440e9 | |||
| e374cbfdd3 | |||
| 0b30ce6571 | |||
| 3b47e1dcdf | |||
| 4e8bc2fca5 | |||
| 0c9663fcef | |||
|
|
181198c0cb | ||
|
|
5ca3fd2613 | ||
|
|
0cd7ec8b22 | ||
|
|
66c1323f00 | ||
| a63304b5e4 | |||
|
|
effd9dae2d | ||
|
|
1787e94f8e | ||
|
|
855ec2f138 |
13
README.md
13
README.md
@@ -45,10 +45,12 @@
|
|||||||
### MVP1
|
### MVP1
|
||||||
|
|
||||||
**1. Landing**
|
**1. Landing**
|
||||||
|
|
||||||
- преимущества сервиса
|
- преимущества сервиса
|
||||||
- оставить заявку (редирект на Страницу оформления заказа)
|
- оставить заявку (редирект на Страницу оформления заказа)
|
||||||
|
|
||||||
**2. Страница для оформления заказа**
|
**2. Страница для оформления заказа**
|
||||||
|
|
||||||
- форма
|
- форма
|
||||||
- номер машины (mask input)
|
- номер машины (mask input)
|
||||||
- цвет машины
|
- цвет машины
|
||||||
@@ -58,10 +60,12 @@
|
|||||||
- после заполнения редирект на страницу с деталями заказа
|
- после заполнения редирект на страницу с деталями заказа
|
||||||
|
|
||||||
**3. Страница с деталями заказа**
|
**3. Страница с деталями заказа**
|
||||||
|
|
||||||
- описание заказа
|
- описание заказа
|
||||||
- детали заказа (id, статус)
|
- детали заказа (id, статус)
|
||||||
|
|
||||||
**3. АРМ оператора**
|
**3. АРМ оператора**
|
||||||
|
|
||||||
- список заказов (RUD)
|
- список заказов (RUD)
|
||||||
- id заказа
|
- id заказа
|
||||||
- статус заказа (готово / не готово)
|
- статус заказа (готово / не готово)
|
||||||
@@ -72,7 +76,6 @@
|
|||||||
- кнопка "Добавить"
|
- кнопка "Добавить"
|
||||||
- кнопка "Удалить"
|
- кнопка "Удалить"
|
||||||
|
|
||||||
|
|
||||||
### Built With
|
### Built With
|
||||||
|
|
||||||
[![React][React.js]][React-url]
|
[![React][React.js]][React-url]
|
||||||
@@ -103,6 +106,14 @@
|
|||||||
|
|
||||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
### Stubs types generation
|
||||||
|
1. generate types with json-literal-typer (should be installed globally)
|
||||||
|
```sh
|
||||||
|
npx json-literal-typer -i <path to json> -o <path to output ts-file>
|
||||||
|
```
|
||||||
|
2. export default type from output file
|
||||||
|
|
||||||
<!-- PARTICIPANTS -->
|
<!-- PARTICIPANTS -->
|
||||||
|
|
||||||
## Participants
|
## Participants
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/* eslint-disable no-undef */
|
/* eslint-disable no-undef */
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const pkg = require("./package");
|
const pkg = require('./package');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
apiPath: "stubs/api",
|
apiPath: 'stubs/api',
|
||||||
webpackConfig: {
|
webpackConfig: {
|
||||||
output: {
|
output: {
|
||||||
publicPath: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`,
|
publicPath: `/static/${pkg.name}/${process.env.VERSION || pkg.version}/`,
|
||||||
@@ -11,17 +11,19 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
/* use https://admin.bro-js.ru/ to create config, navigations and features */
|
/* use https://admin.bro-js.ru/ to create config, navigations and features */
|
||||||
navigations: {
|
navigations: {
|
||||||
"dry-wash.main": "/dry-wash",
|
'dry-wash.main': '/dry-wash',
|
||||||
"dry-wash.create": "/order",
|
'dry-wash.create': '/order',
|
||||||
"dry-wash.view.order": "/order/:orderId",
|
'dry-wash.view.order': '/order/:orderId',
|
||||||
"dry-wash.arm": "/arm",
|
'dry-wash.arm.master': 'master',
|
||||||
|
'dry-wash.arm.order': 'order',
|
||||||
|
'dry-wash.arm': '/arm/*',
|
||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
"dry-wash-pl": {
|
'dry-wash-pl': {
|
||||||
// add your features here in the format [featureName]: { value: string }
|
// add your features here in the format [featureName]: { value: string }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
"dry-wash-pl.api": "/api",
|
'dry-wash-pl.api': '/api',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import pluginJs from "@eslint/js";
|
|||||||
import tseslint from "typescript-eslint";
|
import tseslint from "typescript-eslint";
|
||||||
import pluginReact from "eslint-plugin-react";
|
import pluginReact from "eslint-plugin-react";
|
||||||
import stylistic from '@stylistic/eslint-plugin';
|
import stylistic from '@stylistic/eslint-plugin';
|
||||||
|
import pluginImport from 'eslint-plugin-import';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
|
{ files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
|
||||||
@@ -12,7 +13,8 @@ export default [
|
|||||||
pluginReact.configs.flat.recommended,
|
pluginReact.configs.flat.recommended,
|
||||||
{
|
{
|
||||||
plugins: {
|
plugins: {
|
||||||
'@stylistic': stylistic
|
'@stylistic': stylistic,
|
||||||
|
'import': pluginImport,
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
@@ -24,13 +26,20 @@ export default [
|
|||||||
"caughtErrorsIgnorePattern": "^_"
|
"caughtErrorsIgnorePattern": "^_"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort-imports": ["error", {
|
"sort-imports": ["off"],
|
||||||
"ignoreCase": false,
|
"import/order": [
|
||||||
"ignoreDeclarationSort": true,
|
"error",
|
||||||
"ignoreMemberSort": true,
|
{
|
||||||
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
|
"groups": [
|
||||||
"allowSeparatedGroups": true
|
"builtin",
|
||||||
}],
|
"external",
|
||||||
|
"internal",
|
||||||
|
"parent",
|
||||||
|
["sibling", "index"]
|
||||||
|
],
|
||||||
|
"newlines-between": "always",
|
||||||
|
}
|
||||||
|
],
|
||||||
semi: ["error", "always"],
|
semi: ["error", "always"],
|
||||||
'@stylistic/indent': ['error', 2],
|
'@stylistic/indent': ['error', 2],
|
||||||
'react/prop-types': 'off'
|
'react/prop-types': 'off'
|
||||||
|
|||||||
@@ -13,5 +13,41 @@
|
|||||||
"dry-wash.landing.hero-section.headline": "Revitalize Your Ride with Eco-Friendly Care!",
|
"dry-wash.landing.hero-section.headline": "Revitalize Your Ride with Eco-Friendly Care!",
|
||||||
"dry-wash.landing.make-order-button": "Make order",
|
"dry-wash.landing.make-order-button": "Make order",
|
||||||
"dry-wash.landing.site-logo": "The logo of the \"Dry Master\" company",
|
"dry-wash.landing.site-logo": "The logo of the \"Dry Master\" company",
|
||||||
"dry-wash.landing.social-proof-section.heading": "We are being chosen"
|
"dry-wash.landing.social-proof-section.heading": "We are being chosen",
|
||||||
|
"dry-wash.arm.master.add": "Add",
|
||||||
|
"dry-wash.arm.order.title": "Orders",
|
||||||
|
"dry-wash.arm.order.status.progress": "In Progress",
|
||||||
|
"dry-wash.arm.order.status.complete": "Completed",
|
||||||
|
"dry-wash.arm.order.status.pending": "Pending",
|
||||||
|
"dry-wash.arm.order.status.working": "Working",
|
||||||
|
"dry-wash.arm.order.status.canceled": "Canceled",
|
||||||
|
"dry-wash.arm.order.status.placeholder": "Select Status",
|
||||||
|
"dry-wash.arm.order.table.header.carNumber": "Car Number",
|
||||||
|
"dry-wash.arm.order.table.header.washingTime": "Washing Time",
|
||||||
|
"dry-wash.arm.order.table.header.orderDate": "Order Date",
|
||||||
|
"dry-wash.arm.order.table.header.status": "Status",
|
||||||
|
"dry-wash.arm.order.table.header.telephone": "Telephone",
|
||||||
|
"dry-wash.arm.order.table.header.location": "Location",
|
||||||
|
"dry-wash.arm.master.title": "Masters",
|
||||||
|
"dry-wash.arm.master.table.header.name": "Name",
|
||||||
|
"dry-wash.arm.master.table.header.currentJob": "Current Job",
|
||||||
|
"dry-wash.arm.master.table.header.phone": "Phone",
|
||||||
|
"dry-wash.arm.master.table.header.actions": "Actions",
|
||||||
|
"dry-wash.arm.master.table.actionsMenu.delete": "Delete Master",
|
||||||
|
"dry-wash.arm.master.drawer.title": "Add New Master",
|
||||||
|
"dry-wash.arm.master.drawer.inputName.label": "Full Name",
|
||||||
|
"dry-wash.arm.master.drawer.inputName.placeholder": "Enter Full Name",
|
||||||
|
"dry-wash.arm.master.drawer.inputPhone.label": "Phone Number",
|
||||||
|
"dry-wash.arm.master.drawer.inputPhone.placeholder": "Enter Phone Number",
|
||||||
|
"dry-wash.arm.master.drawer.button.save": "Save",
|
||||||
|
"dry-wash.arm.master.drawer.button.cancel": "Cancel",
|
||||||
|
"dry-wash.arm.master.sideBar.orders": "Orders",
|
||||||
|
"dry-wash.arm.master.sideBar.master": "Masters",
|
||||||
|
"dry-wash.arm.master.sideBar.title": "Dry Master",
|
||||||
|
"dry-wash.notFound.title": "Page Not Found",
|
||||||
|
"dry-wash.notFound.description": "Unfortunately, the page you are looking for does not exist.",
|
||||||
|
"dry-wash.notFound.button.back": "Back to Home",
|
||||||
|
"dry-wash.errorBoundary.title": "Something went wrong",
|
||||||
|
"dry-wash.errorBoundary.description": "We are already working on fixing the issue",
|
||||||
|
"dry-wash.errorBoundary.button.reload": "Reload Page"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"dry-wash.arm.order.title": "Заказы",
|
"dry-wash.arm.order.title": "Заказы",
|
||||||
"dry-wash.arm.order.status.progress": "Выполняется",
|
"dry-wash.arm.order.status.progress": "Выполняется",
|
||||||
"dry-wash.arm.order.status.complete": "Завершено",
|
"dry-wash.arm.order.status.complete": "Завершено",
|
||||||
"dry-wash.arm.order.status.pending": "в ожидании",
|
"dry-wash.arm.order.status.pending": "В ожидании",
|
||||||
"dry-wash.arm.order.status.working": "В работе",
|
"dry-wash.arm.order.status.working": "В работе",
|
||||||
"dry-wash.arm.order.status.canceled": "Отменено",
|
"dry-wash.arm.order.status.canceled": "Отменено",
|
||||||
"dry-wash.arm.order.status.placeholder": "Выберите статус",
|
"dry-wash.arm.order.status.placeholder": "Выберите статус",
|
||||||
@@ -26,9 +26,9 @@
|
|||||||
"dry-wash.arm.master.drawer.inputPhone.placeholder": "Введите номер телефона",
|
"dry-wash.arm.master.drawer.inputPhone.placeholder": "Введите номер телефона",
|
||||||
"dry-wash.arm.master.drawer.button.save": "Сохранить",
|
"dry-wash.arm.master.drawer.button.save": "Сохранить",
|
||||||
"dry-wash.arm.master.drawer.button.cancel": "Отменить",
|
"dry-wash.arm.master.drawer.button.cancel": "Отменить",
|
||||||
|
"dry-wash.arm.master.sideBar.orders": "Заказы",
|
||||||
|
"dry-wash.arm.master.sideBar.master": "Мастера",
|
||||||
"dry-wash.arm.master.sideBar.title": "Сухой мастер",
|
"dry-wash.arm.master.sideBar.title": "Сухой мастер",
|
||||||
"dry-wash.arm.master.sideBar.title.master": "Мастера",
|
|
||||||
"dry-wash.arm.master.sideBar.title.orders": "Заказы",
|
|
||||||
"dry-wash.landing.benefits-section.description": "Откройте для себя преимущества наших услуг по химчистке автомобилей",
|
"dry-wash.landing.benefits-section.description": "Откройте для себя преимущества наших услуг по химчистке автомобилей",
|
||||||
"dry-wash.landing.benefits-section.heading": "Преимущества экологичной автомойки",
|
"dry-wash.landing.benefits-section.heading": "Преимущества экологичной автомойки",
|
||||||
"dry-wash.landing.benefits-section.list.0": "Экологически безопасные продукты",
|
"dry-wash.landing.benefits-section.list.0": "Экологически безопасные продукты",
|
||||||
@@ -49,5 +49,6 @@
|
|||||||
"dry-wash.notFound.button.back": "Вернуться на главную",
|
"dry-wash.notFound.button.back": "Вернуться на главную",
|
||||||
"dry-wash.errorBoundary.title":"Что-то пошло не так",
|
"dry-wash.errorBoundary.title":"Что-то пошло не так",
|
||||||
"dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы",
|
"dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы",
|
||||||
"dry-wash.errorBoundary.button.reload": "Перезагрузить страницу"
|
"dry-wash.errorBoundary.button.reload": "Перезагрузить страницу",
|
||||||
|
"dry-wash.washTime.timeSlot": "{{start}} - {{end}}"
|
||||||
}
|
}
|
||||||
|
|||||||
384
package-lock.json
generated
384
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "dry-wash",
|
"name": "dry-wash",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dry-wash",
|
"name": "dry-wash",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@brojs/cli": "^1.6.3",
|
"@brojs/cli": "^1.6.3",
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"@fontsource/open-sans": "^5.1.0",
|
"@fontsource/open-sans": "^5.1.0",
|
||||||
"@lottiefiles/react-lottie-player": "^3.5.4",
|
"@lottiefiles/react-lottie-player": "^3.5.4",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"framer-motion": "^6.2.8",
|
"framer-motion": "^6.2.8",
|
||||||
"i18next": "^23.16.4",
|
"i18next": "^23.16.4",
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"@stylistic/eslint-plugin": "^2.10.1",
|
"@stylistic/eslint-plugin": "^2.10.1",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-react": "^7.37.2",
|
"eslint-plugin-react": "^7.37.2",
|
||||||
"globals": "^15.11.0",
|
"globals": "^15.11.0",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
@@ -3617,6 +3619,13 @@
|
|||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rtsao/scc": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@stylistic/eslint-plugin": {
|
"node_modules/@stylistic/eslint-plugin": {
|
||||||
"version": "2.10.1",
|
"version": "2.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.10.1.tgz",
|
||||||
@@ -3700,6 +3709,13 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/json5": {
|
||||||
|
"version": "0.0.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||||
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.17.13",
|
"version": "4.17.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
|
||||||
@@ -3791,16 +3807,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.12.2",
|
"version": "8.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz",
|
||||||
"integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==",
|
"integrity": "sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.12.2",
|
"@typescript-eslint/scope-manager": "8.14.0",
|
||||||
"@typescript-eslint/types": "8.12.2",
|
"@typescript-eslint/types": "8.14.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.12.2",
|
"@typescript-eslint/typescript-estree": "8.14.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.12.2",
|
"@typescript-eslint/visitor-keys": "8.14.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3819,6 +3836,117 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
|
||||||
|
"version": "8.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.14.0.tgz",
|
||||||
|
"integrity": "sha512-aBbBrnW9ARIDn92Zbo7rguLnqQ/pOrUguVpbUwzOhkFg2npFDwTgPGqFqE0H5feXcOoJOfX3SxlJaKEVtq54dw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/types": "8.14.0",
|
||||||
|
"@typescript-eslint/visitor-keys": "8.14.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
|
||||||
|
"version": "8.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.14.0.tgz",
|
||||||
|
"integrity": "sha512-yjeB9fnO/opvLJFAsPNYlKPnEM8+z4og09Pk504dkqonT02AyL5Z9SSqlE0XqezS93v6CXn49VHvB2G7XSsl0g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
|
||||||
|
"version": "8.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.14.0.tgz",
|
||||||
|
"integrity": "sha512-OPXPLYKGZi9XS/49rdaCbR5j/S14HazviBlUQFvSKz3npr3NikF+mrgK7CFVur6XEt95DZp/cmke9d5i3vtVnQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/types": "8.14.0",
|
||||||
|
"@typescript-eslint/visitor-keys": "8.14.0",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"fast-glob": "^3.3.2",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"minimatch": "^9.0.4",
|
||||||
|
"semver": "^7.6.0",
|
||||||
|
"ts-api-utils": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
|
||||||
|
"version": "8.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.14.0.tgz",
|
||||||
|
"integrity": "sha512-vG0XZo8AdTH9OE6VFRwAZldNc7qtJ/6NLGWak+BtENuEUXGZgFpihILPiBvKXvJ2nFu27XNGC6rKiwuaoMbYzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/types": "8.14.0",
|
||||||
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@typescript-eslint/parser/node_modules/semver": {
|
||||||
|
"version": "7.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||||
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.12.2",
|
"version": "8.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.12.2.tgz",
|
||||||
@@ -4377,6 +4505,27 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/array.prototype.findlastindex": {
|
||||||
|
"version": "1.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz",
|
||||||
|
"integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.7",
|
||||||
|
"define-properties": "^1.2.1",
|
||||||
|
"es-abstract": "^1.23.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"es-object-atoms": "^1.0.0",
|
||||||
|
"es-shim-unscopables": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/array.prototype.flat": {
|
"node_modules/array.prototype.flat": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
|
||||||
@@ -5156,6 +5305,11 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||||
|
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
@@ -5680,6 +5834,124 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint-import-resolver-node": {
|
||||||
|
"version": "0.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
|
||||||
|
"integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^3.2.7",
|
||||||
|
"is-core-module": "^2.13.0",
|
||||||
|
"resolve": "^1.22.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-import-resolver-node/node_modules/debug": {
|
||||||
|
"version": "3.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
|
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-module-utils": {
|
||||||
|
"version": "2.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
|
||||||
|
"integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^3.2.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"eslint": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-module-utils/node_modules/debug": {
|
||||||
|
"version": "3.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
|
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-import": {
|
||||||
|
"version": "2.31.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
|
||||||
|
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rtsao/scc": "^1.1.0",
|
||||||
|
"array-includes": "^3.1.8",
|
||||||
|
"array.prototype.findlastindex": "^1.2.5",
|
||||||
|
"array.prototype.flat": "^1.3.2",
|
||||||
|
"array.prototype.flatmap": "^1.3.2",
|
||||||
|
"debug": "^3.2.7",
|
||||||
|
"doctrine": "^2.1.0",
|
||||||
|
"eslint-import-resolver-node": "^0.3.9",
|
||||||
|
"eslint-module-utils": "^2.12.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"is-core-module": "^2.15.1",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"minimatch": "^3.1.2",
|
||||||
|
"object.fromentries": "^2.0.8",
|
||||||
|
"object.groupby": "^1.0.3",
|
||||||
|
"object.values": "^1.2.0",
|
||||||
|
"semver": "^6.3.1",
|
||||||
|
"string.prototype.trimend": "^1.0.8",
|
||||||
|
"tsconfig-paths": "^3.15.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-import/node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-import/node_modules/debug": {
|
||||||
|
"version": "3.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||||
|
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/eslint-plugin-import/node_modules/minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eslint-plugin-react": {
|
"node_modules/eslint-plugin-react": {
|
||||||
"version": "7.37.2",
|
"version": "7.37.2",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz",
|
||||||
@@ -6692,20 +6964,6 @@
|
|||||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
|
||||||
"version": "2.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@@ -8351,6 +8609,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object.groupby": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.7",
|
||||||
|
"define-properties": "^1.2.1",
|
||||||
|
"es-abstract": "^1.23.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object.values": {
|
"node_modules/object.values": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
|
||||||
@@ -10148,6 +10421,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/strip-bom": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/strip-json-comments": {
|
"node_modules/strip-json-comments": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||||
@@ -10413,6 +10696,32 @@
|
|||||||
"typescript": ">=4.2.0"
|
"typescript": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tsconfig-paths": {
|
||||||
|
"version": "3.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
||||||
|
"integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/json5": "^0.0.29",
|
||||||
|
"json5": "^1.0.2",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"strip-bom": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"json5": "lib/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.0",
|
"version": "2.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
|
||||||
@@ -10572,6 +10881,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": {
|
||||||
|
"version": "8.12.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.12.2.tgz",
|
||||||
|
"integrity": "sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/scope-manager": "8.12.2",
|
||||||
|
"@typescript-eslint/types": "8.12.2",
|
||||||
|
"@typescript-eslint/typescript-estree": "8.12.2",
|
||||||
|
"@typescript-eslint/visitor-keys": "8.12.2",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"eslint": "^8.57.0 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uglify-js": {
|
"node_modules/uglify-js": {
|
||||||
"version": "3.19.3",
|
"version": "3.19.3",
|
||||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dry-wash",
|
"name": "dry-wash",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"description": "<a id=\"readme-top\"></a>",
|
"description": "<a id=\"readme-top\"></a>",
|
||||||
"main": "./src/index.tsx",
|
"main": "./src/index.tsx",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"@fontsource/open-sans": "^5.1.0",
|
"@fontsource/open-sans": "^5.1.0",
|
||||||
"@lottiefiles/react-lottie-player": "^3.5.4",
|
"@lottiefiles/react-lottie-player": "^3.5.4",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"framer-motion": "^6.2.8",
|
"framer-motion": "^6.2.8",
|
||||||
"i18next": "^23.16.4",
|
"i18next": "^23.16.4",
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
"@stylistic/eslint-plugin": "^2.10.1",
|
"@stylistic/eslint-plugin": "^2.10.1",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"eslint": "^9.14.0",
|
"eslint": "^9.14.0",
|
||||||
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-react": "^7.37.2",
|
"eslint-plugin-react": "^7.37.2",
|
||||||
"globals": "^15.11.0",
|
"globals": "^15.11.0",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
|
|||||||
@@ -1,24 +1,40 @@
|
|||||||
import { generatePath } from "react-router-dom";
|
import { generatePath } from 'react-router-dom';
|
||||||
import { getNavigationValue } from "@brojs/cli";
|
import { getNavigationValue } from '@brojs/cli';
|
||||||
import { Order } from "../models";
|
|
||||||
|
import { Order } from '../models';
|
||||||
|
|
||||||
|
const getFullUrls = (url: string) =>
|
||||||
|
`${getNavigationValue('dry-wash.main')}${url}`;
|
||||||
|
|
||||||
export const URLs = {
|
export const URLs = {
|
||||||
landing: {
|
landing: {
|
||||||
url: getNavigationValue("dry-wash.main"),
|
url: getNavigationValue('dry-wash.main'),
|
||||||
getUrl() {
|
getUrl() {
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderForm: {
|
orderForm: {
|
||||||
url: getNavigationValue("dry-wash.create"),
|
url: getNavigationValue('dry-wash.create'),
|
||||||
getUrl() {
|
getUrl() {
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
orderView: {
|
orderView: {
|
||||||
url: getNavigationValue("dry-wash.view.order"),
|
url: getNavigationValue('dry-wash.view.order'),
|
||||||
getUrl(orderId: Order.Id) {
|
getUrl(orderId: Order.Id) {
|
||||||
return generatePath(this.url, { orderId });
|
return generatePath(this.url, { orderId });
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
armMaster: {
|
||||||
|
url: getNavigationValue('dry-wash.arm.master'),
|
||||||
|
isOn: Boolean(getNavigationValue('dry-wash.arm.master')),
|
||||||
|
},
|
||||||
|
armOrder: {
|
||||||
|
url: getNavigationValue('dry-wash.arm.order'),
|
||||||
|
isOn: Boolean(getNavigationValue('dry-wash.arm.order')),
|
||||||
|
},
|
||||||
|
armBase: {
|
||||||
|
url: getFullUrls(getNavigationValue('dry-wash.arm')),
|
||||||
|
isOn: Boolean(getNavigationValue('dry-wash.arm')),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import Routers from './routes';
|
|
||||||
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
|
import { ChakraProvider, theme as chakraTheme } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
import Routers from './routes';
|
||||||
import ErrorBoundary from './components/ErrorBoundary';
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|||||||
@@ -34,15 +34,16 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|||||||
render() {
|
render() {
|
||||||
const { hasError } = this.state;
|
const { hasError } = this.state;
|
||||||
//TODO: добавить анимацию после залива 404 страницы
|
//TODO: добавить анимацию после залива 404 страницы
|
||||||
|
//TODO: может сделать обертку для хука, чтоб язык менялся без перезагрузки
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
return (
|
return (
|
||||||
<Center minH='100vh'>
|
<Center minH='100vh'>
|
||||||
<VStack spacing={4} textAlign='center'>
|
<VStack spacing={4} textAlign='center'>
|
||||||
<Heading as='h1' size='2xl'>
|
<Heading as='h1' size='2xl'>
|
||||||
{i18next.t('dry-wash.errorBoundary.title')}
|
{i18next.t('~:dry-wash.errorBoundary.title')}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text fontSize='lg'>
|
<Text fontSize='lg'>
|
||||||
{i18next.t('dry-wash.errorBoundary.description')}
|
{i18next.t('~:dry-wash.errorBoundary.description')}
|
||||||
</Text>
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
colorScheme='teal'
|
colorScheme='teal'
|
||||||
@@ -50,7 +51,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|||||||
variant='outline'
|
variant='outline'
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
>
|
>
|
||||||
{i18next.t('dry-wash.errorBoundary.button.reload')}
|
{i18next.t('~:dry-wash.errorBoundary.button.reload')}
|
||||||
</Button>
|
</Button>
|
||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
|
|||||||
@@ -1,23 +1,37 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import Sidebar from '../Sidebar';
|
|
||||||
import Orders from '../Orders';
|
|
||||||
import Masters from '../Masters';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
const LayoutArm = () => (
|
import Sidebar from '../Sidebar';
|
||||||
|
import Orders from '../Orders';
|
||||||
|
import Masters from '../Masters';
|
||||||
|
import { URLs } from '../../__data__/urls';
|
||||||
|
|
||||||
|
const LayoutArm = () => {
|
||||||
|
let defaultRedirect = null;
|
||||||
|
|
||||||
|
if (URLs.armOrder.isOn) {
|
||||||
|
defaultRedirect = URLs.armOrder.url;
|
||||||
|
} else if (URLs.armMaster.isOn) {
|
||||||
|
defaultRedirect = URLs.armMaster.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Flex h='100vh'>
|
<Flex h='100vh'>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<Box flex='1' bg='gray.50'>
|
<Box flex='1' bg='gray.50'>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route>
|
<Route index element={<Navigate to={defaultRedirect} replace />} />
|
||||||
<Route index element={<Navigate to='orders' replace />} />
|
{URLs.armOrder.isOn && (
|
||||||
<Route path='orders' element={<Orders />} />
|
<Route path={URLs.armOrder.url} element={<Orders />} />
|
||||||
<Route path='masters' element={<Masters />} />
|
)}
|
||||||
</Route>
|
{URLs.armMaster.isOn && (
|
||||||
|
<Route path={URLs.armMaster.url} element={<Masters />} />
|
||||||
|
)}
|
||||||
</Routes>
|
</Routes>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default LayoutArm;
|
export default LayoutArm;
|
||||||
|
|||||||
@@ -7,16 +7,18 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { EditIcon } from '@chakra-ui/icons';
|
import { EditIcon } from '@chakra-ui/icons';
|
||||||
import i18next from 'i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const MasterActionsMenu = () => {
|
const MasterActionsMenu = () => {
|
||||||
|
const { t } = useTranslation('~', {
|
||||||
|
keyPrefix: 'dry-wash.arm.master.table.actionsMenu',
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton icon={<EditIcon />} as={IconButton} variant='outline' />
|
<MenuButton icon={<EditIcon />} as={IconButton} variant='outline' />
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem>
|
<MenuItem>{t('delete')}</MenuItem>
|
||||||
{i18next.t('dry-wash.arm.master.table.actionsMenu.delete')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
DrawerHeader,
|
DrawerHeader,
|
||||||
DrawerOverlay,
|
DrawerOverlay,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import i18next from 'i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const MasterDrawer = ({ isOpen, onClose }) => {
|
const MasterDrawer = ({ isOpen, onClose }) => {
|
||||||
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
|
const [newMaster, setNewMaster] = useState({ name: '', phone: '' });
|
||||||
@@ -22,51 +22,44 @@ const MasterDrawer = ({ isOpen, onClose }) => {
|
|||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { t } = useTranslation('~', {
|
||||||
|
keyPrefix: 'dry-wash.arm.master.drawer',
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer isOpen={isOpen} onClose={onClose} size='md'>
|
<Drawer isOpen={isOpen} onClose={onClose} size='md'>
|
||||||
<DrawerOverlay />
|
<DrawerOverlay />
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<DrawerCloseButton />
|
<DrawerCloseButton />
|
||||||
<DrawerHeader>
|
<DrawerHeader>{t('title')}</DrawerHeader>
|
||||||
{i18next.t('dry-wash.arm.master.drawer.title')}
|
|
||||||
</DrawerHeader>
|
|
||||||
<DrawerBody>
|
<DrawerBody>
|
||||||
<FormControl mb='4'>
|
<FormControl mb='4'>
|
||||||
<FormLabel>
|
<FormLabel>{t('inputName.label')}</FormLabel>
|
||||||
{i18next.t('dry-wash.arm.master.drawer.inputName.label')}
|
|
||||||
</FormLabel>
|
|
||||||
<Input
|
<Input
|
||||||
value={newMaster.name}
|
value={newMaster.name}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setNewMaster({ ...newMaster, name: e.target.value })
|
setNewMaster({ ...newMaster, name: e.target.value })
|
||||||
}
|
}
|
||||||
placeholder={i18next.t(
|
placeholder={t('inputName.placeholder')}
|
||||||
'dry-wash.arm.master.drawer.inputName.placeholder',
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>
|
<FormLabel> {t('inputPhone.label')}</FormLabel>
|
||||||
{' '}
|
|
||||||
{i18next.t('dry-wash.arm.master.drawer.inputPhone.label')}
|
|
||||||
</FormLabel>
|
|
||||||
<Input
|
<Input
|
||||||
value={newMaster.phone}
|
value={newMaster.phone}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setNewMaster({ ...newMaster, phone: e.target.value })
|
setNewMaster({ ...newMaster, phone: e.target.value })
|
||||||
}
|
}
|
||||||
placeholder={i18next.t(
|
placeholder={t('inputPhone.placeholder')}
|
||||||
'dry-wash.arm.master.drawer.inputPhone.placeholder',
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</DrawerBody>
|
</DrawerBody>
|
||||||
<DrawerFooter>
|
<DrawerFooter>
|
||||||
<Button colorScheme='teal' mr={3} onClick={handleSave}>
|
<Button colorScheme='teal' mr={3} onClick={handleSave}>
|
||||||
{i18next.t('dry-wash.arm.master.drawer.button.save')}
|
{t('button.save')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant='ghost' onClick={onClose}>
|
<Button variant='ghost' onClick={onClose}>
|
||||||
{i18next.t('dry-wash.arm.master.drawer.button.cancel')}
|
{t('button.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</DrawerFooter>
|
</DrawerFooter>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react';
|
import { Badge, Link, Stack, Td, Tr } from '@chakra-ui/react';
|
||||||
|
|
||||||
import MasterActionsMenu from '../MasterActionsMenu';
|
import MasterActionsMenu from '../MasterActionsMenu';
|
||||||
|
import { getTimeSlot } from '../../lib/date-helpers';
|
||||||
|
|
||||||
const MasterItem = ({ name, schedule, phone }) => {
|
const MasterItem = ({ name, schedule, phone }) => {
|
||||||
return (
|
return (
|
||||||
@@ -8,9 +10,9 @@ const MasterItem = ({ name, schedule, phone }) => {
|
|||||||
<Td>{name}</Td>
|
<Td>{name}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Stack direction='row'>
|
<Stack direction='row'>
|
||||||
{schedule.map((time, index) => (
|
{schedule.map(({ startWashTime, endWashTime }, index) => (
|
||||||
<Badge colorScheme={'green'} key={index}>
|
<Badge colorScheme={'green'} key={index}>
|
||||||
{time}
|
{getTimeSlot(startWashTime, endWashTime)}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -11,36 +11,44 @@ import {
|
|||||||
useDisclosure,
|
useDisclosure,
|
||||||
Flex,
|
Flex,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { mastersData } from '../../mocks';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import MasterItem from '../MasterItem';
|
import MasterItem from '../MasterItem';
|
||||||
import MasterDrawer from '../MasterDrawer';
|
import MasterDrawer from '../MasterDrawer';
|
||||||
import i18next from 'i18next';
|
import data from '../../../stubs/json/arm-masters/success.json';
|
||||||
|
|
||||||
const TABLE_HEADERS = ['name', 'currentJob', 'phone', 'actions'];
|
const TABLE_HEADERS = [
|
||||||
|
'name' as const,
|
||||||
|
'currentJob' as const,
|
||||||
|
'phone' as const,
|
||||||
|
'actions' as const,
|
||||||
|
];
|
||||||
|
|
||||||
const Masters = () => {
|
const Masters = () => {
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
const { t } = useTranslation('~', {
|
||||||
|
keyPrefix: 'dry-wash.arm.master',
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box p='8'>
|
<Box p='8'>
|
||||||
<Flex justifyContent='space-between' alignItems='center' mb='5'>
|
<Flex justifyContent='space-between' alignItems='center' mb='5'>
|
||||||
<Heading size='lg'> {i18next.t('dry-wash.arm.master.title')}</Heading>
|
<Heading size='lg'> {t('title')}</Heading>
|
||||||
<Button colorScheme='green' onClick={onOpen}>
|
<Button colorScheme='green' onClick={onOpen}>
|
||||||
+ {i18next.t('dry-wash.arm.master.add')}
|
+ {t('add')}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Table variant='simple' colorScheme='blackAlpha'>
|
<Table variant='simple' colorScheme='blackAlpha'>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
{TABLE_HEADERS.map((name) => (
|
{TABLE_HEADERS.map((name) => (
|
||||||
<Th key={name}>
|
<Th key={name}>{t(`table.header.${name}`)}</Th>
|
||||||
{i18next.t(`dry-wash.arm.master.table.header.${name}`)}
|
|
||||||
</Th>
|
|
||||||
))}
|
))}
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{mastersData.map((master, index) => (
|
{data.body.map((master, index) => (
|
||||||
<MasterItem key={index} {...master} />
|
<MasterItem key={index} {...master} />
|
||||||
))}
|
))}
|
||||||
</Tbody>
|
</Tbody>
|
||||||
|
|||||||
@@ -1,33 +1,60 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Td, Tr, Link, Select } from '@chakra-ui/react';
|
import { Td, Tr, Link, Select } from '@chakra-ui/react';
|
||||||
import i18next from 'i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const statuses = ['pending', 'progress', 'working', 'canceled', 'complete'];
|
import { getTimeSlot } from '../../lib/date-helpers';
|
||||||
|
|
||||||
|
const statuses = [
|
||||||
|
'pending' as const,
|
||||||
|
'progress' as const,
|
||||||
|
'working' as const,
|
||||||
|
'canceled' as const,
|
||||||
|
'complete' as const,
|
||||||
|
];
|
||||||
|
|
||||||
|
type GetArrItemType<ArrType> =
|
||||||
|
ArrType extends Array<infer ItemType> ? ItemType : never;
|
||||||
|
|
||||||
|
export type OrderProps = {
|
||||||
|
carNumber?: string;
|
||||||
|
startWashTime?: string;
|
||||||
|
endWashTime?: string;
|
||||||
|
orderDate?: string;
|
||||||
|
status?: GetArrItemType<typeof statuses>;
|
||||||
|
phone?: string;
|
||||||
|
location?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const OrderItem = ({
|
const OrderItem = ({
|
||||||
carNumber,
|
carNumber,
|
||||||
washTime,
|
startWashTime,
|
||||||
|
endWashTime,
|
||||||
orderDate,
|
orderDate,
|
||||||
status,
|
status,
|
||||||
phone,
|
phone,
|
||||||
location,
|
location,
|
||||||
}) => {
|
}: OrderProps) => {
|
||||||
|
const { t } = useTranslation('~', {
|
||||||
|
keyPrefix: 'dry-wash.arm.order',
|
||||||
|
});
|
||||||
|
|
||||||
const [statusSelect, setStatus] = useState(status);
|
const [statusSelect, setStatus] = useState(status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr>
|
<Tr>
|
||||||
<Td>{carNumber}</Td>
|
<Td>{carNumber}</Td>
|
||||||
<Td>{washTime}</Td>
|
<Td>{getTimeSlot(startWashTime, endWashTime)}</Td>
|
||||||
<Td>{orderDate}</Td>
|
<Td>{dayjs(orderDate).format('DD.MM.YYYY')}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Select
|
<Select
|
||||||
value={statusSelect}
|
value={statusSelect}
|
||||||
onChange={(e) => setStatus(e.target.value)}
|
onChange={(e) => setStatus(e.target.value as OrderProps['status'])}
|
||||||
placeholder={i18next.t(`dry-wash.arm.order.status.placeholder`)}
|
placeholder={t(`status.placeholder`)}
|
||||||
>
|
>
|
||||||
{statuses.map((status) => (
|
{statuses.map((status) => (
|
||||||
<option key={status} value={status}>
|
<option key={status} value={status}>
|
||||||
{i18next.t(`dry-wash.arm.order.status.${status}`)}
|
{t(`status.${status}`)}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -1,35 +1,45 @@
|
|||||||
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ordersData } from '../../mocks';
|
import { Box, Heading, Table, Thead, Tbody, Tr, Th } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import OrderItem from '../OrderItem';
|
import OrderItem from '../OrderItem';
|
||||||
import i18next from 'i18next';
|
import { OrderProps } from '../OrderItem/OrderItem';
|
||||||
|
import data from '../../../stubs/json/arm-orders/success.json';
|
||||||
|
|
||||||
const Orders = () => {
|
const Orders = () => {
|
||||||
|
const { t } = useTranslation('~', {
|
||||||
|
keyPrefix: 'dry-wash.arm.order',
|
||||||
|
});
|
||||||
|
|
||||||
const TABLE_HEADERS = [
|
const TABLE_HEADERS = [
|
||||||
'carNumber',
|
'carNumber' as const,
|
||||||
'washingTime',
|
'washingTime' as const,
|
||||||
'orderDate',
|
'orderDate' as const,
|
||||||
'status',
|
'status' as const,
|
||||||
'telephone',
|
'telephone' as const,
|
||||||
'location',
|
'location' as const,
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box p='8'>
|
<Box p='8'>
|
||||||
<Heading size='lg' mb='5'>
|
<Heading size='lg' mb='5'>
|
||||||
{i18next.t('dry-wash.arm.order.title')}
|
{t('title')}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Table variant='simple' colorScheme='blackAlpha'>
|
<Table variant='simple' colorScheme='blackAlpha'>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
{TABLE_HEADERS.map((name, key) => (
|
{TABLE_HEADERS.map((name, key) => (
|
||||||
<Th key={key}>
|
<Th key={key}>{t(`table.header.${name}`)}</Th>
|
||||||
{i18next.t(`dry-wash.arm.order.table.header.${name}`)}
|
|
||||||
</Th>
|
|
||||||
))}
|
))}
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
{ordersData.map((order, index) => (
|
{data.body.map((order, index) => (
|
||||||
<OrderItem key={index} {...order} />
|
<OrderItem
|
||||||
|
key={index}
|
||||||
|
{...order}
|
||||||
|
status={order.status as OrderProps['status']}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import { Box, Button, Heading, VStack } from '@chakra-ui/react';
|
import { Box, Button, Heading, VStack, Divider } from '@chakra-ui/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Divider } from '@chakra-ui/react';
|
import { useLocation, Link } from 'react-router-dom';
|
||||||
import i18next from 'i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
const Sidebar = () => (
|
import { URLs } from '../../__data__/urls';
|
||||||
|
|
||||||
|
const Sidebar = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const isActive = (keyword: string) => location.pathname.includes(keyword);
|
||||||
|
|
||||||
|
const { t } = useTranslation('~', {
|
||||||
|
keyPrefix: 'dry-wash.arm.master.sideBar',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
<Box
|
<Box
|
||||||
borderRight='1px solid black'
|
borderRight='1px solid black'
|
||||||
bg='gray.50'
|
bg='gray.50'
|
||||||
@@ -14,33 +23,38 @@ const Sidebar = () => (
|
|||||||
pt='8'
|
pt='8'
|
||||||
>
|
>
|
||||||
<Heading color='green' size='lg' mb='5'>
|
<Heading color='green' size='lg' mb='5'>
|
||||||
{i18next.t(`dry-wash.arm.master.sideBar.title`)}
|
{t('title')}
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<VStack align='start' spacing='4'>
|
<VStack align='start' spacing='4'>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
{URLs.armOrder.isOn && (
|
||||||
<Button
|
<Button
|
||||||
as={Link}
|
as={Link}
|
||||||
to='orders'
|
to={URLs.armOrder.url}
|
||||||
w='100%'
|
w='100%'
|
||||||
colorScheme='green'
|
colorScheme={isActive(URLs.armOrder.url) ? 'green' : 'blue'}
|
||||||
variant='ghost'
|
variant={isActive(URLs.armOrder.url) ? 'solid' : 'ghost'}
|
||||||
>
|
>
|
||||||
{i18next.t(`dry-wash.arm.master.sideBar.title.orders`)}
|
{t('orders')}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
|
{URLs.armMaster.isOn && (
|
||||||
<Button
|
<Button
|
||||||
as={Link}
|
as={Link}
|
||||||
to='masters'
|
to={URLs.armMaster.url}
|
||||||
w='100%'
|
w='100%'
|
||||||
colorScheme='green'
|
colorScheme={isActive(URLs.armMaster.url) ? 'green' : 'blue'}
|
||||||
variant='ghost'
|
variant={isActive(URLs.armMaster.url) ? 'solid' : 'ghost'}
|
||||||
>
|
>
|
||||||
{i18next.t(`dry-wash.arm.master.sideBar.title.master`)}
|
{t('master')}
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
</VStack>
|
</VStack>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|||||||
@@ -1,50 +1,29 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
|
||||||
MdEco,
|
|
||||||
MdMiscellaneousServices,
|
|
||||||
MdPlace,
|
|
||||||
MdHandshake,
|
|
||||||
} from 'react-icons/md';
|
|
||||||
import { Heading, HStack, List, Text, VStack } from '@chakra-ui/react';
|
import { Heading, HStack, List, Text, VStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { CtaButton, PageSection } from '../';
|
import { CtaButton, PageSection } from '../';
|
||||||
|
|
||||||
import { ListItem } from './ListItem';
|
import { ListItem } from './ListItem';
|
||||||
|
import { BenefitsSectionProps } from './types';
|
||||||
|
import { iconsMap } from './helper';
|
||||||
|
|
||||||
export const BenefitsSection: FC = () => {
|
export const BenefitsSection: FC<BenefitsSectionProps> = ({
|
||||||
const { t } = useTranslation('~', {
|
data: { heading, description, list } = {},
|
||||||
keyPrefix: 'dry-wash.landing.benefits-section',
|
}) => {
|
||||||
});
|
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
||||||
|
|
||||||
const listData = [
|
|
||||||
{
|
|
||||||
Icon: MdEco,
|
|
||||||
children: t('list.0'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Icon: MdMiscellaneousServices,
|
|
||||||
children: t('list.1'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Icon: MdPlace,
|
|
||||||
children: t('list.2'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Icon: MdHandshake,
|
|
||||||
children: t('list.3'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<VStack w='full' spacing={2}>
|
<VStack w='full' spacing={2}>
|
||||||
<Heading as='h2'>{t('heading')}</Heading>
|
<Heading as='h2'>{t(heading)}</Heading>
|
||||||
<Text>
|
<Text>{t(description)}</Text>
|
||||||
{t('description')}
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
<List display='flex' flexDirection='column' spacing={3}>
|
<List display='flex' flexDirection='column' spacing={3}>
|
||||||
{listData.map((props, i) => (
|
{list.map((itemKey, i) => (
|
||||||
<ListItem key={i} {...props} />
|
<ListItem key={i} Icon={iconsMap[itemKey]}>
|
||||||
|
{t(itemKey)}
|
||||||
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
<HStack w='full' justify='flex-end'>
|
<HStack w='full' justify='flex-end'>
|
||||||
|
|||||||
13
src/components/landing/BenefitsSection/helper.ts
Normal file
13
src/components/landing/BenefitsSection/helper.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { IconType } from "react-icons";
|
||||||
|
import { MdEco, MdMiscellaneousServices, MdPlace, MdHandshake } from "react-icons/md";
|
||||||
|
|
||||||
|
import { ArrElement } from "../../../lib";
|
||||||
|
|
||||||
|
import { BenefitsList } from "./types";
|
||||||
|
|
||||||
|
export const iconsMap: Record<ArrElement<BenefitsList>, IconType> = {
|
||||||
|
"benefits-section.list.0": MdEco,
|
||||||
|
"benefits-section.list.1": MdMiscellaneousServices,
|
||||||
|
"benefits-section.list.2": MdPlace,
|
||||||
|
"benefits-section.list.3": MdHandshake,
|
||||||
|
};
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
export type { BenefitsSectionProps } from './types';
|
||||||
export { BenefitsSection } from './BenefitsSection';
|
export { BenefitsSection } from './BenefitsSection';
|
||||||
14
src/components/landing/BenefitsSection/types.ts
Normal file
14
src/components/landing/BenefitsSection/types.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export type BenefitsList = [
|
||||||
|
'benefits-section.list.0',
|
||||||
|
'benefits-section.list.1',
|
||||||
|
'benefits-section.list.2',
|
||||||
|
'benefits-section.list.3',
|
||||||
|
];
|
||||||
|
|
||||||
|
export type BenefitsSectionProps = {
|
||||||
|
data: {
|
||||||
|
heading: 'benefits-section.heading';
|
||||||
|
description: 'benefits-section.description';
|
||||||
|
list: BenefitsList;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -2,10 +2,11 @@ import React, { FC } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { ButtonProps, Button } from '@chakra-ui/react';
|
import { ButtonProps, Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { URLs } from '../../../__data__/urls';
|
import { URLs } from '../../../__data__/urls';
|
||||||
|
|
||||||
export const CtaButton: FC<ButtonProps> = (props) => {
|
export const CtaButton: FC<ButtonProps> = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -14,7 +15,7 @@ export const CtaButton: FC<ButtonProps> = (props) => {
|
|||||||
colorScheme='primary'
|
colorScheme='primary'
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{t('dry-wash.landing.make-order-button')}
|
{t('make-order-button')}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import React, { FC } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, List, ListItem } from '@chakra-ui/react';
|
import { Link, List, ListItem } from '@chakra-ui/react';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
|
|
||||||
import { SiteLogo, PageSection } from '../';
|
import { SiteLogo, PageSection } from '../';
|
||||||
|
|
||||||
import { Copyright } from './Copyright';
|
import { Copyright } from './Copyright';
|
||||||
|
|
||||||
export const Footer: FC = () => {
|
export const Footer: FC = () => {
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Box, Heading, Text, Center, VStack, BoxProps } from '@chakra-ui/react';
|
import { Box, Heading, Text, Center, VStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { DemoVideoPosterImg } from '../../../assets/images';
|
import { DemoVideoPosterImg } from '../../../assets/images';
|
||||||
import { CtaButton, SiteLogo, PageSection } from '../';
|
import { CtaButton, SiteLogo, PageSection } from '../';
|
||||||
|
|
||||||
type HeroSectionProps = Pick<BoxProps, 'flexShrink'>;
|
import { HeroSectionProps } from './types';
|
||||||
|
|
||||||
export const HeroSection: FC<HeroSectionProps> = ({ flexShrink }) => {
|
export const HeroSection: FC<HeroSectionProps> = ({
|
||||||
const { t } = useTranslation('~', {
|
data: { headline, description, video } = {},
|
||||||
keyPrefix: 'dry-wash.landing.hero-section',
|
flexShrink,
|
||||||
});
|
}) => {
|
||||||
|
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexShrink={flexShrink} as='header' pos='relative' zIndex={0}>
|
<Box flexShrink={flexShrink} as='header' pos='relative' zIndex={0}>
|
||||||
<Box
|
<Box
|
||||||
as='video'
|
as='video'
|
||||||
src={`${__webpack_public_path__}/remote-assets/demo.mp4`}
|
src={`${__webpack_public_path__}/remote-assets/${video}`}
|
||||||
poster={DemoVideoPosterImg}
|
poster={DemoVideoPosterImg}
|
||||||
autoPlay
|
autoPlay
|
||||||
loop
|
loop
|
||||||
@@ -46,14 +48,14 @@ export const HeroSection: FC<HeroSectionProps> = ({ flexShrink }) => {
|
|||||||
color='white'
|
color='white'
|
||||||
__css={{ textWrap: 'balance' }}
|
__css={{ textWrap: 'balance' }}
|
||||||
>
|
>
|
||||||
{t('headline')}
|
{t(headline)}
|
||||||
</Heading>
|
</Heading>
|
||||||
<Text
|
<Text
|
||||||
textAlign='center'
|
textAlign='center'
|
||||||
__css={{ textWrap: 'balance' }}
|
__css={{ textWrap: 'balance' }}
|
||||||
color='white'
|
color='white'
|
||||||
>
|
>
|
||||||
{t('description')}
|
{t(description)}
|
||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
<CtaButton size='lg' />
|
<CtaButton size='lg' />
|
||||||
|
|||||||
9
src/components/landing/HeroSection/types.ts
Normal file
9
src/components/landing/HeroSection/types.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { BoxProps } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
export type HeroSectionProps = {
|
||||||
|
data: {
|
||||||
|
headline: 'hero-section.headline';
|
||||||
|
description: 'hero-section.description';
|
||||||
|
video: string;
|
||||||
|
};
|
||||||
|
} & Pick<BoxProps, 'flexShrink'>;
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Image } from '@chakra-ui/react';
|
import { Image } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { LogoSvg } from '../../../assets/icons';
|
import { LogoSvg } from '../../../assets/icons';
|
||||||
|
|
||||||
export const SiteLogo: FC = () => {
|
export const SiteLogo: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return <Image src={LogoSvg} alt={t('dry-wash.landing.site-logo')} w={40} />;
|
return <Image src={LogoSvg} alt={t('~:dry-wash.landing.site-logo')} w={40} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo: replace Image by SVG React component
|
// todo: replace Image by SVG React component
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import {
|
|||||||
Tabs,
|
Tabs,
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
import { mockReview } from '../../../../mocks/landing';
|
import { mockReview } from '../../../../mocks/landing';
|
||||||
import { Review } from '../../../../models';
|
import { Review } from '../../../../models';
|
||||||
|
|
||||||
import { ReviewCard } from './ReviewCard';
|
import { ReviewCard } from './ReviewCard';
|
||||||
|
|
||||||
const SLIDE_CHANGE_INTERVAL = 5000;
|
const SLIDE_CHANGE_INTERVAL = 5000;
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Heading, HStack } from '@chakra-ui/react';
|
import { Heading, HStack } from '@chakra-ui/react';
|
||||||
import { CtaButton, PageSection } from '../';
|
|
||||||
import { ReviewsSlider } from './ReviewsSlider';
|
|
||||||
|
|
||||||
export const SocialProofSection: FC = () => {
|
import { CtaButton, PageSection } from '../';
|
||||||
const { t } = useTranslation('~', {
|
|
||||||
keyPrefix: 'dry-wash.landing.social-proof-section',
|
import { ReviewsSlider } from './ReviewsSlider';
|
||||||
});
|
import { SocialProofSectionProps } from './types';
|
||||||
|
|
||||||
|
export const SocialProofSection: FC<SocialProofSectionProps> = ({
|
||||||
|
data: { heading } = {},
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation('~', { keyPrefix: 'dry-wash.landing' });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<Heading as='h2'>{t('heading')}</Heading>
|
<Heading as='h2'>{t(heading)}</Heading>
|
||||||
<ReviewsSlider />
|
<ReviewsSlider />
|
||||||
<HStack w='full' justify='flex-end'>
|
<HStack w='full' justify='flex-end'>
|
||||||
<CtaButton />
|
<CtaButton />
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
export type { SocialProofSectionProps } from './types';
|
||||||
export { SocialProofSection } from './SocialProofSection';
|
export { SocialProofSection } from './SocialProofSection';
|
||||||
5
src/components/landing/SocialProofSection/types.ts
Normal file
5
src/components/landing/SocialProofSection/types.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type SocialProofSectionProps = {
|
||||||
|
data: {
|
||||||
|
heading: 'social-proof-section.heading';
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { FC, PropsWithChildren } from 'react';
|
import React, { FC, PropsWithChildren } from 'react';
|
||||||
import { ChakraProvider } from '@chakra-ui/react';
|
import { ChakraProvider } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { default as landingTheme } from './theme-config';
|
import { default as landingTheme } from './theme-config';
|
||||||
import Fonts from './Fonts';
|
import Fonts from './Fonts';
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { i18nextReactInitConfig } from '@brojs/cli';
|
import { i18nextReactInitConfig } from '@brojs/cli';
|
||||||
import App from './app';
|
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
|
import App from './app';
|
||||||
|
|
||||||
i18next.t = i18next.t.bind(i18next);
|
i18next.t = i18next.t.bind(i18next);
|
||||||
const i18nextPromise = i18nextReactInitConfig(i18next);
|
const i18nextPromise = i18nextReactInitConfig(i18next);
|
||||||
export default () => <App />;
|
export default () => <App />;
|
||||||
|
|||||||
12
src/lib/date-helpers.ts
Normal file
12
src/lib/date-helpers.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import i18next from 'i18next';
|
||||||
|
|
||||||
|
export const getTimeSlot = (start: string, end: string) => {
|
||||||
|
const startDay = dayjs(start).format('HH:mm');
|
||||||
|
const endDay = dayjs(end).format('HH:mm');
|
||||||
|
|
||||||
|
return i18next.t('~:dry-wash.washTime.timeSlot', {
|
||||||
|
start: startDay,
|
||||||
|
end: endDay,
|
||||||
|
});
|
||||||
|
};
|
||||||
1
src/lib/index.ts
Normal file
1
src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './types';
|
||||||
41
src/lib/types.ts
Normal file
41
src/lib/types.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @example type Output = ArrElement<['a', 'b', 'c']>;
|
||||||
|
* // "a" | "b" | "c"
|
||||||
|
*/
|
||||||
|
export type ArrElement<ArrType> = ArrType extends readonly (infer ElementType)[]
|
||||||
|
? ElementType
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example type Output = Split<'a.b1' | 'a.b2', '.'>;
|
||||||
|
* // ["a", "b1"] | ["a", "b2"]
|
||||||
|
*/
|
||||||
|
type Split<S extends string, D extends string> =
|
||||||
|
S extends `${infer A}${D}${infer B}` ? [A, ...Split<B, D>] : [S];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example type Output = NestedObject<["a", "b1"] | ["a", "b2"]>;
|
||||||
|
* // { a: { b1: string; }; } | { a: { b2: string; }; }
|
||||||
|
*/
|
||||||
|
type NestedObject<T extends string[]> =
|
||||||
|
T extends [infer Head, ...infer Tail] ?
|
||||||
|
Head extends string ?
|
||||||
|
{ [key in Head]: NestedObject<Tail extends string[] ? Tail : []> } : never :
|
||||||
|
string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example type Output = UnionToIntersection<{ a: { b1: string; }; } | { a: { b2: string; }; }>;
|
||||||
|
* // { a: { b1: string; }; } & { a: { b2: string; }; }
|
||||||
|
*/
|
||||||
|
type UnionToIntersection<U> =
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @example type Output = CreateTree<'a.b1' | 'a.b2', '.'>;
|
||||||
|
* // { a: { b1: string; }; } & { a: { b2: string; }; }
|
||||||
|
*/
|
||||||
|
export type CreateTree<T> =
|
||||||
|
UnionToIntersection<T extends infer U ?
|
||||||
|
U extends string ?
|
||||||
|
NestedObject<Split<U, '.'>> : never : never>;
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
export const mastersData = [
|
|
||||||
{
|
|
||||||
id: 'masters1',
|
|
||||||
name: 'Иван Иванов',
|
|
||||||
schedule: ['15:00 - 16:30', '17:00 - 18:00'],
|
|
||||||
phone: '+7 900 123 45 67',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'masters2',
|
|
||||||
name: 'Сергей Петров',
|
|
||||||
schedule: ['10:00 - 12:30', '14:00 - 15:30', '16:00 - 17:00'],
|
|
||||||
phone: '+7 900 234 56 78',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const ordersData = [
|
|
||||||
{
|
|
||||||
id: 'order1',
|
|
||||||
carNumber: 'A123BC',
|
|
||||||
washTime: '10:30',
|
|
||||||
orderDate: '2024-10-31',
|
|
||||||
status: 'progress',
|
|
||||||
phone: '79001234567',
|
|
||||||
location: 'Казань, ул. Баумана, 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'order2',
|
|
||||||
carNumber: 'B456CD',
|
|
||||||
washTime: '11:00',
|
|
||||||
orderDate: '2024-10-31',
|
|
||||||
status: 'complete',
|
|
||||||
phone: '79002345678',
|
|
||||||
location: 'Казань, ул. Кремлёвская, 3',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
23
src/models/i18next.d.ts
vendored
23
src/models/i18next.d.ts
vendored
@@ -1,24 +1,5 @@
|
|||||||
import defaultLocale from '../../locales/ru.json';
|
import defaultLocale from '../../locales/ru.json';
|
||||||
|
import { CreateTree } from '../lib';
|
||||||
type Split<S extends string, D extends string> =
|
|
||||||
S extends `${infer A}${D}${infer B}` ? [A, ...Split<B, D>] : [S];
|
|
||||||
|
|
||||||
type NestedObject<T extends string[]> =
|
|
||||||
T extends [infer Head, ...infer Tail] ?
|
|
||||||
Head extends string ?
|
|
||||||
{ [key in Head]: NestedObject<Tail extends string[] ? Tail : []> } : never :
|
|
||||||
string;
|
|
||||||
|
|
||||||
// Основная утилита для обработки union type
|
|
||||||
type CreateTree<T> =
|
|
||||||
UnionToIntersection<T extends infer U ?
|
|
||||||
U extends string ?
|
|
||||||
NestedObject<Split<U, '.'>> : never : never>;
|
|
||||||
|
|
||||||
// Утилита для объединения типов
|
|
||||||
type UnionToIntersection<U> =
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
||||||
|
|
||||||
type LanguageResource = CreateTree<keyof typeof defaultLocale>;
|
type LanguageResource = CreateTree<keyof typeof defaultLocale>;
|
||||||
|
|
||||||
@@ -26,6 +7,6 @@ declare module "i18next" {
|
|||||||
interface CustomTypeOptions {
|
interface CustomTypeOptions {
|
||||||
resources: {
|
resources: {
|
||||||
'~': LanguageResource
|
'~': LanguageResource
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
src/models/landing/index.ts
Normal file
1
src/models/landing/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './stubs';
|
||||||
1
src/models/landing/stubs/index.ts
Normal file
1
src/models/landing/stubs/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as LandingSuccessStub } from './success';
|
||||||
27
src/models/landing/stubs/success.ts
Normal file
27
src/models/landing/stubs/success.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Generated by json-literal-typer
|
||||||
|
// <-- BEGIN
|
||||||
|
interface HeroXsection {
|
||||||
|
description: "hero-section.description";
|
||||||
|
headline: "hero-section.headline";
|
||||||
|
video: "demo.mp4";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Sections {
|
||||||
|
description?: "benefits-section.description";
|
||||||
|
heading: "benefits-section.heading" | "social-proof-section.heading";
|
||||||
|
list?: ("benefits-section.list.0" | "benefits-section.list.1" | "benefits-section.list.2" | "benefits-section.list.3")[];
|
||||||
|
type: "benefits-section" | "social-proof-section";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Body {
|
||||||
|
"hero-section": HeroXsection;
|
||||||
|
sections: Sections[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Root {
|
||||||
|
body: Body;
|
||||||
|
success: true;
|
||||||
|
}
|
||||||
|
// END -->
|
||||||
|
|
||||||
|
export default Root;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import LayoutArm from '../../components/LayoutArm';
|
import LayoutArm from '../../components/LayoutArm';
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { Container, VStack } from '@chakra-ui/react';
|
import { Container, VStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BenefitsSection,
|
BenefitsSection,
|
||||||
Footer,
|
Footer,
|
||||||
@@ -7,6 +8,12 @@ import {
|
|||||||
SocialProofSection,
|
SocialProofSection,
|
||||||
} from '../../components/landing';
|
} from '../../components/landing';
|
||||||
import { LandingThemeProvider } from '../../containers';
|
import { LandingThemeProvider } from '../../containers';
|
||||||
|
import { LandingSuccessStub } from '../../models/landing';
|
||||||
|
import landingSuccessStubJson from '../../../stubs/json/landing/success.json';
|
||||||
|
|
||||||
|
import { isBenefitsSectionData, isSocialProofSectionData } from './types';
|
||||||
|
|
||||||
|
const landingSuccessStub = landingSuccessStubJson as LandingSuccessStub;
|
||||||
|
|
||||||
const Page: FC = () => {
|
const Page: FC = () => {
|
||||||
return (
|
return (
|
||||||
@@ -20,10 +27,19 @@ const Page: FC = () => {
|
|||||||
centerContent
|
centerContent
|
||||||
>
|
>
|
||||||
<VStack w='full' h='full' alignItems='stretch' flexGrow={1}>
|
<VStack w='full' h='full' alignItems='stretch' flexGrow={1}>
|
||||||
<HeroSection flexShrink={0} />
|
<HeroSection
|
||||||
|
data={landingSuccessStub['body']['hero-section']}
|
||||||
|
flexShrink={0}
|
||||||
|
/>
|
||||||
<VStack as='main' flexGrow={1}>
|
<VStack as='main' flexGrow={1}>
|
||||||
<BenefitsSection />
|
{landingSuccessStub.body.sections.map(({ type, ...data }, i) => {
|
||||||
<SocialProofSection />
|
if (isBenefitsSectionData(type, data)) {
|
||||||
|
return <BenefitsSection key={i} data={data} />;
|
||||||
|
}
|
||||||
|
if (isSocialProofSectionData(type, data)) {
|
||||||
|
return <SocialProofSection key={i} data={data} />;
|
||||||
|
}
|
||||||
|
})}
|
||||||
</VStack>
|
</VStack>
|
||||||
<Footer />
|
<Footer />
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
15
src/pages/landing/types.ts
Normal file
15
src/pages/landing/types.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import LandingSuccess from "../../../stubs/json/landing/success.json";
|
||||||
|
import { BenefitsSectionProps, SocialProofSectionProps } from "../../components/landing";
|
||||||
|
import { ArrElement } from "../../lib";
|
||||||
|
|
||||||
|
type SectionsItemData = ArrElement<typeof LandingSuccess['body']['sections']>;
|
||||||
|
type SectionType = SectionsItemData['type'];
|
||||||
|
type SectionData = Omit<SectionsItemData, 'type'>;
|
||||||
|
|
||||||
|
export const isBenefitsSectionData = (type: SectionType, data: SectionData): data is BenefitsSectionProps['data'] => {
|
||||||
|
return type === 'benefits-section';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSocialProofSectionData = (type: SectionType, data: SectionData): data is SocialProofSectionProps['data'] => {
|
||||||
|
return type === 'social-proof-section';
|
||||||
|
};
|
||||||
@@ -2,9 +2,10 @@ import React from 'react';
|
|||||||
import { Text, Button, Center, VStack, Heading } from '@chakra-ui/react';
|
import { Text, Button, Center, VStack, Heading } from '@chakra-ui/react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Player } from '@lottiefiles/react-lottie-player';
|
import { Player } from '@lottiefiles/react-lottie-player';
|
||||||
import animate from '../../assets/animation/notFound.json';
|
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
|
import animate from '../../assets/animation/notFound.json';
|
||||||
|
|
||||||
const NotFound = () => {
|
const NotFound = () => {
|
||||||
return (
|
return (
|
||||||
<Center minH='100vh'>
|
<Center minH='100vh'>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { URLs } from '../../__data__/urls';
|
import { URLs } from '../../__data__/urls';
|
||||||
import { mockOrder } from '../../mocks/landing';
|
import { mockOrder } from '../../mocks/landing';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { lazy, Suspense } from 'react';
|
import React, { lazy, Suspense } from 'react';
|
||||||
import { Routes, Route } from 'react-router-dom';
|
import { Routes, Route } from 'react-router-dom';
|
||||||
|
|
||||||
import { PageSpinner } from './components';
|
import { PageSpinner } from './components';
|
||||||
import Arm from './pages/arm';
|
import Arm from './pages/arm';
|
||||||
import { URLs } from './__data__/urls';
|
import { URLs } from './__data__/urls';
|
||||||
@@ -16,7 +17,9 @@ const Routers = () => {
|
|||||||
<Route path={URLs.landing.url} element={<Landing />} />
|
<Route path={URLs.landing.url} element={<Landing />} />
|
||||||
<Route path={URLs.orderForm.url} element={<OrderForm />} />
|
<Route path={URLs.orderForm.url} element={<OrderForm />} />
|
||||||
<Route path={URLs.orderView.url} element={<OrderView />} />
|
<Route path={URLs.orderView.url} element={<OrderView />} />
|
||||||
<Route path='/dry-wash/arm/*' element={<Arm />}></Route>
|
{URLs.armBase.isOn && (
|
||||||
|
<Route path={URLs.armBase.url} element={<Arm />}></Route>
|
||||||
|
)}
|
||||||
<Route path='*' element={<NotFound />} />
|
<Route path='*' element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
35
stubs/json/arm-masters/success.json
Normal file
35
stubs/json/arm-masters/success.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"id": "masters1",
|
||||||
|
"name": "Иван Иванов",
|
||||||
|
"schedule": [ {
|
||||||
|
"id": "order1",
|
||||||
|
"startWashTime": "2024-11-24T10:30:00.000Z",
|
||||||
|
"endWashTime": "2024-11-24T16:30:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "order2",
|
||||||
|
"startWashTime": "2024-11-24T11:30:00.000Z",
|
||||||
|
"endWashTime": "2024-11-24T17:30:00.000Z"
|
||||||
|
}],
|
||||||
|
"phone": "+7 900 123 45 67"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "masters12",
|
||||||
|
"name": "Иван Иванов",
|
||||||
|
"schedule": [ {
|
||||||
|
"id": "order1",
|
||||||
|
"startWashTime": "2024-11-24T10:30:00.000Z",
|
||||||
|
"endWashTime": "2024-11-24T16:30:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "order2",
|
||||||
|
"startWashTime": "2024-11-24T11:30:00.000Z",
|
||||||
|
"endWashTime": "2024-11-24T17:30:00.000Z"
|
||||||
|
}],
|
||||||
|
"phone": "+7 900 123 45 67"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
stubs/json/arm-orders/success.json
Normal file
25
stubs/json/arm-orders/success.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"id": "order1",
|
||||||
|
"carNumber": "A123BC",
|
||||||
|
"startWashTime": "2024-11-24T10:30:00.000Z",
|
||||||
|
"endWashTime": "2024-11-24T16:30:00.000Z",
|
||||||
|
"orderDate": "2024-11-24T08:41:46.366Z",
|
||||||
|
"status": "progress",
|
||||||
|
"phone": "79001234563",
|
||||||
|
"location": "Казань, ул. Баумана, 1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "order2",
|
||||||
|
"carNumber": "A245BC",
|
||||||
|
"startWashTime": "2024-11-24T11:30:00.000Z",
|
||||||
|
"endWashTime": "2024-11-24T17:30:00.000Z",
|
||||||
|
"orderDate": "2024-11-24T07:40:46.366Z",
|
||||||
|
"status": "progress",
|
||||||
|
"phone": "79001234567",
|
||||||
|
"location": "Казань, ул. Баумана, 43"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
27
stubs/json/landing/success.json
Normal file
27
stubs/json/landing/success.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"body": {
|
||||||
|
"hero-section": {
|
||||||
|
"headline": "hero-section.headline",
|
||||||
|
"description": "hero-section.description",
|
||||||
|
"video": "demo.mp4"
|
||||||
|
},
|
||||||
|
"sections": [
|
||||||
|
{
|
||||||
|
"type": "benefits-section",
|
||||||
|
"heading": "benefits-section.heading",
|
||||||
|
"description": "benefits-section.description",
|
||||||
|
"list": [
|
||||||
|
"benefits-section.list.0",
|
||||||
|
"benefits-section.list.1",
|
||||||
|
"benefits-section.list.2",
|
||||||
|
"benefits-section.list.3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "social-proof-section",
|
||||||
|
"heading": "social-proof-section.heading"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user