From eda869622eca0ea9e975d8b967764ca7b008f0c8 Mon Sep 17 00:00:00 2001 From: RustamRu Date: Sat, 22 Feb 2025 18:56:20 +0300 Subject: [PATCH 1/4] fix: error body message -> error (#88) --- src/__data__/service/utils.ts | 6 +++--- src/models/api/common.ts | 2 +- stubs/json/landing-order-create/error.json | 2 +- stubs/json/landing-order-view/id1-error.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/__data__/service/utils.ts b/src/__data__/service/utils.ts index 2ffe971..f7c296b 100644 --- a/src/__data__/service/utils.ts +++ b/src/__data__/service/utils.ts @@ -13,9 +13,9 @@ export const extractErrorMessageFromResponse = ({ }: FetchBaseQueryError) => { if ( typeof data === 'object' && - 'message' in data && - typeof data.message === 'string' + 'error' in data && + typeof data.error === 'string' ) { - return data.message; + return data.error; } }; diff --git a/src/models/api/common.ts b/src/models/api/common.ts index 4bde4dd..16d573f 100644 --- a/src/models/api/common.ts +++ b/src/models/api/common.ts @@ -9,7 +9,7 @@ export const isErrorMessage = (error: unknown): error is ErrorMessage => typeof type ErrorResponse = { success: false; - message: ErrorMessage; + error: ErrorMessage; }; export type BaseResponse = SuccessResponse | ErrorResponse; diff --git a/stubs/json/landing-order-create/error.json b/stubs/json/landing-order-create/error.json index 3770aac..2bab2f1 100644 --- a/stubs/json/landing-order-create/error.json +++ b/stubs/json/landing-order-create/error.json @@ -1,4 +1,4 @@ { "success": false, - "message": "Не удалось создать заказ" + "error": "Не удалось создать заказ" } \ No newline at end of file diff --git a/stubs/json/landing-order-view/id1-error.json b/stubs/json/landing-order-view/id1-error.json index fa97f72..ebbaea3 100644 --- a/stubs/json/landing-order-view/id1-error.json +++ b/stubs/json/landing-order-view/id1-error.json @@ -1,4 +1,4 @@ { "success": false, - "message": "Не удалось загрузить детали заказа" + "error": "Не удалось загрузить детали заказа" } \ No newline at end of file -- 2.45.2 From de54ac66692d059f9c1afddf259855e49d3a6ec3 Mon Sep 17 00:00:00 2001 From: RustamRu Date: Sat, 22 Feb 2025 18:56:37 +0300 Subject: [PATCH 2/4] feat: api upload car img (#88) --- src/__data__/service/landing.api.ts | 10 +++++++++- src/models/api/order.ts | 11 +++++++++++ stubs/api/admin.js | 16 +++++++++++----- stubs/api/index.js | 16 ++++++++++++++++ .../id1-error-file-size.json | 4 ++++ .../id1-error-file-type.json | 4 ++++ .../id1-success.json | 3 +++ 7 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 stubs/json/landing-order-car-image-upload/id1-error-file-size.json create mode 100644 stubs/json/landing-order-car-image-upload/id1-error-file-type.json create mode 100644 stubs/json/landing-order-car-image-upload/id1-success.json diff --git a/src/__data__/service/landing.api.ts b/src/__data__/service/landing.api.ts index 7f84aef..9372cf8 100644 --- a/src/__data__/service/landing.api.ts +++ b/src/__data__/service/landing.api.ts @@ -1,4 +1,4 @@ -import { GetOrder, CreateOrder } from "../../models/api"; +import { GetOrder, CreateOrder, UploadCarImage } from "../../models/api"; import { api } from "./api"; import { extractBodyFromResponse, extractErrorMessageFromResponse } from "./utils"; @@ -19,5 +19,13 @@ export const landingApi = api.injectEndpoints({ transformResponse: extractBodyFromResponse, transformErrorResponse: extractErrorMessageFromResponse, }), + uploadCarImage: mutation({ + query: ({ orderId, body }) => ({ + url: `/order/${orderId}/upload-car-img`, + body, + method: 'POST' + }), + transformErrorResponse: extractErrorMessageFromResponse, + }), }) }); diff --git a/src/models/api/order.ts b/src/models/api/order.ts index dabe6f9..71e6503 100644 --- a/src/models/api/order.ts +++ b/src/models/api/order.ts @@ -21,6 +21,17 @@ export namespace CreateOrder { }; } +export namespace UploadCarImage { + export type Response = void; + export type Params = { + orderId: Order.Id; + /** + * @example { file: File } + */ + body: FormData; + }; +} + type GetArrItemType = ArrType extends Array ? ItemType : never; diff --git a/stubs/api/admin.js b/stubs/api/admin.js index cf071cf..d5b8bf1 100644 --- a/stubs/api/admin.js +++ b/stubs/api/admin.js @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-require-imports */ const router = require('express').Router(); -const STUBS = { masters: 'success', orders: 'success', orderCreate: 'success', orderView: 'success-pending' }; +const STUBS = { masters: 'success', orders: 'success', orderCreate: 'success', orderView: 'success-pending', orderCarImg: 'success' }; router.get('/set/:name/:value', (req, res) => { const { name, value } = req.params; @@ -15,28 +15,34 @@ router.get('/set/:name/:value', (req, res) => { router.get('/', (req, res) => { res.send(`
- Мастера + Мастера ${generateRadioInput('masters', 'success')} ${generateRadioInput('masters', 'error')} ${generateRadioInput('masters', 'empty')}
- Заказы + Заказы ${generateRadioInput('orders', 'success')} ${generateRadioInput('orders', 'error')} ${generateRadioInput('orders', 'empty')}
- Лендинг - Сделать заказ + Лендинг - Сделать заказ ${generateRadioInput('orderCreate', 'success')} ${generateRadioInput('orderCreate', 'error')}
- Лендинг - Детали заказа + Лендинг - Детали заказа ${generateRadioInput('orderView', 'success-pending')} ${generateRadioInput('orderView', 'success-working')} ${generateRadioInput('orderView', 'error')}
+
+ Лендинг - Детали заказа, фото машины + ${generateRadioInput('orderCarImg', 'success')} + ${generateRadioInput('orderCarImg', 'error-file-type')} + ${generateRadioInput('orderCarImg', 'error-file-size')} +
`); }); diff --git a/stubs/api/index.js b/stubs/api/index.js index 2ecef50..d1e0a3c 100644 --- a/stubs/api/index.js +++ b/stubs/api/index.js @@ -98,6 +98,22 @@ router.post('/order/create', (req, res) => { ); }); +router.post('/order/:orderId/upload-car-img', (req, res) => { + const { orderId } = req.params; + const stubName = `${orderId}-${STUBS.orderCarImg}`; + + try { + res + .status(/error/.test(stubName) ? 500 : 200) + .send(require(`../json/landing-order-car-image-upload/${stubName}.json`)); + } catch (e) { + console.error(e); + res + .status(500) + .send(commonError); + } +}); + router.use('/admin', require('./admin')); module.exports = router; diff --git a/stubs/json/landing-order-car-image-upload/id1-error-file-size.json b/stubs/json/landing-order-car-image-upload/id1-error-file-size.json new file mode 100644 index 0000000..62806a0 --- /dev/null +++ b/stubs/json/landing-order-car-image-upload/id1-error-file-size.json @@ -0,0 +1,4 @@ +{ + "success": false, + "error": "Invalid car image file size. Limit is 5MB" +} \ No newline at end of file diff --git a/stubs/json/landing-order-car-image-upload/id1-error-file-type.json b/stubs/json/landing-order-car-image-upload/id1-error-file-type.json new file mode 100644 index 0000000..c861b2a --- /dev/null +++ b/stubs/json/landing-order-car-image-upload/id1-error-file-type.json @@ -0,0 +1,4 @@ +{ + "success": false, + "error": "Invalid car image file type. Allowed types: jpg, png" +} \ No newline at end of file diff --git a/stubs/json/landing-order-car-image-upload/id1-success.json b/stubs/json/landing-order-car-image-upload/id1-success.json new file mode 100644 index 0000000..28e7be1 --- /dev/null +++ b/stubs/json/landing-order-car-image-upload/id1-success.json @@ -0,0 +1,3 @@ +{ + "success": true +} \ No newline at end of file -- 2.45.2 From 20017cad3c86b8e2773ad6a61770ecd943066884 Mon Sep 17 00:00:00 2001 From: RustamRu Date: Sat, 22 Feb 2025 19:46:27 +0300 Subject: [PATCH 3/4] feat: upload car img form (#88) --- locales/en.json | 6 ++ locales/ru.json | 6 ++ .../order-view/car-img/car-img-form.tsx | 102 ++++++++++++++++++ src/components/order-view/car-img/helper.ts | 35 ++++++ src/components/order-view/car-img/index.ts | 1 + .../order-view/details/order-details.tsx | 12 ++- src/pages/order-view/index.tsx | 36 ++++--- 7 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 src/components/order-view/car-img/car-img-form.tsx create mode 100644 src/components/order-view/car-img/helper.ts create mode 100644 src/components/order-view/car-img/index.ts diff --git a/locales/en.json b/locales/en.json index db39a54..4ef706d 100644 --- a/locales/en.json +++ b/locales/en.json @@ -50,6 +50,12 @@ "dry-wash.order-view.details.location": "Where", "dry-wash.order-view.details.datetime-range": "When", "dry-wash.order-view.details.alert": "The operator will contact you about the payment at the specified phone number", + "dry-wash.order-view.upload-car-image.field.label": "Upload a photo of your car, and our service will quickly calculate the pre-order price!", + "dry-wash.order-view.upload-car-image.field.help": "Allowed formats: .jpg, .png. Maximum size: 5MB", + "dry-wash.order-view.upload-car-image.file-input.placeholder": "Upload a file", + "dry-wash.order-view.upload-car-image.file-input.button": "Upload", + "dry-wash.order-view.upload-car-image-query.success.title": "The car image is successfully uploaded", + "dry-wash.order-view.upload-car-image-query.error.title": "Failed to upload the car image", "dry-wash.arm.master.add": "Add", "dry-wash.arm.order.title": "Orders", "dry-wash.arm.order.table.empty": "Table empty", diff --git a/locales/ru.json b/locales/ru.json index 15586fb..93abaff 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -105,6 +105,12 @@ "dry-wash.order-view.details.location": "Где", "dry-wash.order-view.details.datetime-range": "Когда", "dry-wash.order-view.details.alert": "С вами свяжется оператор насчет оплаты по указанному номеру телефона", + "dry-wash.order-view.upload-car-image.field.label": "Загрузите фото вашего автомобиля, и наш сервис быстро рассчитает предварительную стоимость заказа!", + "dry-wash.order-view.upload-car-image.field.help": "Допустимые форматы: .jpg, .png. Максимальный размер: 5МБ", + "dry-wash.order-view.upload-car-image.file-input.placeholder": "Загрузите файл", + "dry-wash.order-view.upload-car-image.file-input.button": "Загрузить", + "dry-wash.order-view.upload-car-image-query.success.title": "Изображение автомобиля успешно загружено", + "dry-wash.order-view.upload-car-image-query.error.title": "Не удалось загрузить изображение автомобиля", "dry-wash.notFound.title": "Страница не найдена", "dry-wash.notFound.description": "К сожалению, запрашиваемая вами страница не существует.", "dry-wash.notFound.button.back": "Вернуться на главную", diff --git a/src/components/order-view/car-img/car-img-form.tsx b/src/components/order-view/car-img/car-img-form.tsx new file mode 100644 index 0000000..76d84e6 --- /dev/null +++ b/src/components/order-view/car-img/car-img-form.tsx @@ -0,0 +1,102 @@ +import React, { FC, memo, useRef } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { + Button, + FormControl, + FormErrorMessage, + FormHelperText, + FormLabel, + HStack, + Input, +} from '@chakra-ui/react'; +import { useTranslation } from 'react-i18next'; + +import { landingApi } from '../../../__data__/service/landing.api'; +import { UploadCarImage } from '../../../models/api'; + +import { useHandleUploadCarImageResponse } from './helper'; + +type FormValues = { + carImg: File & { + fileName: string; + }; +}; + +type CarImageFormProps = { + orderId: UploadCarImage.Params['orderId']; +}; + +export const CarImageForm: FC = memo(function CarImageForm({ + orderId, +}) { + const { + handleSubmit, + control, + formState: { errors, isSubmitting }, + } = useForm({ shouldFocusError: true }); + + const [uploadCarImage, uploadCarImageMutation] = + landingApi.useUploadCarImageMutation(); + useHandleUploadCarImageResponse(uploadCarImageMutation); + + const onSubmit = (formData: FormValues) => { + const body = new FormData(); + body.append('file', formData.carImg); + uploadCarImage({ orderId, body }); + }; + + const fileInputRef = useRef(null); + + const { t } = useTranslation('~', { + keyPrefix: 'dry-wash.order-view.upload-car-image', + }); + + return ( +
+ + {t('field.label')} + { + return ( + + { + onChange(event.target.files[0]); + handleSubmit(onSubmit)(); + }} + type='file' + hidden + /> + + + + ); + }} + /> + {errors.carImg?.message} + {t('field.help')} + +
+ ); +}); diff --git a/src/components/order-view/car-img/helper.ts b/src/components/order-view/car-img/helper.ts new file mode 100644 index 0000000..f299213 --- /dev/null +++ b/src/components/order-view/car-img/helper.ts @@ -0,0 +1,35 @@ +import { useEffect } from "react"; +import { useToast } from "@chakra-ui/react"; +import { useTranslation } from "react-i18next"; + +import { isErrorMessage } from "../../../models/api"; + +export const useHandleUploadCarImageResponse = (query: { + isSuccess: boolean; + isError: boolean; + error?: unknown; +}) => { + const toast = useToast(); + const { t } = useTranslation('~', { + keyPrefix: 'dry-wash.order-create.upload-car-image-query', + }); + + useEffect(() => { + if (query.isError) { + toast({ + status: 'error', + title: t('error.title'), + description: isErrorMessage(query.error) ? query.error : undefined, + }); + } + }, [query.isError]); + + useEffect(() => { + if (query.isSuccess) { + toast({ + status: 'success', + title: t('success.title'), + }); + } + }, [query.isSuccess]); +}; \ No newline at end of file diff --git a/src/components/order-view/car-img/index.ts b/src/components/order-view/car-img/index.ts new file mode 100644 index 0000000..4a829cf --- /dev/null +++ b/src/components/order-view/car-img/index.ts @@ -0,0 +1 @@ +export { CarImageForm } from './car-img-form'; diff --git a/src/components/order-view/details/order-details.tsx b/src/components/order-view/details/order-details.tsx index 62c8563..9411404 100644 --- a/src/components/order-view/details/order-details.tsx +++ b/src/components/order-view/details/order-details.tsx @@ -5,11 +5,13 @@ import { Heading, HStack, UnorderedList, - VStack, ListItem, Text, } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import localizedFormat from "dayjs/plugin/localizedFormat"; +dayjs.extend(localizedFormat); import { Order } from '../../../models/landing'; import { formatDatetime } from '../../../lib'; @@ -41,7 +43,7 @@ export const OrderDetails: FC = ({ location, startWashTime, endWashTime, - ...props + created }) => { const { t } = useTranslation('~', { keyPrefix: 'dry-wash.order-view.details', @@ -51,7 +53,7 @@ export const OrderDetails: FC = ({ }); return ( - + <> = ({ gap={2} > - {t('title', { number: orderNumber })} + {t('title', { number: orderNumber })} ({dayjs(created).format('LLLL')}) @@ -105,7 +107,7 @@ export const OrderDetails: FC = ({ {t('alert')} - + ); }; diff --git a/src/pages/order-view/index.tsx b/src/pages/order-view/index.tsx index 2367388..fe783ce 100644 --- a/src/pages/order-view/index.tsx +++ b/src/pages/order-view/index.tsx @@ -21,6 +21,7 @@ import { Order } from '../../models/landing'; import { landingApi } from '../../__data__/service/landing.api'; import { isErrorMessage } from '../../models/api'; import { FEATURE } from '../../__data__/features'; +import { CarImageForm } from '../../components/order-view/car-img'; const Page: FC = () => { const { t } = useTranslation('~', { @@ -69,24 +70,31 @@ const Page: FC = () => { <> <> {isSuccess && ( - + + + + )} <> {isError && ( - + -- 2.45.2 From 1968df7bb3ebb14161328b83cf0c85d160682f14 Mon Sep 17 00:00:00 2001 From: RustamRu Date: Sun, 23 Feb 2025 11:58:56 +0300 Subject: [PATCH 4/4] fix: getOrder test (#88) --- src/__data__/service/utils.ts | 1 + .../__snapshots__/order-view.test.tsx.snap | 53 ++++++++++++++++++- .../__snapshots__/ordersList.test.tsx.snap | 2 +- src/pages/order-view/index.tsx | 5 +- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/__data__/service/utils.ts b/src/__data__/service/utils.ts index f7c296b..f55bb9c 100644 --- a/src/__data__/service/utils.ts +++ b/src/__data__/service/utils.ts @@ -13,6 +13,7 @@ export const extractErrorMessageFromResponse = ({ }: FetchBaseQueryError) => { if ( typeof data === 'object' && + data !== null && 'error' in data && typeof data.error === 'string' ) { diff --git a/src/pages/__tests__/__snapshots__/order-view.test.tsx.snap b/src/pages/__tests__/__snapshots__/order-view.test.tsx.snap index fabd24b..72a1240 100644 --- a/src/pages/__tests__/__snapshots__/order-view.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/order-view.test.tsx.snap @@ -16,7 +16,6 @@ exports[`Страница просмотра заказа отображает
Заказ №{{number}} + ( + Sunday, January 19, 2025 5:04 PM + ) С вами свяжется оператор насчет оплаты по указанному номеру телефона
+
+
+ +
+ + + +
+
+ Допустимые форматы: .jpg, .png. Максимальный размер: 5МБ +
+
+
diff --git a/src/pages/__tests__/__snapshots__/ordersList.test.tsx.snap b/src/pages/__tests__/__snapshots__/ordersList.test.tsx.snap index 25091d5..f50f76a 100644 --- a/src/pages/__tests__/__snapshots__/ordersList.test.tsx.snap +++ b/src/pages/__tests__/__snapshots__/ordersList.test.tsx.snap @@ -75,7 +75,7 @@ exports[`Страница заказов должна корректно ото

- 20.02.2025 + 23.02.2025