diff --git a/bro.config.js b/bro.config.js index f12638a..d2a8385 100644 --- a/bro.config.js +++ b/bro.config.js @@ -12,8 +12,8 @@ module.exports = { /* use https://admin.bro-js.ru/ to create config, navigations and features */ navigations: { 'dry-wash.main': '/dry-wash', - 'dry-wash.create': '/order', - 'dry-wash.view.order': '/order/:orderId', + 'dry-wash.order.create': '/order', + 'dry-wash.order.view': '/order/:orderId', 'dry-wash.arm.master': 'master', 'dry-wash.arm.order': 'order', 'dry-wash.arm': '/arm/*', diff --git a/locales/en.json b/locales/en.json index 5820580..516be8e 100644 --- a/locales/en.json +++ b/locales/en.json @@ -14,6 +14,30 @@ "dry-wash.landing.make-order-button": "Make order", "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.order-create.title": "Order a car wash", + "dry-wash.order-create.form.field.validation.required": "This field is required", + "dry-wash.order-create.form.phone-field.label": "Phone number", + "dry-wash.order-create.form.phone-field.invalid": "Enter the valid phone number", + "dry-wash.order-create.form.car-number-field.label": "Car number", + "dry-wash.order-create.form.car-number-field.invalid": "Enter the valid vehicle number", + "dry-wash.order-create.form.car-color-field.label": "The color of the car", + "dry-wash.order-create.form.car-body-field.label": "Car body type", + "dry-wash.order-create.form.washing-datetime-field.label": "What time is the car available?", + "dry-wash.order-create.form.washing-location-field.label": "Where is the car located?", + "dry-wash.order-create.form.washing-location-field.help": "For example, 55.754364, 48.743295 Universitetskaya Street, 1, Innopolis, Verkhneuslonsky district, Republic of Tatarstan (Tatarstan), 420500", + "dry-wash.order-create.car-body-select.placeholder": "Not specified", + "dry-wash.order-create.car-body-select.options.sedan": "Sedan", + "dry-wash.order-create.car-body-select.options.hatchback" : "Hatchback", + "dry-wash.order-create.car-body-select.options.crossover" : "Crossover", + "dry-wash.order-create.car-body-select.options.suv" : "Sport utility vehicle", + "dry-wash.order-create.car-body-select.options.station-wagon" : "Station wagon", + "dry-wash.order-create.car-body-select.options.coupe" : "Coupe", + "dry-wash.order-create.car-body-select.options.minivan" : "Minivan", + "dry-wash.order-create.car-body-select.options.pickup" : "Pickup", + "dry-wash.order-create.car-body-select.options.liftback" : "Liftback", + "dry-wash.order-create.car-body-select.options.sports-car" : "Sports-car", + "dry-wash.order-create.car-body-select.options.other": "Other", + "dry-wash.order-create.form.submit-button.label": "Submit", "dry-wash.arm.master.add": "Add", "dry-wash.arm.order.title": "Orders", "dry-wash.arm.order.status.progress": "In Progress", diff --git a/locales/ru.json b/locales/ru.json index 5c9d91c..65ea24d 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -44,11 +44,35 @@ "dry-wash.landing.make-order-button": "Сделать заказ", "dry-wash.landing.site-logo": "Логотип компании \u00ABDry Master\u00BB", "dry-wash.landing.social-proof-section.heading": "Нас выбирают", + "dry-wash.order-create.title": "Заказать мойку", + "dry-wash.order-create.form.field.validation.required": "Это поле обязательно для заполнения", + "dry-wash.order-create.form.phone-field.label": "Номер телефона", + "dry-wash.order-create.form.phone-field.invalid": "Введите корректный номер телефона", + "dry-wash.order-create.form.car-number-field.label": "Номер автомобиля", + "dry-wash.order-create.form.car-number-field.invalid": "Введите корректный номер автомобиля", + "dry-wash.order-create.form.car-color-field.label": "Цвет автомобиля", + "dry-wash.order-create.form.car-body-field.label": "Тип кузова автомобиля", + "dry-wash.order-create.form.washing-datetime-field.label": "В какое время автомобиль доступен?", + "dry-wash.order-create.form.washing-location-field.label": "Где находится автомобиль?", + "dry-wash.order-create.form.washing-location-field.help": "Например, 55.754364, 48.743295 Университетская улица, 1, Иннополис, Верхнеуслонский район, Республика Татарстан (Татарстан), 420500", + "dry-wash.order-create.car-body-select.placeholder": "Не указан", + "dry-wash.order-create.car-body-select.options.sedan": "Седан", + "dry-wash.order-create.car-body-select.options.hatchback" : "Хэтчбек", + "dry-wash.order-create.car-body-select.options.crossover" : "Кроссовер", + "dry-wash.order-create.car-body-select.options.suv" : "Внедорожник", + "dry-wash.order-create.car-body-select.options.station-wagon" : "Универсал", + "dry-wash.order-create.car-body-select.options.coupe" : "Купе", + "dry-wash.order-create.car-body-select.options.minivan" : "Минивэн", + "dry-wash.order-create.car-body-select.options.pickup" : "Пикап", + "dry-wash.order-create.car-body-select.options.liftback" : "Лифтбек", + "dry-wash.order-create.car-body-select.options.sports-car" : "Спорткар", + "dry-wash.order-create.car-body-select.options.other": "Другой", + "dry-wash.order-create.form.submit-button.label": "Отправить", "dry-wash.notFound.title": "Страница не найдена", "dry-wash.notFound.description": "К сожалению, запрашиваемая вами страница не существует.", "dry-wash.notFound.button.back": "Вернуться на главную", - "dry-wash.errorBoundary.title":"Что-то пошло не так", + "dry-wash.errorBoundary.title": "Что-то пошло не так", "dry-wash.errorBoundary.description": "Мы уже работаем над исправлением проблемы", "dry-wash.errorBoundary.button.reload": "Перезагрузить страницу", "dry-wash.washTime.timeSlot": "{{start}} - {{end}}" -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 37fe373..80008b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,10 @@ "i18next": "^23.16.4", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.53.2", "react-i18next": "^15.1.1", "react-icons": "^5.3.0", + "react-phone-number-input": "^3.4.9", "react-router-dom": "^6.27.0" }, "devDependencies": { @@ -4918,6 +4920,12 @@ "node": ">=6.0" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-webpack-plugin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", @@ -5158,6 +5166,12 @@ "node": ">=8" } }, + "node_modules/country-flag-icons": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.13.tgz", + "integrity": "sha512-4JwHNqaKZ19doQoNcBjsoYA+I7NqCH/mC/6f5cBWvdKzcK5TMmzLpq3Z/syVHMHJuDGFwJ+rPpGizvrqJybJow==", + "license": "MIT" + }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", @@ -7539,6 +7553,27 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/input-format": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/input-format/-/input-format-0.3.11.tgz", + "integrity": "sha512-q24+iW10ZMb7KIRDlVUl3GvFcadf1ttE/QA2waINkDMdjsPXStQSOvdTyHwO8p+4Mq433ILQJZRL8YKtPjNk4g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^18.1.0", + "react-dom": "^18.1.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -8248,6 +8283,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.15", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.15.tgz", + "integrity": "sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g==", + "license": "MIT" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9542,6 +9583,22 @@ } } }, + "node_modules/react-hook-form": { + "version": "7.53.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.2.tgz", + "integrity": "sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.1.1.tgz", @@ -9578,6 +9635,23 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-phone-number-input": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/react-phone-number-input/-/react-phone-number-input-3.4.9.tgz", + "integrity": "sha512-RG40GTjfJwBR5whpEkQMvMMKcbqQSlXiKfiTp2mYoULkTYwxFn04iAVplRizWi3yLPL0fQiL4U+YU+9MIQGZog==", + "license": "MIT", + "dependencies": { + "classnames": "^2.5.1", + "country-flag-icons": "^1.5.11", + "input-format": "^0.3.10", + "libphonenumber-js": "^1.11.12", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-remove-scroll": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", diff --git a/package.json b/package.json index 0d8a125..9a99014 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,10 @@ "i18next": "^23.16.4", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.53.2", "react-i18next": "^15.1.1", "react-icons": "^5.3.0", + "react-phone-number-input": "^3.4.9", "react-router-dom": "^6.27.0" }, "devDependencies": { diff --git a/src/__data__/urls.ts b/src/__data__/urls.ts index abb1331..74cd068 100644 --- a/src/__data__/urls.ts +++ b/src/__data__/urls.ts @@ -1,7 +1,7 @@ import { generatePath } from 'react-router-dom'; import { getNavigationValue } from '@brojs/cli'; -import { Order } from '../models'; +import { Order } from '../models/landing'; const getFullUrls = (url: string) => `${getNavigationValue('dry-wash.main')}${url}`; @@ -13,14 +13,14 @@ export const URLs = { return this.url; }, }, - orderForm: { - url: getNavigationValue('dry-wash.create'), + orderCreate: { + url: getFullUrls(getNavigationValue('dry-wash.order.create')), getUrl() { return this.url; }, }, orderView: { - url: getNavigationValue('dry-wash.view.order'), + url: getFullUrls(getNavigationValue('dry-wash.order.view')), getUrl(orderId: Order.Id) { return generatePath(this.url, { orderId }); }, diff --git a/src/components/landing/CtaButton/CtaButton.tsx b/src/components/landing/CtaButton/CtaButton.tsx index d4769e7..332a641 100644 --- a/src/components/landing/CtaButton/CtaButton.tsx +++ b/src/components/landing/CtaButton/CtaButton.tsx @@ -11,7 +11,7 @@ export const CtaButton: FC = (props) => { return ( + ); +}; diff --git a/src/components/order-form/form/types.ts b/src/components/order-form/form/types.ts new file mode 100644 index 0000000..9746d54 --- /dev/null +++ b/src/components/order-form/form/types.ts @@ -0,0 +1,9 @@ +export type OrderFormValues = { + phone: string; + carNumber: string; + carColor: string; + carBody: string; + carLocation: string; + availableDatetimeBegin: string; + availableDatetimeEnd: string; +}; \ No newline at end of file diff --git a/src/components/order-form/index.ts b/src/components/order-form/index.ts new file mode 100644 index 0000000..b8a36bf --- /dev/null +++ b/src/components/order-form/index.ts @@ -0,0 +1 @@ +export * from './form'; \ No newline at end of file diff --git a/src/containers/LandingThemeProvider/theme-config.ts b/src/containers/LandingThemeProvider/theme-config.ts index 43a0676..19916c4 100644 --- a/src/containers/LandingThemeProvider/theme-config.ts +++ b/src/containers/LandingThemeProvider/theme-config.ts @@ -37,6 +37,44 @@ const overrides = { } }, }, + components: { + Input: { + variants: { + filled: { + field: { + borderRadius: 12, + bgColor: 'primary.50', + color: 'primary.600', + _hover: { + borderColor: 'primary.100', + bgColor: 'primary.50', + }, + _focus: { + borderColor: 'primary.200', + } + } + } + } + }, + Select: { + variants: { + filled: { + field: { + borderRadius: 12, + bgColor: 'primary.50', + color: 'primary.600', + _hover: { + borderColor: 'primary.100', + bgColor: 'primary.50', + }, + _focus: { + borderColor: 'primary.200', + } + } + } + } + } + } }; export default extendTheme(overrides); \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index ddf5c34..13c310b 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,3 +1 @@ -export * from './i18n'; -export * as Order from './order'; -export * as Review from './review'; \ No newline at end of file +export * from './i18n'; \ No newline at end of file diff --git a/src/models/landing/car.ts b/src/models/landing/car.ts new file mode 100644 index 0000000..9058a85 --- /dev/null +++ b/src/models/landing/car.ts @@ -0,0 +1,18 @@ +export type RegistrationNumber = string; // А012ВЕ + +export type Color = string; // #000000 + +export const enum BodyStyle { + UNKNOWN = 0, + SEDAN = 1, + HATCHBACK = 2, + CROSSOVER = 3, + SUV = 4, + STATION_WAGON = 5, + COUPE = 6, + MINIVAN = 7, + PICKUP = 8, + LIFTBACK = 9, + SPORTS_CAR = 10, + OTHER = 99 +} \ No newline at end of file diff --git a/src/models/landing/customer.ts b/src/models/landing/customer.ts new file mode 100644 index 0000000..f98cf1a --- /dev/null +++ b/src/models/landing/customer.ts @@ -0,0 +1 @@ +export type PhoneNumber = string; // +79876543210 \ No newline at end of file diff --git a/src/models/landing/index.ts b/src/models/landing/index.ts index 11add84..c2ff256 100644 --- a/src/models/landing/index.ts +++ b/src/models/landing/index.ts @@ -1 +1,6 @@ +export * as Car from './car'; +export * as Customer from './customer'; +export * as Washing from './washing'; +export * as Order from './order'; // import: Car, Customer, Washing +export * as Review from './review'; export * from './stubs'; \ No newline at end of file diff --git a/src/models/landing/order.ts b/src/models/landing/order.ts new file mode 100644 index 0000000..d286a90 --- /dev/null +++ b/src/models/landing/order.ts @@ -0,0 +1,23 @@ +import { Car, Customer, Washing } from "."; + +export type Id = string; + +export type Create = { + customer: { + phone: Customer.PhoneNumber, + }; + car: { + number: Car.RegistrationNumber, + body: Car.BodyStyle, + color: Car.Color, + }, + washing: { + location: Washing.Location + begin: Washing.AvailableBeginDateTime, + end: Washing.AvailableEndDateTime, + } +}; + +export type View = { + id: Id; +}; \ No newline at end of file diff --git a/src/models/review.ts b/src/models/landing/review.ts similarity index 100% rename from src/models/review.ts rename to src/models/landing/review.ts diff --git a/src/models/landing/washing.ts b/src/models/landing/washing.ts new file mode 100644 index 0000000..c61c638 --- /dev/null +++ b/src/models/landing/washing.ts @@ -0,0 +1,5 @@ +export type Location = string; // ? + +export type AvailableBeginDateTime = string; // YYYY-MM-DDThh:mm + +export type AvailableEndDateTime = string; // YYYY-MM-DDThh:mm \ No newline at end of file diff --git a/src/models/order.ts b/src/models/order.ts deleted file mode 100644 index 5cd62d9..0000000 --- a/src/models/order.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type Id = string; - -export type View = { - id: Id; -}; \ No newline at end of file diff --git a/src/pages/order-create/index.tsx b/src/pages/order-create/index.tsx new file mode 100644 index 0000000..e4dd2ad --- /dev/null +++ b/src/pages/order-create/index.tsx @@ -0,0 +1,32 @@ +import React, { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Container, Heading, VStack } from '@chakra-ui/react'; + +import { LandingThemeProvider } from '../../containers'; +import { OrderForm } from '../../components/order-form'; + +const Page: FC = () => { + const { t } = useTranslation('~', { + keyPrefix: 'dry-wash.order-create', + }); + + return ( + + + + {t('title')} + + + + + ); +}; + +export default Page; \ No newline at end of file diff --git a/src/pages/order-form/index.tsx b/src/pages/order-form/index.tsx deleted file mode 100644 index a6f19cb..0000000 --- a/src/pages/order-form/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { Button } from '@chakra-ui/react'; - -import { URLs } from '../../__data__/urls'; -import { mockOrder } from '../../mocks/landing'; - -const Page = () => { - return ( - <> -

Order form

- {mockOrder.orders.map(({ id }) => ( - - ))} - - ); -}; - -export default Page; diff --git a/src/routes.tsx b/src/routes.tsx index 949a28e..c3b5e47 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -7,16 +7,18 @@ import { URLs } from './__data__/urls'; import NotFound from './pages/notFound/notFound'; const Landing = lazy(() => import('./pages/landing')); -const OrderForm = lazy(() => import('./pages/order-form')); +const OrderCreate = lazy(() => import('./pages/order-create')); const OrderView = lazy(() => import('./pages/order-view')); const Routers = () => { return ( }> - } /> - } /> - } /> + + } /> + } /> + } /> + {URLs.armBase.isOn && ( }> )}