= ({
+ text,
+ handleClose,
+}) => {
+ const { popupState } = usePopup();
+ return (
+
+
+
+ );
+};
+
+export default PopupCheckoutResponse;
diff --git a/src/components/popups/popup-checkout-response/popup-checkout-response.module.scss b/src/components/popups/popup-checkout-response/popup-checkout-response.module.scss
new file mode 100644
index 00000000..7e73327f
--- /dev/null
+++ b/src/components/popups/popup-checkout-response/popup-checkout-response.module.scss
@@ -0,0 +1,26 @@
+@use '@scss/variables' as *;
+
+.textContainer {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ max-width: 500px;
+ min-height: 200px;
+ padding: 50px;
+ font-size: 30px;
+}
+
+.text {
+ margin: 0;
+ text-align: center;
+ color: $active-text-color;
+ font-family: $ubuntu-font;
+ font-size: 20px;
+ font-weight: 400;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 14px;
+ font-weight: 500;
+ }
+}
From 53f9d68cf1b98aaa917cdfd80548da223d4b58bb Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 28 Dec 2023 19:10:59 +0300
Subject: [PATCH 052/141] feat: change link to orders in profile
---
src/pages/checkout/checkout-success/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 42b9502b..9124b751 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -29,7 +29,7 @@ const CheckoutSuccess: React.FC = () => {
Мы уже приступили к его сборке.
За статусом заказа можно следить в{' '}
-
+
личном кабинете
.
From 48c000702a3d2e940a228cd28d6f73a181a2df9a Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 28 Dec 2023 19:13:23 +0300
Subject: [PATCH 053/141] feat: add PopupCheckoutResponse to context
---
src/contexts/popup-context.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/contexts/popup-context.tsx b/src/contexts/popup-context.tsx
index a57bf205..c3eed1c6 100644
--- a/src/contexts/popup-context.tsx
+++ b/src/contexts/popup-context.tsx
@@ -8,6 +8,7 @@ type PopupState = {
openPopupAddressesWarning: boolean;
openPopupAddressesDeleteConfirm: boolean;
openPopupRecipe: boolean;
+ openPopupCheckoutResponse: boolean;
};
type PopupContextType = {
@@ -27,6 +28,7 @@ export const PopupProvider: React.FC<{ children: ReactNode }> = ({ children }) =
openPopupAddressesWarning: false,
openPopupAddressesDeleteConfirm: false,
openPopupRecipe: false,
+ openPopupCheckoutResponse: false,
});
const handleOpenPopup = (popupName: string) => {
From 577bb19831124f4ee54dc28df2ce3093c24444c6 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 28 Dec 2023 19:15:43 +0300
Subject: [PATCH 054/141] fix: mark userData as optional field
---
src/services/generated-api/data-contracts.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index f2aaae26..6f7dd8fb 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -1056,7 +1056,7 @@ export interface OrderPostDelete {
}
export interface OrderPostAdd extends OrderPostDelete {
- user_data: {
+ user_data?: {
first_name: string;
last_name: string;
phone_number: string;
From deeadf4e10cf1bbf144c0208519c4fa4c5d5b41b Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 28 Dec 2023 19:28:06 +0300
Subject: [PATCH 055/141] fix: delivery method passed from ShoppingCart
---
src/pages/shopping-cart/index.tsx | 29 ++++++++++++++++-------------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/src/pages/shopping-cart/index.tsx b/src/pages/shopping-cart/index.tsx
index c8d5af0f..c644555d 100644
--- a/src/pages/shopping-cart/index.tsx
+++ b/src/pages/shopping-cart/index.tsx
@@ -12,7 +12,7 @@ import { Product } from '@services/generated-api/data-contracts';
const ShoppingCart: React.FC = () => {
const { cartData, clearCart, loading } = useCart();
- const [activeButton, setActiveButton] = React.useState('shipment');
+ const [deliveryMethod, setDeliveryMethod] = React.useState('By courier');
const navigate = useNavigate();
const [promotionProducts, setPromotionProducts] = useState([]);
@@ -41,15 +41,13 @@ const ShoppingCart: React.FC = () => {
}, []);
const handleOrderTypeClick = (type: string) => {
- setActiveButton(type);
+ setDeliveryMethod(type);
};
const handleSubmitOrderClick = () => {
- const typeToSend = activeButton;
-
navigate('/cart/order', {
state: {
- orderType: typeToSend,
+ orderType: deliveryMethod,
},
});
};
@@ -95,10 +93,13 @@ const ShoppingCart: React.FC = () => {
{
- handleOrderTypeClick('shipment');
+ handleOrderTypeClick('By courier');
}}
>
{
{
- handleOrderTypeClick('pickup');
+ handleOrderTypeClick('Point of delivery');
}}
>
Самовывоз
From d6967064ccaa01717be04de882407c41deff1864 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 28 Dec 2023 21:35:07 +0300
Subject: [PATCH 056/141] feat: add popup with errors on checkout
---
src/pages/checkout/index.tsx | 173 +++++++++++++++++++++--------------
1 file changed, 106 insertions(+), 67 deletions(-)
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index f536d6cb..61370070 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -1,9 +1,11 @@
-import React, { useEffect } from 'react';
+import React, { useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import styles from './checkout.module.scss';
import api from '@services/api.ts';
import Input from '@ui/input';
import { useFormAndValidation } from '@hooks/use-form-and-validation.ts';
+import { usePopup } from '@hooks/use-popup';
+import PopupCheckoutResponse from '@components/popups/popup-checkout-response';
import { OrderPostAdd } from '@services/generated-api/data-contracts.ts';
import { useAuth } from '@hooks/use-auth.ts';
import { useCart } from '@hooks/use-cart-context.ts';
@@ -16,47 +18,78 @@ type Address = {
const Checkout: React.FC = () => {
const { isLoggedIn, user } = useAuth();
const { loadCartData, cartData } = useCart();
+ const { handleOpenPopup, handleClosePopup } = usePopup();
const location = useLocation();
- const recievedType = location.state?.orderType;
+ const receivedType = location.state?.orderType;
+ const deliveryType = receivedType ? receivedType : 'Point of delivery';
const { values, handleChange, errors, isValid } = useFormAndValidation();
const navigate = useNavigate();
-
- const [deliveryType, setDeliveryType] = React.useState('shipment');
const [selectedPayment, setSelectedPayment] = React.useState('');
const [selectedTime, setSelectedTime] = React.useState('');
const [selectedAddress, setSelectedAddress] = React.useState('');
- const [actualDeliveryType, setActualDeliveryType] = React.useState('');
const userAddresses = user?.addresses as unknown[] as Address[];
const [comment, setComment] = React.useState('');
+ const [popupText, setPopupText] = useState('');
const addressesById = {
- 1: 'Ленина, 23, кв. 19',
- 2: 'Улица 2, дом 5',
- 3: 'Другой адрес и т.д.',
+ 1: 'Санкт-Петербург Невский проспект 17',
+ 2: 'Санкт-Петербург Горохова улица 10',
+ 3: 'Санкт-Петербург Невский проспект 89',
+ 4: 'Санкт-Петербург Большой Самсониевский 6',
+ 5: 'Санкт-Петербург Лесной проспект 56',
+ 6: 'Санкт-Петербург Владимирский проспект 1',
+ 7: 'Санкт-Петербург Лиговский проспект 170',
+ };
+
+ const openInfoPopup = (text: string) => {
+ setPopupText(text);
+ handleOpenPopup('openPopupCheckoutResponse');
};
const handleSubmitOrder = () => {
- if (isLoggedIn) {
- if (
- !selectedPayment ||
- !actualDeliveryType ||
- (actualDeliveryType !== 'shipment' && !selectedAddress?.toString().trim())
- ) {
- alert('Пожалуйста, заполните все обязательные поля');
+ switch (true) {
+ case isLoggedIn && !user.first_name:
+ openInfoPopup('Пожалуйста, заполните имя в личном кабинете');
+ return;
+ case isLoggedIn && !user.last_name:
+ openInfoPopup('Пожалуйста, заполните фамилию в личном кабинете');
+ return;
+ case isLoggedIn && !user.phone_number:
+ openInfoPopup('Пожалуйста, заполните номер телефона в личном кабинете');
return;
- }
- } else {
- if (
- !values.order_firstName?.toString().trim() ||
- !values.order_lastName?.toString().trim() ||
- !values.order_phoneNumber?.toString().trim() ||
- !values.order_email?.toString().trim() ||
- !selectedPayment ||
- !actualDeliveryType ||
- (actualDeliveryType !== 'shipment' && !selectedAddress?.toString().trim())
- ) {
- alert('Пожалуйста, заполните все обязательные поля');
+ case isLoggedIn && !selectedAddress?.toString().trim():
+ openInfoPopup(
+ 'Пожалуйста, выберите адрес (добавить адрес можно в личном кабинете)'
+ );
+ return;
+ case isLoggedIn && !selectedPayment:
+ openInfoPopup('Пожалуйста, выберите способ оплаты');
+ return;
+ }
+
+ switch (true) {
+ case !isLoggedIn && !values.order_firstName?.toString().trim():
+ openInfoPopup('Пожалуйста, заполните имя');
+ return;
+ case !isLoggedIn && !values.order_lastName?.toString().trim():
+ openInfoPopup('Пожалуйста, заполните фамилию');
+ return;
+ case !isLoggedIn && !values.order_phoneNumber?.toString().trim():
+ openInfoPopup('Пожалуйста, заполните номер телефона');
+ return;
+ case !isLoggedIn && !values.order_email?.toString().trim():
+ openInfoPopup('Пожалуйста, заполните e-mail');
+ return;
+ case !isLoggedIn &&
+ deliveryType === 'Point of delivery' &&
+ !selectedAddress?.toString().trim():
+ openInfoPopup('Пожалуйста, выберите адрес');
+ return;
+ case !isLoggedIn && !selectedAddress?.toString().trim():
+ openInfoPopup('Пожалуйста, введите адрес');
+ return;
+ case !isLoggedIn && !selectedPayment:
+ openInfoPopup('Пожалуйста, выберите способ оплаты');
return;
- }
}
let formData: OrderPostAdd = {
@@ -69,15 +102,18 @@ const Checkout: React.FC = () => {
payment_method: selectedPayment as
| 'Payment at the point of delivery'
| 'In getting by cash',
- delivery_method: actualDeliveryType as 'Point of delivery' | 'By courier',
+ delivery_method: deliveryType as 'Point of delivery' | 'By courier',
delivery_point:
- actualDeliveryType === 'shipment' ? Number(selectedAddress) || 2 : 2,
+ deliveryType === 'Point of delivery' ? Number(selectedAddress) : null,
package: 0,
comment: comment,
add_address: selectedAddress || '',
};
- if (isLoggedIn && actualDeliveryType === 'By courier') {
+ deliveryType === 'By courier' && delete formData.delivery_point;
+ isLoggedIn && delete formData.user_data;
+
+ if (isLoggedIn && deliveryType === 'By courier') {
delete formData.add_address;
formData = { ...formData, address: parseInt(selectedAddress, 10) };
}
@@ -89,12 +125,10 @@ const Checkout: React.FC = () => {
loadCartData();
})
.catch((error) => {
- console.log(error);
- if (error.response && error.response.data && error.response.data.errors) {
- const errorMessage = error.response.data.errors;
- alert('Ошибка при создании заказа: ' + errorMessage);
+ if (error.errors[0].detail) {
+ openInfoPopup('Ошибка при создании заказа: ' + error.errors[0].detail);
} else {
- alert('Произошла ошибка при создании заказа.');
+ openInfoPopup('Произошла ошибка при создании заказа.');
}
});
};
@@ -116,21 +150,10 @@ const Checkout: React.FC = () => {
setSelectedAddress(event.target.value);
};
- React.useState(() => {
- if (!recievedType) {
- setDeliveryType('shipment');
- } else {
- setDeliveryType(recievedType);
- }
- });
-
- useEffect(() => {
- if (deliveryType === 'shipment') {
- setActualDeliveryType('By courier');
- } else {
- setActualDeliveryType('Point of delivery');
- }
- }, [deliveryType]);
+ const handleClose = () => {
+ handleClosePopup('openPopupCheckoutResponse');
+ setPopupText('');
+ };
return (
-
-
- Оплата в пункте выдачи
-
+ {deliveryType === 'By courier' && (
+
+
+ Оплата курьеру
+
+ )}
+ {deliveryType === 'Point of delivery' && (
+
+
+ Оплата в пункте выдачи
+
+ )}
@@ -411,7 +449,7 @@ const Checkout: React.FC = () => {
- {deliveryType === 'shipment' ? 'Доставка' : 'Самовывоз'}
+ {deliveryType === 'By courier' ? 'Доставка' : 'Самовывоз'}
0 руб.
@@ -443,6 +481,7 @@ const Checkout: React.FC = () => {
+
);
};
From a43c761e97b7e106b59465f23145eb6761f1c52a Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 3 Jan 2024 20:07:26 +0300
Subject: [PATCH 057/141] refactor: move switch to separate function and add
default
---
src/pages/checkout/index.tsx | 53 ++++++++++++++++--------------------
1 file changed, 24 insertions(+), 29 deletions(-)
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index 61370070..19647ba2 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -21,7 +21,7 @@ const Checkout: React.FC = () => {
const { handleOpenPopup, handleClosePopup } = usePopup();
const location = useLocation();
const receivedType = location.state?.orderType;
- const deliveryType = receivedType ? receivedType : 'Point of delivery';
+ const deliveryType = receivedType || 'Point of delivery';
const { values, handleChange, errors, isValid } = useFormAndValidation();
const navigate = useNavigate();
const [selectedPayment, setSelectedPayment] = React.useState('');
@@ -45,52 +45,47 @@ const Checkout: React.FC = () => {
handleOpenPopup('openPopupCheckoutResponse');
};
- const handleSubmitOrder = () => {
+ const validateOrderData = () => {
switch (true) {
case isLoggedIn && !user.first_name:
- openInfoPopup('Пожалуйста, заполните имя в личном кабинете');
- return;
+ return openInfoPopup('Пожалуйста, заполните имя в личном кабинете');
case isLoggedIn && !user.last_name:
- openInfoPopup('Пожалуйста, заполните фамилию в личном кабинете');
- return;
+ return openInfoPopup('Пожалуйста, заполните фамилию в личном кабинете');
case isLoggedIn && !user.phone_number:
- openInfoPopup('Пожалуйста, заполните номер телефона в личном кабинете');
- return;
+ return openInfoPopup('Пожалуйста, заполните номер телефона в личном кабинете');
+ case isLoggedIn &&
+ deliveryType === 'Point of delivery' &&
+ !selectedAddress?.toString().trim():
+ return openInfoPopup('Пожалуйста, выберите адрес');
case isLoggedIn && !selectedAddress?.toString().trim():
- openInfoPopup(
+ return openInfoPopup(
'Пожалуйста, выберите адрес (добавить адрес можно в личном кабинете)'
);
- return;
case isLoggedIn && !selectedPayment:
- openInfoPopup('Пожалуйста, выберите способ оплаты');
- return;
- }
-
- switch (true) {
+ return openInfoPopup('Пожалуйста, выберите способ оплаты');
case !isLoggedIn && !values.order_firstName?.toString().trim():
- openInfoPopup('Пожалуйста, заполните имя');
- return;
+ return openInfoPopup('Пожалуйста, заполните имя');
case !isLoggedIn && !values.order_lastName?.toString().trim():
- openInfoPopup('Пожалуйста, заполните фамилию');
- return;
+ return openInfoPopup('Пожалуйста, заполните фамилию');
case !isLoggedIn && !values.order_phoneNumber?.toString().trim():
- openInfoPopup('Пожалуйста, заполните номер телефона');
- return;
+ return openInfoPopup('Пожалуйста, заполните номер телефона');
case !isLoggedIn && !values.order_email?.toString().trim():
- openInfoPopup('Пожалуйста, заполните e-mail');
- return;
+ return openInfoPopup('Пожалуйста, заполните e-mail');
case !isLoggedIn &&
deliveryType === 'Point of delivery' &&
!selectedAddress?.toString().trim():
- openInfoPopup('Пожалуйста, выберите адрес');
- return;
+ return openInfoPopup('Пожалуйста, выберите адрес');
case !isLoggedIn && !selectedAddress?.toString().trim():
- openInfoPopup('Пожалуйста, введите адрес');
- return;
+ return openInfoPopup('Пожалуйста, введите адрес');
case !isLoggedIn && !selectedPayment:
- openInfoPopup('Пожалуйста, выберите способ оплаты');
- return;
+ return openInfoPopup('Пожалуйста, выберите способ оплаты');
+ default:
+ return true;
}
+ };
+
+ const handleSubmitOrder = () => {
+ if (!validateOrderData()) return;
let formData: OrderPostAdd = {
user_data: {
From 68965cb94d9b5384fac11dbf5eab8db5a1b2bc07 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 3 Jan 2024 20:14:01 +0300
Subject: [PATCH 058/141] refactor: move pickupPointAddresses to constants file
---
src/data/constants.ts | 10 ++++++++++
src/pages/checkout/index.tsx | 12 ++----------
2 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/src/data/constants.ts b/src/data/constants.ts
index e00b08e5..08d62dd3 100644
--- a/src/data/constants.ts
+++ b/src/data/constants.ts
@@ -8,3 +8,13 @@ export const URLS = {
AGREEMENT: '/agreement',
DELIVERY_COND: '/delivery-conditions',
};
+
+export const pickupPointAddresses = {
+ 1: 'Санкт-Петербург Невский проспект 17',
+ 2: 'Санкт-Петербург Горохова улица 10',
+ 3: 'Санкт-Петербург Невский проспект 89',
+ 4: 'Санкт-Петербург Большой Самсониевский 6',
+ 5: 'Санкт-Петербург Лесной проспект 56',
+ 6: 'Санкт-Петербург Владимирский проспект 1',
+ 7: 'Санкт-Петербург Лиговский проспект 170',
+};
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index 19647ba2..29deee2a 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -9,6 +9,7 @@ import PopupCheckoutResponse from '@components/popups/popup-checkout-response';
import { OrderPostAdd } from '@services/generated-api/data-contracts.ts';
import { useAuth } from '@hooks/use-auth.ts';
import { useCart } from '@hooks/use-cart-context.ts';
+import { pickupPointAddresses } from '@data/constants';
type Address = {
id: number;
@@ -30,15 +31,6 @@ const Checkout: React.FC = () => {
const userAddresses = user?.addresses as unknown[] as Address[];
const [comment, setComment] = React.useState('');
const [popupText, setPopupText] = useState('');
- const addressesById = {
- 1: 'Санкт-Петербург Невский проспект 17',
- 2: 'Санкт-Петербург Горохова улица 10',
- 3: 'Санкт-Петербург Невский проспект 89',
- 4: 'Санкт-Петербург Большой Самсониевский 6',
- 5: 'Санкт-Петербург Лесной проспект 56',
- 6: 'Санкт-Петербург Владимирский проспект 1',
- 7: 'Санкт-Петербург Лиговский проспект 170',
- };
const openInfoPopup = (text: string) => {
setPopupText(text);
@@ -250,7 +242,7 @@ const Checkout: React.FC = () => {
Выберите адрес
- {Object.entries(addressesById).map(([id, address]) => (
+ {Object.entries(pickupPointAddresses).map(([id, address]) => (
Date: Wed, 3 Jan 2024 20:42:42 +0300
Subject: [PATCH 059/141] refactor: add enums for delivery types and payments
types
---
src/pages/checkout/index.tsx | 42 ++++++++++++++------
src/services/generated-api/data-contracts.ts | 4 +-
2 files changed, 31 insertions(+), 15 deletions(-)
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index 29deee2a..5aec49e6 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -16,13 +16,24 @@ type Address = {
address: string;
};
+enum deliveryTypeEnum {
+ pointOfDelivery = 'Point of delivery',
+ byCourier = 'By courier',
+}
+
+enum paymentMethodEnum {
+ pointOfDelivery = 'Payment at the point of delivery',
+ onDelivery = 'In getting by cash',
+ online = 'Online',
+}
+
const Checkout: React.FC = () => {
const { isLoggedIn, user } = useAuth();
const { loadCartData, cartData } = useCart();
const { handleOpenPopup, handleClosePopup } = usePopup();
const location = useLocation();
const receivedType = location.state?.orderType;
- const deliveryType = receivedType || 'Point of delivery';
+ const deliveryType = receivedType || deliveryTypeEnum.pointOfDelivery;
const { values, handleChange, errors, isValid } = useFormAndValidation();
const navigate = useNavigate();
const [selectedPayment, setSelectedPayment] = React.useState('');
@@ -46,7 +57,7 @@ const Checkout: React.FC = () => {
case isLoggedIn && !user.phone_number:
return openInfoPopup('Пожалуйста, заполните номер телефона в личном кабинете');
case isLoggedIn &&
- deliveryType === 'Point of delivery' &&
+ deliveryType === deliveryTypeEnum.pointOfDelivery &&
!selectedAddress?.toString().trim():
return openInfoPopup('Пожалуйста, выберите адрес');
case isLoggedIn && !selectedAddress?.toString().trim():
@@ -64,7 +75,7 @@ const Checkout: React.FC = () => {
case !isLoggedIn && !values.order_email?.toString().trim():
return openInfoPopup('Пожалуйста, заполните e-mail');
case !isLoggedIn &&
- deliveryType === 'Point of delivery' &&
+ deliveryType === deliveryTypeEnum.pointOfDelivery &&
!selectedAddress?.toString().trim():
return openInfoPopup('Пожалуйста, выберите адрес');
case !isLoggedIn && !selectedAddress?.toString().trim():
@@ -87,20 +98,25 @@ const Checkout: React.FC = () => {
email: values.order_email?.toString() || '',
},
payment_method: selectedPayment as
- | 'Payment at the point of delivery'
- | 'In getting by cash',
- delivery_method: deliveryType as 'Point of delivery' | 'By courier',
+ | paymentMethodEnum.pointOfDelivery
+ | paymentMethodEnum.onDelivery
+ | paymentMethodEnum.online,
+ delivery_method: deliveryType as
+ | deliveryTypeEnum.pointOfDelivery
+ | deliveryTypeEnum.byCourier,
delivery_point:
- deliveryType === 'Point of delivery' ? Number(selectedAddress) : null,
+ deliveryType === deliveryTypeEnum.pointOfDelivery
+ ? Number(selectedAddress)
+ : null,
package: 0,
comment: comment,
add_address: selectedAddress || '',
};
- deliveryType === 'By courier' && delete formData.delivery_point;
+ deliveryType === deliveryTypeEnum.byCourier && delete formData.delivery_point;
isLoggedIn && delete formData.user_data;
- if (isLoggedIn && deliveryType === 'By courier') {
+ if (isLoggedIn && deliveryType === deliveryTypeEnum.byCourier) {
delete formData.add_address;
formData = { ...formData, address: parseInt(selectedAddress, 10) };
}
@@ -230,7 +246,7 @@ const Checkout: React.FC = () => {
)}
- {deliveryType !== 'By courier' ? (
+ {deliveryType !== deliveryTypeEnum.byCourier ? (
<>
Адрес пункта самовывоза
{selectedAddress !== null ? (
@@ -381,7 +397,7 @@ const Checkout: React.FC = () => {
Оплата онлайн
- {deliveryType === 'By courier' && (
+ {deliveryType === deliveryTypeEnum.byCourier && (
{
Оплата курьеру
)}
- {deliveryType === 'Point of delivery' && (
+ {deliveryType === deliveryTypeEnum.pointOfDelivery && (
{
- {deliveryType === 'By courier' ? 'Доставка' : 'Самовывоз'}
+ {deliveryType === deliveryTypeEnum.byCourier ? 'Доставка' : 'Самовывоз'}
0 руб.
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index 6f7dd8fb..670a1fbb 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -1006,7 +1006,7 @@ export interface OrderList {
| 'Delivered'
| 'Completed';
/** Payment Method */
- payment_method?: 'Payment at the point of delivery' | 'In getting by cash';
+ payment_method?: 'Payment at the point of delivery' | 'In getting by cash' | 'Online';
/** Is paid */
is_paid?: boolean;
/** Delivery Method */
@@ -1038,7 +1038,7 @@ export interface OrderList {
export interface OrderPostDelete {
/** Payment Method */
- payment_method?: 'Payment at the point of delivery' | 'In getting by cash';
+ payment_method?: 'Payment at the point of delivery' | 'In getting by cash' | 'Online';
/** Delivery Method */
delivery_method?: 'Point of delivery' | 'By courier';
/** Delivery Point */
From 1771eddcead5f353f5fee1da8e51d84e746eeea2 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 3 Jan 2024 20:58:33 +0300
Subject: [PATCH 060/141] refactor: (rebase) add links to constants object
---
src/data/constants.ts | 2 ++
src/pages/checkout/checkout-success/index.tsx | 3 ++-
src/pages/checkout/index.tsx | 4 ++--
3 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/data/constants.ts b/src/data/constants.ts
index 08d62dd3..97b63095 100644
--- a/src/data/constants.ts
+++ b/src/data/constants.ts
@@ -7,6 +7,8 @@ export const URLS = {
CATALOG: '/catalog',
AGREEMENT: '/agreement',
DELIVERY_COND: '/delivery-conditions',
+ CART_SUCCESS: '/cart/success',
+ PROFILE_ORDERS: '/profile/orders/',
};
export const pickupPointAddresses = {
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 9124b751..6caa5d0e 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import OurBlock from '@components/our-block';
import OrderStatusTracker from '@components/order-status-tracker';
+import { URLS } from '@data/constants';
import styles from './checkout-success.module.scss';
@@ -29,7 +30,7 @@ const CheckoutSuccess: React.FC = () => {
Мы уже приступили к его сборке.
За статусом заказа можно следить в{' '}
-
+
личном кабинете
.
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index 5aec49e6..08c55ccf 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -9,7 +9,7 @@ import PopupCheckoutResponse from '@components/popups/popup-checkout-response';
import { OrderPostAdd } from '@services/generated-api/data-contracts.ts';
import { useAuth } from '@hooks/use-auth.ts';
import { useCart } from '@hooks/use-cart-context.ts';
-import { pickupPointAddresses } from '@data/constants';
+import { pickupPointAddresses, URLS } from '@data/constants';
type Address = {
id: number;
@@ -124,7 +124,7 @@ const Checkout: React.FC = () => {
api
.usersOrderCreate(formData)
.then((res) => {
- navigate('/cart/success', { state: { order: res.order_number } });
+ navigate(URLS.CART_SUCCESS, { state: { order: res.order_number } });
loadCartData();
})
.catch((error) => {
From 05dd9a8b841c59a177927820bc2f2a3d703bf239 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 3 Jan 2024 21:41:18 +0300
Subject: [PATCH 061/141] refactor: move popup text to constants
---
src/data/constants.ts | 17 +++++++++++++++++
src/pages/checkout/index.tsx | 34 ++++++++++++++++------------------
2 files changed, 33 insertions(+), 18 deletions(-)
diff --git a/src/data/constants.ts b/src/data/constants.ts
index 97b63095..7c1f70cb 100644
--- a/src/data/constants.ts
+++ b/src/data/constants.ts
@@ -20,3 +20,20 @@ export const pickupPointAddresses = {
6: 'Санкт-Петербург Владимирский проспект 1',
7: 'Санкт-Петербург Лиговский проспект 170',
};
+
+export const popupInfoText = {
+ fillNameAuth: 'Пожалуйста, заполните имя в личном кабинете',
+ fillSurnameAuth: 'Пожалуйста, заполните фамилию в личном кабинете',
+ fillPhoneAuth: 'Пожалуйста, заполните номер телефона в личном кабинете',
+ chooseAddress: 'Пожалуйста, выберите адрес',
+ chooseOrFillAddress:
+ 'Пожалуйста, выберите адрес (добавить адрес можно в личном кабинете)',
+ choosePaymentMethod: 'Пожалуйста, выберите способ оплаты',
+ enterSurname: 'Пожалуйста, заполните фамилию',
+ enterName: 'Пожалуйста, заполните имя',
+ enterPhone: 'Пожалуйста, заполните номер телефона',
+ enterEmail: 'Пожалуйста, заполните e-mail',
+ enterAddress: 'Пожалуйста, введите адрес',
+ errorShort: 'Ошибка при создании заказа: ',
+ errorLong: 'Ошибка при создании заказа: ',
+};
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index 08c55ccf..0f65ee88 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -9,7 +9,7 @@ import PopupCheckoutResponse from '@components/popups/popup-checkout-response';
import { OrderPostAdd } from '@services/generated-api/data-contracts.ts';
import { useAuth } from '@hooks/use-auth.ts';
import { useCart } from '@hooks/use-cart-context.ts';
-import { pickupPointAddresses, URLS } from '@data/constants';
+import { pickupPointAddresses, URLS, popupInfoText } from '@data/constants';
type Address = {
id: number;
@@ -51,37 +51,35 @@ const Checkout: React.FC = () => {
const validateOrderData = () => {
switch (true) {
case isLoggedIn && !user.first_name:
- return openInfoPopup('Пожалуйста, заполните имя в личном кабинете');
+ return openInfoPopup(popupInfoText.fillNameAuth);
case isLoggedIn && !user.last_name:
- return openInfoPopup('Пожалуйста, заполните фамилию в личном кабинете');
+ return openInfoPopup(popupInfoText.fillSurnameAuth);
case isLoggedIn && !user.phone_number:
- return openInfoPopup('Пожалуйста, заполните номер телефона в личном кабинете');
+ return openInfoPopup(popupInfoText.fillPhoneAuth);
case isLoggedIn &&
deliveryType === deliveryTypeEnum.pointOfDelivery &&
!selectedAddress?.toString().trim():
- return openInfoPopup('Пожалуйста, выберите адрес');
+ return openInfoPopup(popupInfoText.chooseAddress);
case isLoggedIn && !selectedAddress?.toString().trim():
- return openInfoPopup(
- 'Пожалуйста, выберите адрес (добавить адрес можно в личном кабинете)'
- );
+ return openInfoPopup(popupInfoText.chooseOrFillAddress);
case isLoggedIn && !selectedPayment:
- return openInfoPopup('Пожалуйста, выберите способ оплаты');
+ return openInfoPopup(popupInfoText.choosePaymentMethod);
case !isLoggedIn && !values.order_firstName?.toString().trim():
- return openInfoPopup('Пожалуйста, заполните имя');
+ return openInfoPopup(popupInfoText.enterName);
case !isLoggedIn && !values.order_lastName?.toString().trim():
- return openInfoPopup('Пожалуйста, заполните фамилию');
+ return openInfoPopup(popupInfoText.enterSurname);
case !isLoggedIn && !values.order_phoneNumber?.toString().trim():
- return openInfoPopup('Пожалуйста, заполните номер телефона');
+ return openInfoPopup(popupInfoText.enterPhone);
case !isLoggedIn && !values.order_email?.toString().trim():
- return openInfoPopup('Пожалуйста, заполните e-mail');
+ return openInfoPopup(popupInfoText.enterEmail);
case !isLoggedIn &&
deliveryType === deliveryTypeEnum.pointOfDelivery &&
!selectedAddress?.toString().trim():
- return openInfoPopup('Пожалуйста, выберите адрес');
+ return openInfoPopup(popupInfoText.chooseAddress);
case !isLoggedIn && !selectedAddress?.toString().trim():
- return openInfoPopup('Пожалуйста, введите адрес');
+ return openInfoPopup(popupInfoText.enterAddress);
case !isLoggedIn && !selectedPayment:
- return openInfoPopup('Пожалуйста, выберите способ оплаты');
+ return openInfoPopup(popupInfoText.choosePaymentMethod);
default:
return true;
}
@@ -129,9 +127,9 @@ const Checkout: React.FC = () => {
})
.catch((error) => {
if (error.errors[0].detail) {
- openInfoPopup('Ошибка при создании заказа: ' + error.errors[0].detail);
+ openInfoPopup(popupInfoText.errorShort + error.errors[0].detail);
} else {
- openInfoPopup('Произошла ошибка при создании заказа.');
+ openInfoPopup(popupInfoText.errorLong);
}
});
};
From 799423065e860ba70f7c77af70118ee24e821c7d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 15:56:35 +0300
Subject: [PATCH 062/141] docs: change workflow file to stop autodeploy from
develop
---
.github/workflows/good_food_frontend_workflow.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/good_food_frontend_workflow.yaml b/.github/workflows/good_food_frontend_workflow.yaml
index 249396ac..e2d65e68 100644
--- a/.github/workflows/good_food_frontend_workflow.yaml
+++ b/.github/workflows/good_food_frontend_workflow.yaml
@@ -32,7 +32,7 @@ jobs:
npm run lint:styles
npm run lint:prettier
- name: send message
- if: ${{ github.ref != 'refs/heads/develop' || github.ref != 'refs/heads/main' }}
+ if: ${{ github.ref != 'refs/heads/main' }}
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_TO }}
@@ -49,7 +49,7 @@ jobs:
build_and_push_to_docker_hub:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
- if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main'
+ if: github.ref == 'refs/heads/main'
steps:
- name: Check out the repo
From da6ffb446bb51f9752d5abee94de8ed44ffe98e6 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Thu, 28 Dec 2023 22:26:50 +0200
Subject: [PATCH 063/141] feat: add ratings and reviews structure, add
rating-stars component
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил внутреннюю структуру компонентов для фичи "Рейтинги и отзывы". Добавил компонент Rating
Stars
---
src/App.tsx | 3 ++
src/assets/images/star-rating-filled.svg | 3 ++
src/assets/images/star-rating-outlined.svg | 3 ++
.../rating-stars/index.tsx | 42 +++++++++++++++++++
.../rating-stars/rating-stars.module.scss | 39 +++++++++++++++++
5 files changed, 90 insertions(+)
create mode 100644 src/assets/images/star-rating-filled.svg
create mode 100644 src/assets/images/star-rating-outlined.svg
create mode 100644 src/components/ratings-and-reviews-components/rating-stars/index.tsx
create mode 100644 src/components/ratings-and-reviews-components/rating-stars/rating-stars.module.scss
diff --git a/src/App.tsx b/src/App.tsx
index 34f3cc7a..9250d575 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,6 +24,7 @@ import RecipeList from '@pages/recipe-list/index.tsx';
import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
+import RatingStars from '@components/ratings-and-reviews-components/rating-stars/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -52,6 +53,8 @@ function App() {
} />
} />
} />
+ {/* // TODO: Clean up after Ratings and Reviews task completed */}
+ } />
}
diff --git a/src/assets/images/star-rating-filled.svg b/src/assets/images/star-rating-filled.svg
new file mode 100644
index 00000000..f661d524
--- /dev/null
+++ b/src/assets/images/star-rating-filled.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/assets/images/star-rating-outlined.svg b/src/assets/images/star-rating-outlined.svg
new file mode 100644
index 00000000..1086969e
--- /dev/null
+++ b/src/assets/images/star-rating-outlined.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/components/ratings-and-reviews-components/rating-stars/index.tsx b/src/components/ratings-and-reviews-components/rating-stars/index.tsx
new file mode 100644
index 00000000..01ccdbac
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/rating-stars/index.tsx
@@ -0,0 +1,42 @@
+import React, { useState } from 'react';
+
+import styles from './rating-stars.module.scss';
+
+const RatingStars: React.FC<{ defaultRating: number }> = ({ defaultRating }) => {
+ const [rating, setRating] = useState(defaultRating);
+ const [hover, setHover] = useState(0);
+ const starsArray = [1, 2, 3, 4, 5];
+
+ return (
+ setHover(0)}
+ >
+ {starsArray.map((el) => (
+
+ {
+ setRating(el);
+ }}
+ // NOTE: Reset rating on click on the star, corresponding to current rating
+ onClick={() => el === rating && setRating(0)}
+ />
+ setHover(el)}
+ />
+
+ ))}
+
+ );
+};
+
+export default RatingStars;
diff --git a/src/components/ratings-and-reviews-components/rating-stars/rating-stars.module.scss b/src/components/ratings-and-reviews-components/rating-stars/rating-stars.module.scss
new file mode 100644
index 00000000..a7f63765
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/rating-stars/rating-stars.module.scss
@@ -0,0 +1,39 @@
+.container {
+ padding: 0;
+ margin: 0;
+ background: none;
+ border: none;
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+}
+
+.item {
+ padding: 0;
+ margin: 0;
+}
+
+.input {
+ display: none;
+}
+
+.star {
+ display: block;
+ padding: 0;
+ margin: 0;
+ width: 24px;
+ aspect-ratio: 1;
+ background-image: url('@images/star-rating-outlined.svg');
+ background-color: transparent;
+ border: none;
+ cursor: pointer;
+
+ &.active,
+ &.hovered {
+ background-image: url('@images/star-rating-filled.svg');
+ }
+
+ @media screen and (width <= 768px) {
+ width: 20px;
+ }
+}
From 156ffcca2d37f794b3bc317686f5334568931682 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 30 Dec 2023 00:03:31 +0200
Subject: [PATCH 064/141] feat: add review-and-rating-post-form component
layout
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил верстку формы для отзыва и оценки
---
src/App.tsx | 7 ++-
.../rating-stars/index.tsx | 8 ++-
.../rating-stars/rating-stars.module.scss | 2 +
.../review-and-rating-post-form/index.tsx | 55 +++++++++++++++++++
.../review-and-rating-post-form.module.scss | 39 +++++++++++++
.../types/index.ts | 9 +++
6 files changed, 115 insertions(+), 5 deletions(-)
create mode 100644 src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
create mode 100644 src/components/ratings-and-reviews-components/review-and-rating-post-form/review-and-rating-post-form.module.scss
create mode 100644 src/components/ratings-and-reviews-components/types/index.ts
diff --git a/src/App.tsx b/src/App.tsx
index 9250d575..591e30de 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,7 +24,7 @@ import RecipeList from '@pages/recipe-list/index.tsx';
import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
-import RatingStars from '@components/ratings-and-reviews-components/rating-stars/index.tsx';
+import ReviewAndRatingPostForm from '@components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -54,7 +54,10 @@ function App() {
} />
} />
{/* // TODO: Clean up after Ratings and Reviews task completed */}
- } />
+ }
+ />
}
diff --git a/src/components/ratings-and-reviews-components/rating-stars/index.tsx b/src/components/ratings-and-reviews-components/rating-stars/index.tsx
index 01ccdbac..7cd1cd73 100644
--- a/src/components/ratings-and-reviews-components/rating-stars/index.tsx
+++ b/src/components/ratings-and-reviews-components/rating-stars/index.tsx
@@ -2,7 +2,11 @@ import React, { useState } from 'react';
import styles from './rating-stars.module.scss';
-const RatingStars: React.FC<{ defaultRating: number }> = ({ defaultRating }) => {
+interface IRatingStars {
+ defaultRating: number;
+}
+
+const RatingStars: React.FC = ({ defaultRating }) => {
const [rating, setRating] = useState(defaultRating);
const [hover, setHover] = useState(0);
const starsArray = [1, 2, 3, 4, 5];
@@ -24,8 +28,6 @@ const RatingStars: React.FC<{ defaultRating: number }> = ({ defaultRating }) =>
onChange={() => {
setRating(el);
}}
- // NOTE: Reset rating on click on the star, corresponding to current rating
- onClick={() => el === rating && setRating(0)}
/>
= ({ defaultReview, productId }) => {
+ const { values, setValues, handleChange } = useFormAndValidation({
+ text: '',
+ });
+
+ useEffect(() => {
+ // NOTE: Temporary output of product ID
+ console.log(productId, defaultReview);
+ defaultReview && setValues({ text: defaultReview?.text });
+ }, [defaultReview, productId, setValues]);
+
+ const handleSubmit = (e: SyntheticEvent) => {
+ e.preventDefault();
+ console.log('submited');
+ };
+
+ return (
+
+
+ {defaultReview ? `Вы оценили товар ${defaultReview.pub_date}` : 'Оценить товар'}
+
+ {/* // NOTE: Rating is handling it's api */}
+
+
+
+ );
+};
+
+export default ReviewAndRatingPostForm;
diff --git a/src/components/ratings-and-reviews-components/review-and-rating-post-form/review-and-rating-post-form.module.scss b/src/components/ratings-and-reviews-components/review-and-rating-post-form/review-and-rating-post-form.module.scss
new file mode 100644
index 00000000..a445672b
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/review-and-rating-post-form/review-and-rating-post-form.module.scss
@@ -0,0 +1,39 @@
+@use '@scss/_variables' as *;
+
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+
+ @media screen and (width <= 768px) {
+ gap: 12px;
+ }
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+
+ @media screen and (width <= 768px) {
+ gap: 12px;
+ }
+}
+
+.input {
+ box-sizing: border-box;
+ padding: 20px;
+ min-height: 120px;
+ border-radius: 16px;
+ border: 1px solid $footer-background-color;
+ outline: none;
+ font-family: $ubuntu-font;
+ font-size: 20px;
+ font-weight: 400;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 12px;
+ min-height: 104px;
+ }
+}
diff --git a/src/components/ratings-and-reviews-components/types/index.ts b/src/components/ratings-and-reviews-components/types/index.ts
new file mode 100644
index 00000000..9bea9426
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/types/index.ts
@@ -0,0 +1,9 @@
+export interface IReview {
+ id: number;
+ author: string;
+ product: string;
+ score: number;
+ pub_date: string;
+ was_edited: boolean;
+ text: string;
+}
From 61dd60b9193042843a9b0d0580b7f87d2b4e9722 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 30 Dec 2023 15:28:34 +0200
Subject: [PATCH 065/141] feat: add review and rating requests logic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил логику запросов для добавления и исправления оценки и отзыва в компоненте Review And Rating
Post Form. Добавил нужные типы в Data Contracts и функции запросов в Api
---
.../rating-stars/index.tsx | 9 ++--
.../review-and-rating-post-form/index.tsx | 52 ++++++++++++++-----
src/services/api.ts | 31 +++++++++++
src/services/generated-api/data-contracts.ts | 21 ++++++++
4 files changed, 96 insertions(+), 17 deletions(-)
diff --git a/src/components/ratings-and-reviews-components/rating-stars/index.tsx b/src/components/ratings-and-reviews-components/rating-stars/index.tsx
index 7cd1cd73..bd5a6788 100644
--- a/src/components/ratings-and-reviews-components/rating-stars/index.tsx
+++ b/src/components/ratings-and-reviews-components/rating-stars/index.tsx
@@ -1,13 +1,12 @@
import React, { useState } from 'react';
-
import styles from './rating-stars.module.scss';
interface IRatingStars {
- defaultRating: number;
+ rating: number;
+ onChange: (rating: number) => void;
}
-const RatingStars: React.FC = ({ defaultRating }) => {
- const [rating, setRating] = useState(defaultRating);
+const RatingStars: React.FC = ({ rating, onChange }) => {
const [hover, setHover] = useState(0);
const starsArray = [1, 2, 3, 4, 5];
@@ -26,7 +25,7 @@ const RatingStars: React.FC = ({ defaultRating }) => {
checked={el === rating}
value={el}
onChange={() => {
- setRating(el);
+ onChange(el);
}}
/>
= ({ defaultReview, productId }) => {
const { values, setValues, handleChange } = useFormAndValidation({
- text: '',
+ text: defaultReview?.text || '',
+ score: defaultReview?.score || 0,
});
- useEffect(() => {
- // NOTE: Temporary output of product ID
- console.log(productId, defaultReview);
- defaultReview && setValues({ text: defaultReview?.text });
- }, [defaultReview, productId, setValues]);
+ // useEffect(() => {
+ // // NOTE: Temporary output of product ID
+ // console.log(productId, defaultReview);
+ // defaultReview &&
+ // setValues({ text: defaultReview?.text, score: defaultReview?.score });
+ // }, [defaultReview, productId, setValues]);
- const handleSubmit = (e: SyntheticEvent) => {
+ const handleReviewSubmit = (e: SyntheticEvent) => {
e.preventDefault();
- console.log('submited');
+
+ // TODO: Fix then
+ // TODO: Fix catch
+ if (defaultReview?.score && values.text && values.text !== defaultReview?.text) {
+ api
+ .reviewsUpdate(productId, defaultReview.id, { text: values.text as string })
+ .then((res) => console.log(res))
+ .catch((err) => console.log(err));
+ console.log('submited');
+ }
+ };
+
+ const handleRatingChange = (rating: number) => {
+ // TODO: Fix then
+ // TODO: Fix catch
+ if (!defaultReview || defaultReview?.score !== rating) {
+ const apiPromise =
+ defaultReview && defaultReview.score > 0
+ ? api.reviewsUpdate(productId, defaultReview.id, {
+ score: rating,
+ })
+ : api.reviewsCreate(productId, { score: rating });
+ apiPromise
+ .then((res) => console.log(res))
+ .then(() => setValues({ ...values, score: rating }))
+ .catch((err) => console.log(err));
+ }
};
return (
@@ -29,9 +58,8 @@ const ReviewAndRatingPostForm: React.FC<{
{defaultReview ? `Вы оценили товар ${defaultReview.pub_date}` : 'Оценить товар'}
- {/* // NOTE: Rating is handling it's api */}
-
-
+
+
Date: Sat, 30 Dec 2023 17:06:57 +0200
Subject: [PATCH 066/141] feat: add review and rating-display components
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил компоненты Review и Rating Display для отображения одного отзыва
---
src/App.tsx | 18 +++++-
.../rating-display/index.tsx | 16 ++++++
.../rating-display/rating-display.module.scss | 27 +++++++++
.../{rating-stars => rating-input}/index.tsx | 14 ++---
.../rating-input.module.scss} | 5 +-
.../review-and-rating-post-form/index.tsx | 8 +--
.../review/index.tsx | 25 ++++++++
.../review/review.module.scss | 57 +++++++++++++++++++
.../types/index.ts | 9 ---
src/services/generated-api/data-contracts.ts | 39 +++++++++++--
10 files changed, 190 insertions(+), 28 deletions(-)
create mode 100644 src/components/ratings-and-reviews-components/rating-display/index.tsx
create mode 100644 src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss
rename src/components/ratings-and-reviews-components/{rating-stars => rating-input}/index.tsx (76%)
rename src/components/ratings-and-reviews-components/{rating-stars/rating-stars.module.scss => rating-input/rating-input.module.scss} (93%)
create mode 100644 src/components/ratings-and-reviews-components/review/index.tsx
create mode 100644 src/components/ratings-and-reviews-components/review/review.module.scss
delete mode 100644 src/components/ratings-and-reviews-components/types/index.ts
diff --git a/src/App.tsx b/src/App.tsx
index 591e30de..001c7843 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,7 +24,8 @@ import RecipeList from '@pages/recipe-list/index.tsx';
import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
-import ReviewAndRatingPostForm from '@components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx';
+// import ReviewAndRatingPostForm from '@components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx';
+import Review from '@components/ratings-and-reviews-components/review/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -56,7 +57,20 @@ function App() {
{/* // TODO: Clean up after Ratings and Reviews task completed */}
}
+ // element={ }
+ element={
+
+ }
/>
= ({ rating }) => {
+ const starsArray = [1, 2, 3, 4, 5];
+
+ return (
+
+ {starsArray.map((el) => (
+
+ ))}
+
+ );
+};
+
+export default RatingDisplay;
diff --git a/src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss b/src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss
new file mode 100644
index 00000000..3a07cbff
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss
@@ -0,0 +1,27 @@
+.container {
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+
+ @media screen and (width <= 768px) {
+ gap: 4px;
+ }
+}
+
+.star {
+ width: 24px;
+ aspect-ratio: 1;
+ background-image: url('@images/star-rating-outlined.svg');
+ background-position: center;
+ background-size: cover;
+
+ &.active {
+ background-image: url('@images/star-rating-filled.svg');
+ }
+
+ @media screen and (width <= 768px) {
+ width: 12px;
+ }
+}
diff --git a/src/components/ratings-and-reviews-components/rating-stars/index.tsx b/src/components/ratings-and-reviews-components/rating-input/index.tsx
similarity index 76%
rename from src/components/ratings-and-reviews-components/rating-stars/index.tsx
rename to src/components/ratings-and-reviews-components/rating-input/index.tsx
index bd5a6788..d8b57854 100644
--- a/src/components/ratings-and-reviews-components/rating-stars/index.tsx
+++ b/src/components/ratings-and-reviews-components/rating-input/index.tsx
@@ -1,12 +1,12 @@
import React, { useState } from 'react';
-import styles from './rating-stars.module.scss';
+import styles from './rating-input.module.scss';
-interface IRatingStars {
+interface RatingInput {
rating: number;
onChange: (rating: number) => void;
}
-const RatingStars: React.FC = ({ rating, onChange }) => {
+const RatingInput: React.FC = ({ rating, onChange }) => {
const [hover, setHover] = useState(0);
const starsArray = [1, 2, 3, 4, 5];
@@ -22,11 +22,9 @@ const RatingStars: React.FC = ({ rating, onChange }) => {
className={styles.input}
type="radio"
name="rating"
- checked={el === rating}
value={el}
- onChange={() => {
- onChange(el);
- }}
+ onChange={() => onChange(el)}
+ checked={el === rating}
/>
= ({ rating, onChange }) => {
);
};
-export default RatingStars;
+export default RatingInput;
diff --git a/src/components/ratings-and-reviews-components/rating-stars/rating-stars.module.scss b/src/components/ratings-and-reviews-components/rating-input/rating-input.module.scss
similarity index 93%
rename from src/components/ratings-and-reviews-components/rating-stars/rating-stars.module.scss
rename to src/components/ratings-and-reviews-components/rating-input/rating-input.module.scss
index 71224767..122c5e12 100644
--- a/src/components/ratings-and-reviews-components/rating-stars/rating-stars.module.scss
+++ b/src/components/ratings-and-reviews-components/rating-input/rating-input.module.scss
@@ -28,13 +28,16 @@
background-position: center;
background-size: cover;
border: none;
- cursor: pointer;
&.active,
&.hovered {
background-image: url('@images/star-rating-filled.svg');
}
+ .clickable {
+ cursor: pointer;
+ }
+
@media screen and (width <= 768px) {
width: 20px;
}
diff --git a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
index ae3ca24a..bf380e6e 100644
--- a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
+++ b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
@@ -1,13 +1,13 @@
import React, { SyntheticEvent } from 'react';
-import { IReview } from '../types';
+import { Review } from '@services/generated-api/data-contracts';
import api from '@services/api';
import { useFormAndValidation } from '@hooks/use-form-and-validation';
-import RatingStars from '@components/ratings-and-reviews-components/rating-stars';
+import RatingInput from '@components/ratings-and-reviews-components/rating-input';
import Button from '@components/Button';
import styles from './review-and-rating-post-form.module.scss';
const ReviewAndRatingPostForm: React.FC<{
- defaultReview: IReview | null;
+ defaultReview: Review | null;
productId: number;
}> = ({ defaultReview, productId }) => {
const { values, setValues, handleChange } = useFormAndValidation({
@@ -58,7 +58,7 @@ const ReviewAndRatingPostForm: React.FC<{
{defaultReview ? `Вы оценили товар ${defaultReview.pub_date}` : 'Оценить товар'}
-
+
= ({ review }) => {
+ return (
+
+
+
+ {review.author}
+
+ {new Date(review.pub_date).toLocaleDateString('ru-RU', dateOptions as never)}
+
+
+
+
+ {review.text}
+
+ );
+};
+
+export default Review;
diff --git a/src/components/ratings-and-reviews-components/review/review.module.scss b/src/components/ratings-and-reviews-components/review/review.module.scss
new file mode 100644
index 00000000..ce383cab
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/review/review.module.scss
@@ -0,0 +1,57 @@
+@use '@scss/_variables' as *;
+
+.container {
+ font-family: $ubuntu-font;
+ color: $active-text-color;
+ display: flex;
+ flex-direction: column;
+ gap: 28px;
+ box-sizing: border-box;
+ padding: 40px 40px 24px;
+ min-height: 178px;
+ border-radius: 16px;
+ border: 1px solid $footer-background-color;
+
+ @media screen and (width <= 768px) {
+ padding: 20px;
+ min-height: 104px;
+ gap: 12px;
+ }
+}
+
+.info {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.title {
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: row;
+ gap: 36px;
+ text-transform: uppercase;
+ font-size: 22px;
+ font-weight: 500;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 14px;
+ gap: 22px;
+ }
+}
+
+.text {
+ padding: 0;
+ margin: 0;
+ font-size: 20px;
+ font-weight: 400;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 14px;
+ font-weight: 500;
+ }
+}
diff --git a/src/components/ratings-and-reviews-components/types/index.ts b/src/components/ratings-and-reviews-components/types/index.ts
deleted file mode 100644
index 9bea9426..00000000
--- a/src/components/ratings-and-reviews-components/types/index.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-export interface IReview {
- id: number;
- author: string;
- product: string;
- score: number;
- pub_date: string;
- was_edited: boolean;
- text: string;
-}
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index b548dc86..7b480315 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -1066,22 +1066,53 @@ export interface OrderPostAdd extends OrderPostDelete {
}
export interface ReviewCreate {
- /** Score
+ /** Rating Score
* @min 1
* @max 5
*/
score: number;
- /** Text*/
+ /** Review Text */
text?: string;
}
export interface ReviewUpdate {
- /** Score
+ /** Rating Score
* @min 1
* @max 5
*/
score?: number;
- /** Text*/
+ /** ReviewText */
text?: string;
}
+
+export interface Review {
+ /** ID */
+ id: number;
+
+ /** Author
+ * @pattern ^[\w.@+-]+$
+ */
+ author: string;
+
+ /** Product */
+ product: string;
+
+ /** Rating Score
+ * @min 1
+ * @max 5
+ */
+ score: number;
+
+ /**
+ * Publication date
+ * @format date-time
+ */
+ pub_date: string;
+
+ /** Was Edited */
+ was_edited: boolean;
+
+ /** Review Text */
+ text: string;
+}
From 81983bc8a043284678cc87035fac9de4978ede74 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 30 Dec 2023 18:03:10 +0200
Subject: [PATCH 067/141] feat: add pluralizer util for matching of word form
and number
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил функцию для согласования текста с числительными
---
.../utils/pluralizer.ts | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 src/components/ratings-and-reviews-components/utils/pluralizer.ts
diff --git a/src/components/ratings-and-reviews-components/utils/pluralizer.ts b/src/components/ratings-and-reviews-components/utils/pluralizer.ts
new file mode 100644
index 00000000..69a6f302
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/utils/pluralizer.ts
@@ -0,0 +1,23 @@
+// NOTE: This is used to pick a value from array of word forms
+// For English, 1st and 3rd elements should be the same
+
+// Example: ['дней', 'день', 'дня'][plural(x)]
+// 0 -> 'дней'
+// 1 -> 'день'
+// 2 -> 'дня'
+// 3 -> 'дня'
+// 231 -> 'день'
+
+export default function plural(x: number) {
+ if (x % 100 >= 5 && x % 100 <= 19) {
+ return 0;
+ } else {
+ if (x % 10 === 0 || x % 10 >= 5) {
+ return 0;
+ } else if (x % 10 >= 2 && x % 10 <= 4) {
+ return 2;
+ } else {
+ return 1;
+ }
+ }
+}
From 8811d42880eecc451cdea312471c5b934b1b3fe5 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 30 Dec 2023 20:48:09 +0200
Subject: [PATCH 068/141] feat: add ratings-breakdown component
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил компонент Ratings Breakdown для отображения сводной информации по оценкам. Полоску
реализовал на
---
src/App.tsx | 28 +++--
.../ratings-breakdown/index.tsx | 47 ++++++++
.../ratings-breakdown.module.scss | 107 ++++++++++++++++++
3 files changed, 171 insertions(+), 11 deletions(-)
create mode 100644 src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
create mode 100644 src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss
diff --git a/src/App.tsx b/src/App.tsx
index 001c7843..b3c233c3 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -25,7 +25,8 @@ import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
// import ReviewAndRatingPostForm from '@components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx';
-import Review from '@components/ratings-and-reviews-components/review/index.tsx';
+// import Review from '@components/ratings-and-reviews-components/review/index.tsx';
+import RatingsBreakdown from '@components/ratings-and-reviews-components/ratings-breakdown/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -58,17 +59,22 @@ function App() {
}
+ // element={
+ //
+ // }
element={
-
}
/>
diff --git a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
new file mode 100644
index 00000000..556045c5
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+
+import plural from '../utils/pluralizer';
+import styles from './ratings-breakdown.module.scss';
+
+interface IRatingsBreakdown {
+ // sum: number;
+ // ratings: { [key: number]: number };
+ ratings: number[];
+}
+
+const RatingsBreakdown: React.FC = ({ ratings }) => {
+ const starsArray = [5, 4, 3, 2, 1];
+ const sum = ratings.reduce((a, b) => a + b, 0);
+ const amount = ratings.length;
+ const average = (sum / amount).toFixed(1);
+ const title = ['оценок', 'оценка', 'оценки'][plural(amount)];
+
+ return (
+
+
+
+ {amount} {title}
+
+
+ {starsArray.map((mark) => (
+
+
+ el === mark).length || 0}
+ max={amount}
+ />
+
+ ))}
+
+
+ );
+};
+
+export default RatingsBreakdown;
diff --git a/src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss b/src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss
new file mode 100644
index 00000000..0aab0e42
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss
@@ -0,0 +1,107 @@
+@use '@scss/_variables' as *;
+
+.container {
+ font-family: $ubuntu-font;
+ color: $active-text-color;
+ display: flex;
+ flex-direction: column;
+ max-width: 200px;
+
+ @media screen and (width <= 768px) {
+ max-width: 186px;
+ }
+}
+
+.average {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 4px;
+ font-size: 36px;
+ font-weight: 700;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 15px;
+ }
+}
+
+.bigStar {
+ width: 40px;
+ aspect-ratio: 1;
+ background-image: url('@images/star-rating-filled.svg');
+ background-position: center;
+ background-size: cover;
+
+ @media screen and (width <= 768px) {
+ width: 16px;
+ }
+}
+
+.smallStar {
+ width: 24px;
+ aspect-ratio: 1;
+ background-image: url('@images/star-rating-filled.svg');
+ background-position: center;
+ background-size: cover;
+
+ @media screen and (width <= 768px) {
+ width: 16px;
+ }
+}
+
+.title {
+ padding: 0;
+ margin: 0;
+ font-size: 22px;
+ font-weight: 500;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 12px;
+ font-weight: 400;
+ }
+}
+
+.list {
+ padding: 20px 0 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.item {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 20px;
+}
+
+.mark {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 8px;
+ font-size: 22px;
+ font-weight: 500;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 15px;
+ font-weight: 700;
+ }
+}
+
+.progress {
+ appearance: none;
+ height: 2px;
+
+ &::-webkit-progress-bar {
+ background-color: #293929;
+ }
+
+ &::-webkit-progress-value {
+ background-color: #f5b63b;
+ }
+}
From 53e0aa6847315279017cf680e3236470106b52e9 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 30 Dec 2023 22:57:40 +0200
Subject: [PATCH 069/141] feat: add reviews-list component
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил компонент Reviews List
---
src/App.tsx | 58 ++++++++++++++++++-
.../ratings-breakdown/index.tsx | 2 -
.../reviews-list/index.tsx | 37 ++++++++++++
.../reviews-list/reviews-list.module.scss | 52 +++++++++++++++++
4 files changed, 144 insertions(+), 5 deletions(-)
create mode 100644 src/components/ratings-and-reviews-components/reviews-list/index.tsx
create mode 100644 src/components/ratings-and-reviews-components/reviews-list/reviews-list.module.scss
diff --git a/src/App.tsx b/src/App.tsx
index b3c233c3..0538e4a4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -26,7 +26,8 @@ import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
// import ReviewAndRatingPostForm from '@components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx';
// import Review from '@components/ratings-and-reviews-components/review/index.tsx';
-import RatingsBreakdown from '@components/ratings-and-reviews-components/ratings-breakdown/index.tsx';
+// import RatingsBreakdown from '@components/ratings-and-reviews-components/ratings-breakdown/index.tsx';
+import ReviewsList from '@components/ratings-and-reviews-components/reviews-list/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -72,9 +73,60 @@ function App() {
// }}
// />
// }
+ // element={
+ //
+ // }
element={
-
}
/>
diff --git a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
index 556045c5..1f02c5db 100644
--- a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
+++ b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
@@ -4,8 +4,6 @@ import plural from '../utils/pluralizer';
import styles from './ratings-breakdown.module.scss';
interface IRatingsBreakdown {
- // sum: number;
- // ratings: { [key: number]: number };
ratings: number[];
}
diff --git a/src/components/ratings-and-reviews-components/reviews-list/index.tsx b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
new file mode 100644
index 00000000..c55ab5a9
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
@@ -0,0 +1,37 @@
+import React, { useState } from 'react';
+import { Review as IReview } from '@services/generated-api/data-contracts';
+import plural from '../utils/pluralizer';
+import Review from '../review';
+import styles from './reviews-list.module.scss';
+
+interface IReviewsList {
+ reviews: IReview[];
+}
+
+const ReviewsList: React.FC = ({ reviews }) => {
+ const [itemsShown, setItemsShown] = useState(3);
+ const amount = reviews.length;
+ const title = ['отзывов', 'отзыв', 'отзыва'][plural(amount)];
+
+ return (
+
+
+ {amount} {title}
+
+
+ {reviews.slice(0, itemsShown).map((item) => (
+
+
+
+ ))}
+
+ {itemsShown < amount && (
+
setItemsShown(itemsShown + 3)}>
+ Загрузить еще
+
+ )}
+
+ );
+};
+
+export default ReviewsList;
diff --git a/src/components/ratings-and-reviews-components/reviews-list/reviews-list.module.scss b/src/components/ratings-and-reviews-components/reviews-list/reviews-list.module.scss
new file mode 100644
index 00000000..5f553406
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/reviews-list/reviews-list.module.scss
@@ -0,0 +1,52 @@
+@use '@scss/_variables' as *;
+@use '@scss/_mixins' as *;
+
+.container {
+ font-family: $ubuntu-font;
+ color: $active-text-color;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.title {
+ padding: 0;
+ margin: 0;
+ font-size: 24px;
+ font-weight: 700;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 15px;
+ }
+}
+
+.list {
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+}
+
+.button {
+ @extend %default-button-text;
+
+ align-self: start;
+ padding: 10px 20px;
+ background-color: transparent;
+ border: 1px solid $accent-color-darkgreen;
+ border-radius: 16px;
+ transition: 0.3s;
+
+ &:hover {
+ color: white;
+ background-color: $accent-color-bright-green;
+ }
+
+ @media screen and (width <= 768px) {
+ font-size: 13px;
+ padding: 8px 16px;
+ border-radius: 12px;
+ }
+}
From b0e753668b5ad426f2d0859731988bad9cf4bad4 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sun, 31 Dec 2023 02:09:11 +0200
Subject: [PATCH 070/141] feat: add ratings-and-reviews-widget component, fix
review interface
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил виджет Ratings And Reviews Widget для отображения всего блока с отзывами и оценками. Добавил
Api. Отредактировал тип данных Review, теперь там есть id автора комментария
---
src/App.tsx | 76 +---------------
.../ratings-and-reviews-widget/index.tsx | 91 +++++++++++++++++++
.../ratings-and-reviews-widget.module.scss | 39 ++++++++
.../ratings-breakdown/index.tsx | 1 -
.../review-and-rating-post-form/index.tsx | 73 +++++++++------
.../review/index.tsx | 4 +-
.../reviews-list/index.tsx | 2 +-
src/services/generated-api/data-contracts.ts | 14 ++-
8 files changed, 190 insertions(+), 110 deletions(-)
create mode 100644 src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
create mode 100644 src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss
diff --git a/src/App.tsx b/src/App.tsx
index 0538e4a4..6550ceac 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,10 +24,7 @@ import RecipeList from '@pages/recipe-list/index.tsx';
import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
-// import ReviewAndRatingPostForm from '@components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx';
-// import Review from '@components/ratings-and-reviews-components/review/index.tsx';
-// import RatingsBreakdown from '@components/ratings-and-reviews-components/ratings-breakdown/index.tsx';
-import ReviewsList from '@components/ratings-and-reviews-components/reviews-list/index.tsx';
+import RatingsAndReviewsWidget from '@components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -59,76 +56,7 @@ function App() {
{/* // TODO: Clean up after Ratings and Reviews task completed */}
}
- // element={
- //
- // }
- // element={
- //
- // }
- element={
-
- }
+ element={ }
/>
= ({
+ productId,
+ userId,
+}) => {
+ const [reviews, setReviews] = useState(null);
+ const [userReview, setUserReview] = useState(null);
+ const [reviewsWithText, setReviewsWithText] = useState(null);
+ const [ratings, setRatings] = useState(null);
+
+ useEffect(() => {
+ api
+ .reviewsList(productId)
+ .then((res) => {
+ res[0] &&
+ res.sort(function (a: Review, b: Review) {
+ return Number(new Date(b.pub_date)) - Number(new Date(a.pub_date));
+ });
+ setReviews(res[0] ? res : null);
+ })
+ .catch((err) => console.log(err));
+ // TODO: Add request to check if this user can add reviews to this product
+ }, [productId]);
+
+ useEffect(() => {
+ if (reviews) {
+ const filtered = reviews.filter((item: Review) => !!item.text);
+ const ratings = reviews.map((item: Review) => item.score);
+ const userReview = reviews.find((item: Review) => item.author.id === userId);
+ setReviewsWithText(filtered[0] ? filtered : null);
+ setRatings(ratings[0] ? ratings : null);
+ setUserReview(userReview || null);
+ }
+ }, [reviews, userId]);
+
+ const handleAddReview = (review: Review) => {
+ setUserReview(review);
+ setReviews(reviews ? [review, ...reviews] : [review]);
+ };
+
+ const handleUpdateReview = (review: Review) => {
+ setUserReview(review);
+ const newReviews = reviews
+ ?.slice()
+ .map((item) => (item.id === review.id ? review : item));
+ setReviews(newReviews || null);
+ };
+
+ return (
+
+ Отзывы
+
+
+
+
+ {ratings && (
+
+ {reviewsWithText && (
+
+
+
+ )}
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default RatingsAndReviewsWidget;
diff --git a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss
new file mode 100644
index 00000000..b8878bca
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss
@@ -0,0 +1,39 @@
+@use '@scss/_variables' as *;
+
+.section {
+ font-family: $ubuntu-font;
+ color: $active-text-color;
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+
+ @media screen and (width <= 768px) {
+ gap: 20px;
+ }
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: 1fr 200px;
+ gap: 40px 107px;
+
+ @media screen and (width <= 768px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.form {
+ grid-column: span 2;
+
+ @media screen and (width <= 768px) {
+ grid-column: 1;
+ }
+}
+
+.ratings {
+ padding-right: 40px;
+
+ @media screen and (width <= 768px) {
+ grid-row: 1;
+ }
+}
diff --git a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
index 1f02c5db..57df4614 100644
--- a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
+++ b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
@@ -1,5 +1,4 @@
import React from 'react';
-
import plural from '../utils/pluralizer';
import styles from './ratings-breakdown.module.scss';
diff --git a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
index bf380e6e..3b6da835 100644
--- a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
+++ b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
@@ -1,4 +1,4 @@
-import React, { SyntheticEvent } from 'react';
+import React, { SyntheticEvent, useEffect } from 'react';
import { Review } from '@services/generated-api/data-contracts';
import api from '@services/api';
import { useFormAndValidation } from '@hooks/use-form-and-validation';
@@ -6,48 +6,60 @@ import RatingInput from '@components/ratings-and-reviews-components/rating-input
import Button from '@components/Button';
import styles from './review-and-rating-post-form.module.scss';
-const ReviewAndRatingPostForm: React.FC<{
- defaultReview: Review | null;
+const dateOptions = { day: '2-digit', month: '2-digit', year: '2-digit' };
+
+interface IReviewAndRatingPostForm {
+ review: Review | null;
productId: number;
-}> = ({ defaultReview, productId }) => {
+ onAddReview: (review: Review) => void;
+ onUpdateReview: (review: Review) => void;
+}
+
+const ReviewAndRatingPostForm: React.FC = ({
+ review,
+ productId,
+ onAddReview,
+ onUpdateReview,
+}) => {
const { values, setValues, handleChange } = useFormAndValidation({
- text: defaultReview?.text || '',
- score: defaultReview?.score || 0,
+ text: review?.text || '',
+ score: review?.score || 0,
});
- // useEffect(() => {
- // // NOTE: Temporary output of product ID
- // console.log(productId, defaultReview);
- // defaultReview &&
- // setValues({ text: defaultReview?.text, score: defaultReview?.score });
- // }, [defaultReview, productId, setValues]);
+ useEffect(() => {
+ review && setValues({ text: review?.text, score: review?.score });
+ }, [review, productId, setValues]);
const handleReviewSubmit = (e: SyntheticEvent) => {
e.preventDefault();
-
- // TODO: Fix then
- // TODO: Fix catch
- if (defaultReview?.score && values.text && values.text !== defaultReview?.text) {
+ // TODO: Add error processing in catch block
+ if (review?.score && values.text && values.text !== review?.text) {
api
- .reviewsUpdate(productId, defaultReview.id, { text: values.text as string })
- .then((res) => console.log(res))
+ .reviewsUpdate(productId, review.id, { text: values.text as string })
+ .then((res) => onUpdateReview(res))
.catch((err) => console.log(err));
console.log('submited');
}
};
const handleRatingChange = (rating: number) => {
- // TODO: Fix then
- // TODO: Fix catch
- if (!defaultReview || defaultReview?.score !== rating) {
+ // TODO: Add error processing in catch block
+ if (!review || review?.score !== rating) {
const apiPromise =
- defaultReview && defaultReview.score > 0
- ? api.reviewsUpdate(productId, defaultReview.id, {
- score: rating,
- })
- : api.reviewsCreate(productId, { score: rating });
+ review && review.score > 0
+ ? api
+ .reviewsUpdate(productId, review.id, {
+ score: rating,
+ })
+ .then((res) => {
+ onUpdateReview(res);
+ return res;
+ })
+ : api.reviewsCreate(productId, { score: rating }).then((res) => {
+ onAddReview(res);
+ return res;
+ });
apiPromise
- .then((res) => console.log(res))
.then(() => setValues({ ...values, score: rating }))
.catch((err) => console.log(err));
}
@@ -56,7 +68,12 @@ const ReviewAndRatingPostForm: React.FC<{
return (
- {defaultReview ? `Вы оценили товар ${defaultReview.pub_date}` : 'Оценить товар'}
+ {review ? 'Вы оценили товар ' : 'Оценить товар'}
+ {review && (
+
+ {new Date(review.pub_date).toLocaleDateString('ru-RU', dateOptions as never)}
+
+ )}
diff --git a/src/components/ratings-and-reviews-components/review/index.tsx b/src/components/ratings-and-reviews-components/review/index.tsx
index 886ae810..becea98f 100644
--- a/src/components/ratings-and-reviews-components/review/index.tsx
+++ b/src/components/ratings-and-reviews-components/review/index.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import { Review as IReview } from '@services/generated-api/data-contracts';
-import styles from './review.module.scss';
import RatingDisplay from '../rating-display';
+import styles from './review.module.scss';
const dateOptions = { day: '2-digit', month: '2-digit', year: '2-digit' };
@@ -10,7 +10,7 @@ const Review: React.FC<{ review: IReview }> = ({ review }) => {
- {review.author}
+ {review.author.username}
{new Date(review.pub_date).toLocaleDateString('ru-RU', dateOptions as never)}
diff --git a/src/components/ratings-and-reviews-components/reviews-list/index.tsx b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
index c55ab5a9..ac4b4e8e 100644
--- a/src/components/ratings-and-reviews-components/reviews-list/index.tsx
+++ b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
@@ -20,7 +20,7 @@ const ReviewsList: React.FC = ({ reviews }) => {
{reviews.slice(0, itemsShown).map((item) => (
-
+
))}
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index 7b480315..76b9c237 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -1090,10 +1090,16 @@ export interface Review {
/** ID */
id: number;
- /** Author
- * @pattern ^[\w.@+-]+$
- */
- author: string;
+ /** Author */
+ author: {
+ /** ID of author */
+ id: number;
+
+ /** Name of author
+ * @pattern ^[\w.@+-]+$
+ * */
+ username: string;
+ };
/** Product */
product: string;
From 55bd399aca936e6ce3966b9dbedf6540529d2414 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sun, 31 Dec 2023 02:24:09 +0200
Subject: [PATCH 071/141] feat: add check if user ordered product
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил проверку, покупал ли пользователь этот продукт
---
src/App.tsx | 2 +-
.../ratings-and-reviews-widget/index.tsx | 60 +++++++++++--------
src/services/api.ts | 10 ++++
src/services/generated-api/data-contracts.ts | 6 ++
4 files changed, 51 insertions(+), 27 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 6550ceac..2448179b 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -56,7 +56,7 @@ function App() {
{/* // TODO: Clean up after Ratings and Reviews task completed */}
}
+ element={ }
/>
= ({
const [userReview, setUserReview] = useState(null);
const [reviewsWithText, setReviewsWithText] = useState(null);
const [ratings, setRatings] = useState(null);
+ const [hasOrderedThis, setHasOrderedThis] = useState(false);
useEffect(() => {
api
@@ -31,7 +32,10 @@ const RatingsAndReviewsWidget: React.FC = ({
setReviews(res[0] ? res : null);
})
.catch((err) => console.log(err));
- // TODO: Add request to check if this user can add reviews to this product
+ api
+ .productsOrderCheck(productId)
+ .then((res) => setHasOrderedThis(res.ordered))
+ .catch((err) => console.log(err));
}, [productId]);
useEffect(() => {
@@ -59,32 +63,36 @@ const RatingsAndReviewsWidget: React.FC = ({
};
return (
-
- Отзывы
-
-
-
-
- {ratings && (
-
- {reviewsWithText && (
-
-
-
- )}
-
-
-
+ (hasOrderedThis || reviews) && (
+
+ Отзывы
+
+ {hasOrderedThis && (
+
+
-
- )}
-
-
+ )}
+ {ratings && (
+
+ {reviewsWithText && (
+
+
+
+ )}
+
+
+
+
+
+ )}
+
+
+ )
);
};
diff --git a/src/services/api.ts b/src/services/api.ts
index fb9986ed..c30a5a69 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -383,6 +383,16 @@ class Api {
});
}
+ productsOrderCheck(id: number) {
+ return this._request(`products/${id}/order-user-check/`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Token ${Cookies.get('token')}`,
+ },
+ });
+ }
+
/* -------------------------- FavoriteProducts -------------------------- */
favoriteProductsList() {
return this._request('favorite-products/', {
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index 76b9c237..ec9c63c3 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -1122,3 +1122,9 @@ export interface Review {
/** Review Text */
text: string;
}
+
+export interface OrderCheck {
+ product: number;
+ user: number;
+ ordered: boolean;
+}
From ecb13b6498072943c5ef348eb7c23736f2d97b69 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sun, 31 Dec 2023 03:07:06 +0200
Subject: [PATCH 072/141] feat: add ratings and reviews to product page
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил секцию с отзывами и оценками на страницу продукта. Поправил работу лейбла с рейтингом и
количеством отзывов в шапке страницы. Добавил ссылку на секцию с отзывами
---
src/App.tsx | 6 -
.../ratings-and-reviews-widget/index.tsx | 16 +-
.../review-and-rating-post-form/index.tsx | 1 -
src/pages/product/index.tsx | 198 ++++++++++--------
src/pages/product/product.module.scss | 14 +-
src/services/generated-api/data-contracts.ts | 1 +
6 files changed, 132 insertions(+), 104 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 2448179b..34f3cc7a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,7 +24,6 @@ import RecipeList from '@pages/recipe-list/index.tsx';
import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
-import RatingsAndReviewsWidget from '@components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -53,11 +52,6 @@ function App() {
} />
} />
} />
- {/* // TODO: Clean up after Ratings and Reviews task completed */}
- }
- />
}
diff --git a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
index 8877e0ca..371b70a9 100644
--- a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
+++ b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
@@ -8,18 +8,22 @@ import styles from './ratings-and-reviews-widget.module.scss';
interface IRatingsAndReviewsWidget {
productId: number;
- userId: number;
}
-const RatingsAndReviewsWidget: React.FC = ({
- productId,
- userId,
-}) => {
+const RatingsAndReviewsWidget: React.FC = ({ productId }) => {
const [reviews, setReviews] = useState(null);
const [userReview, setUserReview] = useState(null);
const [reviewsWithText, setReviewsWithText] = useState(null);
const [ratings, setRatings] = useState(null);
const [hasOrderedThis, setHasOrderedThis] = useState(false);
+ const [userId, setUserId] = useState(null);
+
+ useEffect(() => {
+ api
+ .usersMeRead()
+ .then((res) => setUserId(res.id))
+ .catch((err) => console.log(err));
+ }, []);
useEffect(() => {
api
@@ -64,7 +68,7 @@ const RatingsAndReviewsWidget: React.FC = ({
return (
(hasOrderedThis || reviews) && (
-
+
Отзывы
{hasOrderedThis && (
diff --git a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
index 3b6da835..c29af1ee 100644
--- a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
+++ b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
@@ -38,7 +38,6 @@ const ReviewAndRatingPostForm: React.FC
= ({
.reviewsUpdate(productId, review.id, { text: values.text as string })
.then((res) => onUpdateReview(res))
.catch((err) => console.log(err));
- console.log('submited');
}
};
diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx
index 45499411..0dc781ad 100644
--- a/src/pages/product/index.tsx
+++ b/src/pages/product/index.tsx
@@ -6,11 +6,13 @@ import api from '@services/api.ts';
import Preloader from '@components/preloader';
import ReviewStar from '@images/review-star.svg';
import Breadcrumbs from '@components/breadcrumbs';
-import { Product as ProductType } from '@services/generated-api/data-contracts';
+import RatingsAndReviewsWidget from '@components/ratings-and-reviews-components/ratings-and-reviews-widget';
+import { Product as ProductType, Review } from '@services/generated-api/data-contracts';
import { useAuth } from '@hooks/use-auth';
import { usePopup } from '@hooks/use-popup';
import { useCart } from '@hooks/use-cart-context.ts';
import { useNavigate } from 'react-router-dom';
+import plural from '@components/ratings-and-reviews-components/utils/pluralizer';
const Product: React.FC = () => {
const { cartData, updateCart, deleteCart } = useCart();
@@ -18,6 +20,7 @@ const Product: React.FC = () => {
const [isProductInCart, setIsProductInCart] = React.useState(false);
const [isLoaded, setIsLoaded] = React.useState(false);
const [productItem, setProductItem] = React.useState(null);
+ const [reviewsAmount, setReviewsAmount] = React.useState(0);
const { isLoggedIn } = useAuth();
const { handleOpenPopup } = usePopup();
@@ -43,6 +46,13 @@ const Product: React.FC = () => {
console.log(error);
navigate('/упс');
});
+ api
+ .reviewsList(Number(id))
+ .then((res) => {
+ const amount = res.filter((item: Review) => !!item.text).length;
+ setReviewsAmount(amount);
+ })
+ .catch((err) => console.log(err));
} else {
console.log('ID is undefined');
navigate('/упс');
@@ -95,103 +105,115 @@ const Product: React.FC = () => {
}
return (
-
- {!productItem ? (
-
- ) : (
-
-
-
-
-
-
{productItem.name}
-
-
Арт. {productItem.id}
-
-
-
4.8
+
+
+ {!productItem ? (
+
+ ) : (
+
+
+
+
+
+
{productItem.name}
+
-
2 отзыва
+
+
+ {productItem.price} руб. / {newMeasureUnit}
+
+
+
+
-
- {productItem.price} руб. / {newMeasureUnit}
-
-
-
-
-
-
-
-
-
-
- {productItem.description}
-
-
-
Срок годности
-
5 суток
+
-
-
Производитель
+
- {`«${productItem.producer.producer_name}»`}
+ {productItem.description}
-
-
-
Энергетическая ценность (на 100гр.)
-
-
-
белки
-
{productItem.proteins}г
-
-
-
-
жиры
-
{productItem.fats}г
-
-
-
-
углеводы
-
{productItem.carbohydrates}г
-
-
-
-
ккал-ть
-
{productItem.kcal}г
+
+
Срок годности
+
5 суток
+
+
+
Производитель
+
+ {`«${productItem.producer.producer_name}»`}
+
+
+
+
Энергетическая ценность (на 100гр.)
+
+
+
белки
+
{productItem.proteins}г
+
+
+
+
жиры
+
{productItem.fats}г
+
+
+
+
углеводы
+
{productItem.carbohydrates}г
+
+
+
+
ккал-ть
+
{productItem.kcal}г
+
-
- )}
-
+ )}
+
+ {id &&
}
+
);
};
diff --git a/src/pages/product/product.module.scss b/src/pages/product/product.module.scss
index 5b7fdf81..d4c824a5 100644
--- a/src/pages/product/product.module.scss
+++ b/src/pages/product/product.module.scss
@@ -12,14 +12,13 @@
}
}
-.product {
+.container {
margin: 0 auto;
width: calc(100% - 129px * 2);
height: 100%;
display: flex;
- gap: 28px;
flex-direction: column;
- justify-content: center;
+ gap: 60px;
@media screen and (width < 768px) {
padding: 0;
@@ -27,6 +26,14 @@
}
}
+.product {
+ width: 100%;
+ display: flex;
+ gap: 28px;
+ flex-direction: column;
+ justify-content: center;
+}
+
.product__section {
opacity: 0;
transform: translateY(20px);
@@ -107,6 +114,7 @@
color: var(--hint, #2180b6);
font-size: 18px;
margin: 0;
+ text-decoration: none;
@media screen and (width < 768px) {
font-size: 13px;
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index ec9c63c3..d11acf2e 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -358,6 +358,7 @@ export interface Product {
* @format uri
*/
photo?: string;
+ rating?: number;
components: ComponentLight[];
/**
* Kcal
From 7f199ca06bf208dcae8240327b9025f102eb6328 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 6 Jan 2024 13:22:15 +0200
Subject: [PATCH 073/141] refactor: move constants to separate file, fix arg
naming in map methods
---
.../rating-display/index.tsx | 10 ++++++----
.../rating-display/rating-display.module.scss | 2 +-
.../rating-input/index.tsx | 18 +++++++++---------
.../ratings-and-reviews-widget.module.scss | 11 ++++++++++-
.../ratings-breakdown/index.tsx | 6 +++---
.../ratings-breakdown.module.scss | 11 +++--------
.../review-and-rating-post-form/index.tsx | 3 +--
.../review/index.tsx | 3 +--
.../review/review.module.scss | 4 ++--
.../reviews-list/index.tsx | 5 +++--
.../utils/constants.ts | 11 +++++++++++
.../utils/types.ts | 0
12 files changed, 50 insertions(+), 34 deletions(-)
create mode 100644 src/components/ratings-and-reviews-components/utils/constants.ts
create mode 100644 src/components/ratings-and-reviews-components/utils/types.ts
diff --git a/src/components/ratings-and-reviews-components/rating-display/index.tsx b/src/components/ratings-and-reviews-components/rating-display/index.tsx
index 3a310faa..7930e829 100644
--- a/src/components/ratings-and-reviews-components/rating-display/index.tsx
+++ b/src/components/ratings-and-reviews-components/rating-display/index.tsx
@@ -1,13 +1,15 @@
import React from 'react';
+import { starsArray } from '../utils/constants';
import styles from './rating-display.module.scss';
const RatingDisplay: React.FC<{ rating: number }> = ({ rating }) => {
- const starsArray = [1, 2, 3, 4, 5];
-
return (
- {starsArray.map((el) => (
-
+ {starsArray.map((element) => (
+
))}
);
diff --git a/src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss b/src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss
index 3a07cbff..edb381b2 100644
--- a/src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss
+++ b/src/components/ratings-and-reviews-components/rating-display/rating-display.module.scss
@@ -21,7 +21,7 @@
background-image: url('@images/star-rating-filled.svg');
}
- @media screen and (width <= 768px) {
+ @media screen and (width <= 1180px) {
width: 12px;
}
}
diff --git a/src/components/ratings-and-reviews-components/rating-input/index.tsx b/src/components/ratings-and-reviews-components/rating-input/index.tsx
index d8b57854..79cb7db7 100644
--- a/src/components/ratings-and-reviews-components/rating-input/index.tsx
+++ b/src/components/ratings-and-reviews-components/rating-input/index.tsx
@@ -1,4 +1,5 @@
import React, { useState } from 'react';
+import { starsArray } from '../utils/constants';
import styles from './rating-input.module.scss';
interface RatingInput {
@@ -8,7 +9,6 @@ interface RatingInput {
const RatingInput: React.FC = ({ rating, onChange }) => {
const [hover, setHover] = useState(0);
- const starsArray = [1, 2, 3, 4, 5];
return (
= ({ rating, onChange }) => {
// NOTE: Keep hover active while mouse in component
onMouseLeave={() => setHover(0)}
>
- {starsArray.map((el) => (
-
+ {starsArray.map((element) => (
+
onChange(el)}
- checked={el === rating}
+ value={element}
+ onChange={() => onChange(element)}
+ checked={element === rating}
/>
setHover(el)}
+ onMouseEnter={() => setHover(element)}
/>
))}
diff --git a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss
index b8878bca..3759bb73 100644
--- a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss
+++ b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/ratings-and-reviews-widget.module.scss
@@ -14,9 +14,14 @@
.grid {
display: grid;
- grid-template-columns: 1fr 200px;
+ grid-template-columns: 1fr 240px;
gap: 40px 107px;
+ @media screen and (width <= 1180px) {
+ grid-template-columns: 1fr 160px;
+ gap: 40px 40px;
+ }
+
@media screen and (width <= 768px) {
grid-template-columns: 1fr;
}
@@ -33,6 +38,10 @@
.ratings {
padding-right: 40px;
+ @media screen and (width <= 1180px) {
+ padding-right: 20px;
+ }
+
@media screen and (width <= 768px) {
grid-row: 1;
}
diff --git a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
index 57df4614..f8fb41a8 100644
--- a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
+++ b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import plural from '../utils/pluralizer';
+import { starsArray, ratingsTitleOptions } from '../utils/constants';
import styles from './ratings-breakdown.module.scss';
interface IRatingsBreakdown {
@@ -7,11 +8,10 @@ interface IRatingsBreakdown {
}
const RatingsBreakdown: React.FC = ({ ratings }) => {
- const starsArray = [5, 4, 3, 2, 1];
const sum = ratings.reduce((a, b) => a + b, 0);
const amount = ratings.length;
const average = (sum / amount).toFixed(1);
- const title = ['оценок', 'оценка', 'оценки'][plural(amount)];
+ const title = ratingsTitleOptions[plural(amount)];
return (
@@ -31,7 +31,7 @@ const RatingsBreakdown: React.FC = ({ ratings }) => {
el === mark).length || 0}
+ value={ratings.filter((element) => element === mark).length || 0}
max={amount}
/>
diff --git a/src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss b/src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss
index 0aab0e42..9c859110 100644
--- a/src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss
+++ b/src/components/ratings-and-reviews-components/ratings-breakdown/ratings-breakdown.module.scss
@@ -5,11 +5,6 @@
color: $active-text-color;
display: flex;
flex-direction: column;
- max-width: 200px;
-
- @media screen and (width <= 768px) {
- max-width: 186px;
- }
}
.average {
@@ -45,7 +40,7 @@
background-position: center;
background-size: cover;
- @media screen and (width <= 768px) {
+ @media screen and (width <= 1180px) {
width: 16px;
}
}
@@ -67,7 +62,7 @@
padding: 20px 0 0;
margin: 0;
display: flex;
- flex-direction: column;
+ flex-direction: column-reverse;
gap: 12px;
}
@@ -87,7 +82,7 @@
font-weight: 500;
line-height: 140%;
- @media screen and (width <= 768px) {
+ @media screen and (width <= 1180px) {
font-size: 15px;
font-weight: 700;
}
diff --git a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
index c29af1ee..c54fb993 100644
--- a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
+++ b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
@@ -4,10 +4,9 @@ import api from '@services/api';
import { useFormAndValidation } from '@hooks/use-form-and-validation';
import RatingInput from '@components/ratings-and-reviews-components/rating-input';
import Button from '@components/Button';
+import { dateOptions } from '../utils/constants';
import styles from './review-and-rating-post-form.module.scss';
-const dateOptions = { day: '2-digit', month: '2-digit', year: '2-digit' };
-
interface IReviewAndRatingPostForm {
review: Review | null;
productId: number;
diff --git a/src/components/ratings-and-reviews-components/review/index.tsx b/src/components/ratings-and-reviews-components/review/index.tsx
index becea98f..1e13d85e 100644
--- a/src/components/ratings-and-reviews-components/review/index.tsx
+++ b/src/components/ratings-and-reviews-components/review/index.tsx
@@ -1,10 +1,9 @@
import React from 'react';
import { Review as IReview } from '@services/generated-api/data-contracts';
import RatingDisplay from '../rating-display';
+import { dateOptions } from '../utils/constants';
import styles from './review.module.scss';
-const dateOptions = { day: '2-digit', month: '2-digit', year: '2-digit' };
-
const Review: React.FC<{ review: IReview }> = ({ review }) => {
return (
diff --git a/src/components/ratings-and-reviews-components/review/review.module.scss b/src/components/ratings-and-reviews-components/review/review.module.scss
index ce383cab..e13ed40a 100644
--- a/src/components/ratings-and-reviews-components/review/review.module.scss
+++ b/src/components/ratings-and-reviews-components/review/review.module.scss
@@ -37,7 +37,7 @@
font-weight: 500;
line-height: 140%;
- @media screen and (width <= 768px) {
+ @media screen and (width <= 1180px) {
font-size: 14px;
gap: 22px;
}
@@ -50,7 +50,7 @@
font-weight: 400;
line-height: 140%;
- @media screen and (width <= 768px) {
+ @media screen and (width <= 1180px) {
font-size: 14px;
font-weight: 500;
}
diff --git a/src/components/ratings-and-reviews-components/reviews-list/index.tsx b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
index ac4b4e8e..7bd62771 100644
--- a/src/components/ratings-and-reviews-components/reviews-list/index.tsx
+++ b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
@@ -1,7 +1,8 @@
import React, { useState } from 'react';
import { Review as IReview } from '@services/generated-api/data-contracts';
-import plural from '../utils/pluralizer';
import Review from '../review';
+import plural from '../utils/pluralizer';
+import { reviewsTitleOptions } from '../utils/constants';
import styles from './reviews-list.module.scss';
interface IReviewsList {
@@ -11,7 +12,7 @@ interface IReviewsList {
const ReviewsList: React.FC = ({ reviews }) => {
const [itemsShown, setItemsShown] = useState(3);
const amount = reviews.length;
- const title = ['отзывов', 'отзыв', 'отзыва'][plural(amount)];
+ const title = reviewsTitleOptions[plural(amount)];
return (
diff --git a/src/components/ratings-and-reviews-components/utils/constants.ts b/src/components/ratings-and-reviews-components/utils/constants.ts
new file mode 100644
index 00000000..7d321c96
--- /dev/null
+++ b/src/components/ratings-and-reviews-components/utils/constants.ts
@@ -0,0 +1,11 @@
+// Possible ratings
+export const starsArray = [1, 2, 3, 4, 5];
+
+// Ratings breakdown component title options for different plurals
+export const ratingsTitleOptions = ['оценок', 'оценка', 'оценки'];
+
+// Reviews list component title options for different plurals
+export const reviewsTitleOptions = ['отзывов', 'отзыв', 'отзыва'];
+
+// Date formatting options
+export const dateOptions = { day: '2-digit', month: '2-digit', year: '2-digit' };
diff --git a/src/components/ratings-and-reviews-components/utils/types.ts b/src/components/ratings-and-reviews-components/utils/types.ts
new file mode 100644
index 00000000..e69de29b
From 0f1a864a6f9bcf83c897ffdf2394f64a5ce434c7 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 6 Jan 2024 13:44:19 +0200
Subject: [PATCH 074/141] refactor: decompose and refactor logic to be more
simple and readable
---
.../ratings-and-reviews-widget/index.tsx | 15 +++---
.../review-and-rating-post-form/index.tsx | 46 +++++++++++--------
2 files changed, 33 insertions(+), 28 deletions(-)
diff --git a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
index 371b70a9..8090fa96 100644
--- a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
+++ b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
@@ -43,14 +43,13 @@ const RatingsAndReviewsWidget: React.FC = ({ productId
}, [productId]);
useEffect(() => {
- if (reviews) {
- const filtered = reviews.filter((item: Review) => !!item.text);
- const ratings = reviews.map((item: Review) => item.score);
- const userReview = reviews.find((item: Review) => item.author.id === userId);
- setReviewsWithText(filtered[0] ? filtered : null);
- setRatings(ratings[0] ? ratings : null);
- setUserReview(userReview || null);
- }
+ if (!reviews) return;
+ const filtered = reviews.filter((item: Review) => !!item.text);
+ const ratings = reviews.map((item: Review) => item.score);
+ const userReview = reviews.find((item: Review) => item.author.id === userId);
+ setReviewsWithText(filtered[0] ? filtered : null);
+ setRatings(ratings[0] ? ratings : null);
+ setUserReview(userReview || null);
}, [reviews, userId]);
const handleAddReview = (review: Review) => {
diff --git a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
index c54fb993..1abf6c94 100644
--- a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
+++ b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
@@ -29,38 +29,44 @@ const ReviewAndRatingPostForm: React.FC = ({
review && setValues({ text: review?.text, score: review?.score });
}, [review, productId, setValues]);
+ const handleReviewUpdate = (res: Review) => {
+ onUpdateReview(res);
+ return res;
+ };
+
+ const handleReviewCreate = (res: Review) => {
+ onAddReview(res);
+ return res;
+ };
+
const handleReviewSubmit = (e: SyntheticEvent) => {
e.preventDefault();
+
// TODO: Add error processing in catch block
if (review?.score && values.text && values.text !== review?.text) {
api
.reviewsUpdate(productId, review.id, { text: values.text as string })
- .then((res) => onUpdateReview(res))
+ .then(handleReviewUpdate)
.catch((err) => console.log(err));
}
};
const handleRatingChange = (rating: number) => {
+ if (review?.score === rating) return;
+
+ const updateReview =
+ review && review.score > 0
+ ? api
+ .reviewsUpdate(productId, review.id, {
+ score: rating,
+ })
+ .then(handleReviewUpdate)
+ : api.reviewsCreate(productId, { score: rating }).then(handleReviewCreate);
+
// TODO: Add error processing in catch block
- if (!review || review?.score !== rating) {
- const apiPromise =
- review && review.score > 0
- ? api
- .reviewsUpdate(productId, review.id, {
- score: rating,
- })
- .then((res) => {
- onUpdateReview(res);
- return res;
- })
- : api.reviewsCreate(productId, { score: rating }).then((res) => {
- onAddReview(res);
- return res;
- });
- apiPromise
- .then(() => setValues({ ...values, score: rating }))
- .catch((err) => console.log(err));
- }
+ updateReview
+ .then(() => setValues({ ...values, score: rating }))
+ .catch((err) => console.log(err));
};
return (
From 67f1f079994cb9b0d5acbeac93c51acadbd4be48 Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Sat, 6 Jan 2024 14:51:20 +0200
Subject: [PATCH 075/141] refactor: organize types and interfaces in
utils/types
---
.../rating-display/index.tsx | 11 +++---
.../rating-input/index.tsx | 24 ++++++-------
.../ratings-and-reviews-widget/index.tsx | 35 +++++++++++--------
.../ratings-breakdown/index.tsx | 5 ++-
.../review-and-rating-post-form/index.tsx | 23 ++++++------
.../review/index.tsx | 6 ++--
.../reviews-list/index.tsx | 21 +++++------
.../utils/types.ts | 24 +++++++++++++
8 files changed, 91 insertions(+), 58 deletions(-)
diff --git a/src/components/ratings-and-reviews-components/rating-display/index.tsx b/src/components/ratings-and-reviews-components/rating-display/index.tsx
index 7930e829..ed522fbf 100644
--- a/src/components/ratings-and-reviews-components/rating-display/index.tsx
+++ b/src/components/ratings-and-reviews-components/rating-display/index.tsx
@@ -1,14 +1,17 @@
import React from 'react';
import { starsArray } from '../utils/constants';
+import { IRatingsAndReviews } from '../utils/types';
import styles from './rating-display.module.scss';
-const RatingDisplay: React.FC<{ rating: number }> = ({ rating }) => {
+interface IRatingDisplay extends Pick {}
+
+const RatingDisplay: React.FC = ({ rating }) => {
return (
- {starsArray.map((element) => (
+ {starsArray.map((mark) => (
))}
diff --git a/src/components/ratings-and-reviews-components/rating-input/index.tsx b/src/components/ratings-and-reviews-components/rating-input/index.tsx
index 79cb7db7..5ef2a557 100644
--- a/src/components/ratings-and-reviews-components/rating-input/index.tsx
+++ b/src/components/ratings-and-reviews-components/rating-input/index.tsx
@@ -1,13 +1,11 @@
import React, { useState } from 'react';
import { starsArray } from '../utils/constants';
+import { IRatingsAndReviews } from '../utils/types';
import styles from './rating-input.module.scss';
-interface RatingInput {
- rating: number;
- onChange: (rating: number) => void;
-}
+interface IRatingInput extends Pick {}
-const RatingInput: React.FC = ({ rating, onChange }) => {
+const RatingInput: React.FC = ({ rating, onRatingChange }) => {
const [hover, setHover] = useState(0);
return (
@@ -16,21 +14,21 @@ const RatingInput: React.FC = ({ rating, onChange }) => {
// NOTE: Keep hover active while mouse in component
onMouseLeave={() => setHover(0)}
>
- {starsArray.map((element) => (
-
+ {starsArray.map((mark) => (
+
onChange(element)}
- checked={element === rating}
+ value={mark}
+ onChange={() => onRatingChange(mark)}
+ checked={mark === rating}
/>
setHover(element)}
+ onMouseEnter={() => setHover(mark)}
/>
))}
diff --git a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
index 8090fa96..aaab677c 100644
--- a/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
+++ b/src/components/ratings-and-reviews-components/ratings-and-reviews-widget/index.tsx
@@ -1,22 +1,27 @@
import React, { useEffect, useState } from 'react';
import api from '@services/api';
-import { Review } from '@services/generated-api/data-contracts';
import ReviewAndRatingPostForm from '../review-and-rating-post-form';
import RatingsBreakdown from '../ratings-breakdown';
import ReviewsList from '../reviews-list';
+import {
+ IRatingsAndReviews,
+ IReview,
+ TRatingNullable,
+ TRatingsNullable,
+ TReviewNullable,
+ TReviewsNullable,
+} from '../utils/types';
import styles from './ratings-and-reviews-widget.module.scss';
-interface IRatingsAndReviewsWidget {
- productId: number;
-}
+interface IRatingsAndReviewsWidget extends Pick {}
const RatingsAndReviewsWidget: React.FC = ({ productId }) => {
- const [reviews, setReviews] = useState(null);
- const [userReview, setUserReview] = useState(null);
- const [reviewsWithText, setReviewsWithText] = useState(null);
- const [ratings, setRatings] = useState(null);
+ const [reviews, setReviews] = useState(null);
+ const [userReview, setUserReview] = useState(null);
+ const [reviewsWithText, setReviewsWithText] = useState(null);
+ const [ratings, setRatings] = useState(null);
const [hasOrderedThis, setHasOrderedThis] = useState(false);
- const [userId, setUserId] = useState(null);
+ const [userId, setUserId] = useState(null);
useEffect(() => {
api
@@ -30,7 +35,7 @@ const RatingsAndReviewsWidget: React.FC = ({ productId
.reviewsList(productId)
.then((res) => {
res[0] &&
- res.sort(function (a: Review, b: Review) {
+ res.sort(function (a: IReview, b: IReview) {
return Number(new Date(b.pub_date)) - Number(new Date(a.pub_date));
});
setReviews(res[0] ? res : null);
@@ -44,20 +49,20 @@ const RatingsAndReviewsWidget: React.FC = ({ productId
useEffect(() => {
if (!reviews) return;
- const filtered = reviews.filter((item: Review) => !!item.text);
- const ratings = reviews.map((item: Review) => item.score);
- const userReview = reviews.find((item: Review) => item.author.id === userId);
+ const filtered = reviews.filter((review: IReview) => !!review.text);
+ const ratings = reviews.map((review: IReview) => review.score);
+ const userReview = reviews.find((review: IReview) => review.author.id === userId);
setReviewsWithText(filtered[0] ? filtered : null);
setRatings(ratings[0] ? ratings : null);
setUserReview(userReview || null);
}, [reviews, userId]);
- const handleAddReview = (review: Review) => {
+ const handleAddReview = (review: IReview) => {
setUserReview(review);
setReviews(reviews ? [review, ...reviews] : [review]);
};
- const handleUpdateReview = (review: Review) => {
+ const handleUpdateReview = (review: IReview) => {
setUserReview(review);
const newReviews = reviews
?.slice()
diff --git a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
index f8fb41a8..aebc4b3a 100644
--- a/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
+++ b/src/components/ratings-and-reviews-components/ratings-breakdown/index.tsx
@@ -1,11 +1,10 @@
import React from 'react';
import plural from '../utils/pluralizer';
import { starsArray, ratingsTitleOptions } from '../utils/constants';
+import { IRatingsAndReviews } from '../utils/types';
import styles from './ratings-breakdown.module.scss';
-interface IRatingsBreakdown {
- ratings: number[];
-}
+interface IRatingsBreakdown extends Pick {}
const RatingsBreakdown: React.FC = ({ ratings }) => {
const sum = ratings.reduce((a, b) => a + b, 0);
diff --git a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
index 1abf6c94..41ec3b5b 100644
--- a/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
+++ b/src/components/ratings-and-reviews-components/review-and-rating-post-form/index.tsx
@@ -1,17 +1,15 @@
import React, { SyntheticEvent, useEffect } from 'react';
-import { Review } from '@services/generated-api/data-contracts';
import api from '@services/api';
import { useFormAndValidation } from '@hooks/use-form-and-validation';
import RatingInput from '@components/ratings-and-reviews-components/rating-input';
import Button from '@components/Button';
import { dateOptions } from '../utils/constants';
+import { IRatingsAndReviews, IReview, TRating, TReviewNullable } from '../utils/types';
import styles from './review-and-rating-post-form.module.scss';
-interface IReviewAndRatingPostForm {
- review: Review | null;
- productId: number;
- onAddReview: (review: Review) => void;
- onUpdateReview: (review: Review) => void;
+interface IReviewAndRatingPostForm
+ extends Pick {
+ review: TReviewNullable;
}
const ReviewAndRatingPostForm: React.FC = ({
@@ -29,12 +27,12 @@ const ReviewAndRatingPostForm: React.FC = ({
review && setValues({ text: review?.text, score: review?.score });
}, [review, productId, setValues]);
- const handleReviewUpdate = (res: Review) => {
+ const handleReviewUpdate = (res: IReview) => {
onUpdateReview(res);
return res;
};
- const handleReviewCreate = (res: Review) => {
+ const handleReviewCreate = (res: IReview) => {
onAddReview(res);
return res;
};
@@ -45,13 +43,16 @@ const ReviewAndRatingPostForm: React.FC = ({
// TODO: Add error processing in catch block
if (review?.score && values.text && values.text !== review?.text) {
api
- .reviewsUpdate(productId, review.id, { text: values.text as string })
+ .reviewsUpdate(productId, review.id, { text: values.text } as Pick<
+ IReview,
+ 'text'
+ >)
.then(handleReviewUpdate)
.catch((err) => console.log(err));
}
};
- const handleRatingChange = (rating: number) => {
+ const handleRatingChange = (rating: TRating) => {
if (review?.score === rating) return;
const updateReview =
@@ -79,7 +80,7 @@ const ReviewAndRatingPostForm: React.FC = ({
)}
-
+
= ({ review }) => {
+interface IReview extends Pick {}
+
+const Review: React.FC = ({ review }) => {
return (
diff --git a/src/components/ratings-and-reviews-components/reviews-list/index.tsx b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
index 7bd62771..3406d2c7 100644
--- a/src/components/ratings-and-reviews-components/reviews-list/index.tsx
+++ b/src/components/ratings-and-reviews-components/reviews-list/index.tsx
@@ -1,16 +1,14 @@
import React, { useState } from 'react';
-import { Review as IReview } from '@services/generated-api/data-contracts';
import Review from '../review';
import plural from '../utils/pluralizer';
import { reviewsTitleOptions } from '../utils/constants';
+import { IRatingsAndReviews } from '../utils/types';
import styles from './reviews-list.module.scss';
-interface IReviewsList {
- reviews: IReview[];
-}
+interface IReviewsList extends Pick
{}
const ReviewsList: React.FC = ({ reviews }) => {
- const [itemsShown, setItemsShown] = useState(3);
+ const [reviewsShown, setReviewsShown] = useState(3);
const amount = reviews.length;
const title = reviewsTitleOptions[plural(amount)];
@@ -20,14 +18,17 @@ const ReviewsList: React.FC = ({ reviews }) => {
{amount} {title}
- {reviews.slice(0, itemsShown).map((item) => (
-
-
+ {reviews.slice(0, reviewsShown).map((review) => (
+
+
))}
- {itemsShown < amount && (
- setItemsShown(itemsShown + 3)}>
+ {reviewsShown < amount && (
+ setReviewsShown(reviewsShown + 3)}
+ >
Загрузить еще
)}
diff --git a/src/components/ratings-and-reviews-components/utils/types.ts b/src/components/ratings-and-reviews-components/utils/types.ts
index e69de29b..1f310c43 100644
--- a/src/components/ratings-and-reviews-components/utils/types.ts
+++ b/src/components/ratings-and-reviews-components/utils/types.ts
@@ -0,0 +1,24 @@
+import { Review } from '@services/generated-api/data-contracts';
+
+export type TRating = number;
+
+export interface IRatingsAndReviews {
+ productId: number;
+ rating: TRating;
+ ratings: TRating[];
+ review: Review;
+ reviews: Review[];
+ onRatingChange: (rating: TRating) => void;
+ onAddReview: (review: Review) => void;
+ onUpdateReview: (review: Review) => void;
+}
+
+export interface IReview extends Review {}
+
+export type TReviewsNullable = null | Review[];
+
+export type TReviewNullable = null | Review;
+
+export type TRatingsNullable = null | number[];
+
+export type TRatingNullable = null | number;
From db67afa6c9c676b8b51ffcfc26b65600494bd271 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 08:04:20 +0300
Subject: [PATCH 076/141] fix: change folder name to lowercase
---
src/components/making-order-btn/index.tsx | 4 ++--
src/components/product-card/index.tsx | 2 +-
src/components/top-selling-this-week/index.tsx | 2 +-
src/pages/product/index.tsx | 2 +-
4 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/components/making-order-btn/index.tsx b/src/components/making-order-btn/index.tsx
index 8c6c7922..d3d06043 100644
--- a/src/components/making-order-btn/index.tsx
+++ b/src/components/making-order-btn/index.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import styles from './making-order-btn.module.scss';
-import Button from '@components/Button';
-import type { ButtonProps } from '@components/Button';
+import Button from '@components/button';
+import type { ButtonProps } from '@components/button';
interface MakingOrderBtnProps extends Pick {}
diff --git a/src/components/product-card/index.tsx b/src/components/product-card/index.tsx
index 5646b758..2511e490 100644
--- a/src/components/product-card/index.tsx
+++ b/src/components/product-card/index.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
-import Button from '@components/Button';
+import Button from '@components/button';
import styles from './product-card.module.scss';
import { BASE_URL } from '@data/constants.ts';
import LikeIcon from '@images/like-icon.svg?react';
diff --git a/src/components/top-selling-this-week/index.tsx b/src/components/top-selling-this-week/index.tsx
index e3507cc8..55503beb 100644
--- a/src/components/top-selling-this-week/index.tsx
+++ b/src/components/top-selling-this-week/index.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import api from '@services/api';
import type { Product } from '@services/generated-api/data-contracts';
-import Button from '@components/Button';
+import Button from '@components/button';
import ProductCard from '@components/product-card';
import styles from './top-selling-this-week.module.scss';
diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx
index 0dc781ad..2889058b 100644
--- a/src/pages/product/index.tsx
+++ b/src/pages/product/index.tsx
@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import styles from './product.module.scss';
-import Button from '@components/Button';
+import Button from '@components/button';
import { useParams } from 'react-router';
import api from '@services/api.ts';
import Preloader from '@components/preloader';
From 648c37a082d3e7fc525977134240a17a17a0f02d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 08:06:40 +0300
Subject: [PATCH 077/141] fix: linter autofix
---
src/pages/agreement/agreement.module.scss | 2 +-
src/pages/agreement/index.tsx | 486 ++++++++--
.../delivery-conditions.module.scss | 2 +-
src/pages/delivery-conditions/index.tsx | 896 +++++++++++++++---
4 files changed, 1150 insertions(+), 236 deletions(-)
diff --git a/src/pages/agreement/agreement.module.scss b/src/pages/agreement/agreement.module.scss
index 57650e47..9246e1b4 100644
--- a/src/pages/agreement/agreement.module.scss
+++ b/src/pages/agreement/agreement.module.scss
@@ -37,4 +37,4 @@
.agreement_p {
width: 100%;
-}
\ No newline at end of file
+}
diff --git a/src/pages/agreement/index.tsx b/src/pages/agreement/index.tsx
index 4d960b13..94c30b6f 100644
--- a/src/pages/agreement/index.tsx
+++ b/src/pages/agreement/index.tsx
@@ -83,110 +83,398 @@ const Agreement: React.FC = () => {
«Сайт») и/или мобильного приложения Оператора (далее - «Приложение»). Далее по
тексту «Сайт» и «Приложение» при совместном упоминание именуются «Сервис».
- 1.5 Настоящая Политика определяет порядок и условия осуществления обработки персональных данных Субъектов, передавших свои персональные данные для обработки Оператору с использованием и без использования средств автоматизации, устанавливает процедуры, направленные на предотвращение нарушений законодательства Российской Федерации, связанных с обработкой персональных данных.
- 1.6 Политика разработана с целью обеспечения защиты прав Субъектов при обработке их персональных данных, а также продвижения товаров, работ, услуг Оператора/исполнителя/доставщика путем осуществления прямых контактов с потенциальным потребителем с помощью средств связи.
- 1.7 Оператор осуществляет обработку общедоступную и иную категорию персональных данных Субъекта (не относящуюся к биометрической или специальной):
- - фамилия, имя, отчество;
- - номер телефона;
- - адрес электронной почты;
- - почтовый адрес;
- - данные об оказанных и оказываемых Субъекту услугах, история заказов Субъекта, обращений Субъекта
- к Оператору посредством использования Сервиса;
- 1.8. При использовании Сервиса Субъектом персональных данных, Оператор получает и обрабатывает данные, получаемые через программные средства Яндекс. Метрика, Google tag manager, Google analytics, Google Firebase и иные обезличенные данные, которые автоматически передаются в процессе использования Сервиса посредством установленного на техническом устройстве Субъекта программного обеспечения:
+
+ 1.5 Настоящая Политика определяет порядок и условия осуществления обработки
+ персональных данных Субъектов, передавших свои персональные данные для
+ обработки Оператору с использованием и без использования средств
+ автоматизации, устанавливает процедуры, направленные на предотвращение
+ нарушений законодательства Российской Федерации, связанных с обработкой
+ персональных данных.
+
+
+ 1.6 Политика разработана с целью обеспечения защиты прав Субъектов при
+ обработке их персональных данных, а также продвижения товаров, работ, услуг
+ Оператора/исполнителя/доставщика путем осуществления прямых контактов с
+ потенциальным потребителем с помощью средств связи.
+
+
+ 1.7 Оператор осуществляет обработку общедоступную и иную категорию
+ персональных данных Субъекта (не относящуюся к биометрической или
+ специальной):
+
+
+ - фамилия, имя, отчество;
+
+
+ - номер телефона;
+
+
+ - адрес электронной почты;
+
+
+ - почтовый адрес;
+
+
+ - данные об оказанных и оказываемых Субъекту услугах, история заказов
+ Субъекта, обращений Субъекта к Оператору посредством использования Сервиса;
+
+
+ 1.8. При использовании Сервиса Субъектом персональных данных, Оператор
+ получает и обрабатывает данные, получаемые через программные средства Яндекс.
+ Метрика, Google tag manager, Google analytics, Google Firebase и иные
+ обезличенные данные, которые автоматически передаются в процессе использования
+ Сервиса посредством установленного на техническом устройстве Субъекта
+ программного обеспечения:
+
- IP-адрес;
- - идентификаторы мобильных устройств;
- - данные файлов cookie;
- - количество просмотров;
- - посещение страниц;
- - поиск по сайту;
- - поиск в приложении;
- - точки входа (сторонние сайты, с которых пользователь по ссылке переходит на сайты Компании);
- - точки выхода (ссылки на сайтах Компании, по которым пользователь переходит на сторонние сайты);
- - страна пользователя;
- - регион пользователя;
- - провайдер пользователя;
- - браузер пользователя;
- - системные языки пользователя;
- - ОС пользователя;
- Мы используем такую информацию для понимания и анализа тенденций, администрирования сайта, изучения поведения пользователей на сайте и сбора демографической информации о нашем основном контингенте пользователей в целом. Компания может использовать такую информацию в своих маркетинговых целях.
- 1.9 Оператором на принципах:
- - законности целей и способов обработки персональных данных, добросовестности и справедливости
- в деятельности Оператора;
- - ограничения обработки персональных данных достижением конкретных, заранее определенных
-и законных целей;
- - достоверности персональных данных, их достаточности для целей обработки, недопустимости обработки персональных данных, избыточных по отношению к целям, заявленным при сборе персональных данных;
- - обработки только персональных данных, которые отвечают целям их обработки.Недопустима обработка персональных данных, несовместимая с целями сбора персональных данных;
- - соответствия содержания и объема обрабатываемых персональных данных заявленным целям обработки. Обрабатываемые персональные данные не должны быть избыточными по отношению
-к заявленным целям их обработки;
- - недопустимости объединения баз данных, содержащих персональные данные, обработка которых осуществляется в целях, не совместимых между собой;
- - обеспечения точности персональных данных, их достаточности, а в необходимых случаях и актуальности по отношению к целям обработки персональных данных. Оператор принимает необходимые меры либо обеспечивает их принятие по удалению или уточнению неполных или неточных данных;
- - хранения персональных данных в форме, позволяющей определить субъекта персональных данных,
-не дольше, чем этого требуют цели обработки персональных данных, если срок хранения персональных данных не установлен федеральным законом, договором, стороной которого, выгодоприобретателем
-или поручителем по которому является субъект персональных данных. Обрабатываемые персональные данные подлежат уничтожению либо обезличиванию по достижении целей обработки или в случае утраты необходимости в достижении этих целей, если иное не предусмотрено федеральным законом.
+
+ - идентификаторы мобильных устройств;
+
+
+ - данные файлов cookie;
+
+
+ - количество просмотров;
+
+
+ - посещение страниц;
+
+
+ - поиск по сайту;
+
+
+ - поиск в приложении;
+
+
+ - точки входа (сторонние сайты, с которых пользователь по ссылке переходит на
+ сайты Компании);
+
+
+ - точки выхода (ссылки на сайтах Компании, по которым пользователь переходит
+ на сторонние сайты);
+
+
+ - страна пользователя;
+
+
+ - регион пользователя;
+
+
+ - провайдер пользователя;
+
+
+ - браузер пользователя;
+
+
+ - системные языки пользователя;
+
+
+ - ОС пользователя;
+
+
+ Мы используем такую информацию для понимания и анализа тенденций,
+ администрирования сайта, изучения поведения пользователей на сайте и сбора
+ демографической информации о нашем основном контингенте пользователей в целом.
+ Компания может использовать такую информацию в своих маркетинговых целях.
+
+
+ 1.9 Оператором на принципах:
+
+
+ - законности целей и способов обработки персональных данных, добросовестности
+ и справедливости в деятельности Оператора;
+
+
+ - ограничения обработки персональных данных достижением конкретных, заранее
+ определенных и законных целей;
+
+
+ - достоверности персональных данных, их достаточности для целей обработки,
+ недопустимости обработки персональных данных, избыточных по отношению к целям,
+ заявленным при сборе персональных данных;
+
+
+ - обработки только персональных данных, которые отвечают целям их
+ обработки.Недопустима обработка персональных данных, несовместимая с целями
+ сбора персональных данных;
+
+
+ - соответствия содержания и объема обрабатываемых персональных данных
+ заявленным целям обработки. Обрабатываемые персональные данные не должны быть
+ избыточными по отношению к заявленным целям их обработки;
+
+
+ - недопустимости объединения баз данных, содержащих персональные данные,
+ обработка которых осуществляется в целях, не совместимых между собой;
+
+
+ - обеспечения точности персональных данных, их достаточности, а в необходимых
+ случаях и актуальности по отношению к целям обработки персональных данных.
+ Оператор принимает необходимые меры либо обеспечивает их принятие по удалению
+ или уточнению неполных или неточных данных;
+
+
+ - хранения персональных данных в форме, позволяющей определить субъекта
+ персональных данных, не дольше, чем этого требуют цели обработки персональных
+ данных, если срок хранения персональных данных не установлен федеральным
+ законом, договором, стороной которого, выгодоприобретателем или поручителем по
+ которому является субъект персональных данных. Обрабатываемые персональные
+ данные подлежат уничтожению либо обезличиванию по достижении целей обработки
+ или в случае утраты необходимости в достижении этих целей, если иное не
+ предусмотрено федеральным законом.
+
2. Цели сбора персональных данных
- 2.1. Оператор осуществляет обработку персональных данных Субъектов путем ведения баз данных автоматизированным, механическим, ручным способами в целях:
- 2.1.1. обработки заказов, запросов или других действий Субъекта, связанных с регистрацией, авторизацией на Сервисе, осуществлением заказов через Сервис;
- 2.1.2. оповещения об изменении пользовательского соглашения об условиях доставки, порядка оказания услуг, меню, перечня проводимых Оператором акций, скидок и иных маркетинговых мероприятий.
- 2.1.3. в иных целях в случае, если соответствующие действия Оператора не противоречат действующему законодательству, деятельности Оператора, и на проведение указанной обработки получено согласие Субъекта персональных данных.
- 2.2. Данные, указанные в п. 1.7. настоящей Политики, обрабатываются в целях осуществления аналитики Сервиса, принципов использования Сервиса, совершенствования функционирования Сервиса, решения технических проблем Сервиса, разработки новых продуктов, расширения услуг, выявления популярности мероприятий и определения эффективности рекламных кампаний; обеспечения безопасности
-и предотвращения мошенничества, предоставления эффективной клиентской поддержки.
- 2.3. Обработка персональных данных Субъекта осуществляется Оператором с момента получения согласия Субъекта персональных данных. Согласие на обработку персональных данных может быть дано Субъектом персональных данных в любой форме, позволяющей подтвердить факт получения согласия, если иное не установлено федеральным законом: в письменной, устной или иной форме, предусмотренной действующим законодательством, в том числе посредством совершения Субъектом персональных данных конклюдентных действий (акцепта размещенной на Сервисе оферты – пользовательского соглашения об условиях доставки). В случае отсутствия согласия Субъекта персональных данных на обработку его персональных данных, такая обработка не осуществляется.
+
+ 2.1. Оператор осуществляет обработку персональных данных Субъектов путем
+ ведения баз данных автоматизированным, механическим, ручным способами в целях:
+
+
+ 2.1.1. обработки заказов, запросов или других действий Субъекта, связанных с
+ регистрацией, авторизацией на Сервисе, осуществлением заказов через Сервис;
+
+
+ 2.1.2. оповещения об изменении пользовательского соглашения об условиях
+ доставки, порядка оказания услуг, меню, перечня проводимых Оператором акций,
+ скидок и иных маркетинговых мероприятий.
+
+
+ 2.1.3. в иных целях в случае, если соответствующие действия Оператора не
+ противоречат действующему законодательству, деятельности Оператора, и на
+ проведение указанной обработки получено согласие Субъекта персональных данных.
+
+
+ 2.2. Данные, указанные в п. 1.7. настоящей Политики, обрабатываются в целях
+ осуществления аналитики Сервиса, принципов использования Сервиса,
+ совершенствования функционирования Сервиса, решения технических проблем
+ Сервиса, разработки новых продуктов, расширения услуг, выявления популярности
+ мероприятий и определения эффективности рекламных кампаний; обеспечения
+ безопасности и предотвращения мошенничества, предоставления эффективной
+ клиентской поддержки.
+
+
+ 2.3. Обработка персональных данных Субъекта осуществляется Оператором с
+ момента получения согласия Субъекта персональных данных. Согласие на обработку
+ персональных данных может быть дано Субъектом персональных данных в любой
+ форме, позволяющей подтвердить факт получения согласия, если иное не
+ установлено федеральным законом: в письменной, устной или иной форме,
+ предусмотренной действующим законодательством, в том числе посредством
+ совершения Субъектом персональных данных конклюдентных действий (акцепта
+ размещенной на Сервисе оферты – пользовательского соглашения об условиях
+ доставки). В случае отсутствия согласия Субъекта персональных данных на
+ обработку его персональных данных, такая обработка не осуществляется.
+
- 2.4. Персональные данные Субъектов получаются Оператором:
- 2.4.1. посредством личной передачи Субъектом персональных данных при внесении сведений в формы
-в электронном виде на Сервисе Оператора;
- 2.4.2. посредством личной передачи Субъектом персональных данных посредством сообщения в устной форме по телефону в процессе оформления заказа;
- 2.4.3. иными способами, не противоречащими законодательству Российской Федерации и требованиям международного законодательства о защите персональных данных.
- 2.5. Согласие на обработку персональных данных считается предоставленным посредством совершения Субъектом персональных данных любого действия или совокупности следующих действий:
- 2.5.1. оформления заказа на Сервисе Оператора;
- 2.5.2. проставления на Сервисе в соответствующей форме отметки о согласии на обработку
-персональных данных;
- 2.5.3. отправки посредством смс кода (сообщения) персональных данных при авторизации/регистрации, оформления Заказа на Сервисе Оператора.
- 3. Правовые основания обработки персональных данных
- 3.1. Оператор обрабатывает персональные данные Субъекта на основании:
- 3.1.1. Пользовательского соглашения об условиях доставки, размещенного на Сервисе Оператора;
- 3.1.2. Устава Оператора.
- 3.2. Оператор обрабатывает персональные данные Субъекта только в случае их отправки Субъектом через соответствующие электронные формы, расположенные на Сервисе Оператора или переданные лично субъектом посредством телефонной связи при оформлении Заказа. Отправляя свои персональные данные Оператору, Субъект выражает свое согласие с настоящей Политикой.
- 3.3. Оператор обрабатывает обезличенные данные о Пользователе в случае, если Пользователь разрешил это в настройках браузера (включено сохранение файлов «cookie» и использование технологии JavaScript) в соответствии с положениями настоящей Политики.
- 4. Порядок и условия обработки персональных данных
- 4.1. Оператор осуществляет обработку персональных данных посредством совершения любого действия (операции) или совокупности действий (операций), включая следующие: сбор; запись; систематизация; накопление; хранение; уточнение (обновление, изменение); извлечение; использование; передача; обезличивание; блокирование; удаление; уничтожение.
- 4.2. В соответствии с настоящей Политикой Оператор может осуществлять обработку персональных данных самостоятельно, а также с привлечением третьих лиц, которые привлекаются по поручению Оператора и осуществляют обработку для выполнения указанных в настоящей Политики целей.
- 4.3. В случае поручения обработки персональных данных третьему лицу, объем передаваемых третьему лицу для обработки персональных данных и количество используемых этим лицом способов обработки должны быть минимально необходимым и для выполнения им своих обязанностей перед Оператором.
-В отношении обработки персональных данных третьим лицом устанавливается обязанность такого лица соблюдать конфиденциальность персональных данных и обеспечивать безопасность персональных данных при их обработке.
- 4.4. В процессе предоставления услуг, при осуществлении внутрихозяйственной деятельности Оператор использует автоматизированную, с применением средств вычислительной техники, так
-и неавтоматизированную, с применением бумажного документооборота, обработку
-персональных данных.
- 4.5. В отношении персональных данных Субъекта сохраняется конфиденциальность, кроме случаев добровольного предоставления Субъектом своих персональных данных неограниченному кругу лиц.
-В данном случае Субъект соглашается с тем, что часть его персональных данных
-становится общедоступной.
- 5. Сведения о реализуемых требованиях к защите персональных данных
- 5.1. Оператор защищает персональные данные Субъекта в соответствии с требованиями, предъявляемыми к защите такого рода информации, и несет ответственность за использование безопасных методов защиты такой информации.
- 5.2. Для защиты персональных данных Субъекта, обеспечения их надлежащего использования
-и предотвращения несанкционированного и/или случайного доступа к ним третьими лицами, Оператор применяет необходимые и достаточные технические и административные меры. Предоставляемые Субъектом персональные данные хранятся на серверах с ограниченным доступом, расположенных
-в охраняемых помещениях.
- 5.3. В случае подтверждения факта неточности персональных данных или неправомерности их обработки, персональные данные подлежат их актуализации Оператором, а обработка должна быть прекращена, соответственно.
- 5.4. Субъект может в любой момент отозвать свое согласие на обработку персональных данных при условии, что подобная процедура не нарушает требований законодательства Российской Федерации.
- 5.5. Согласие Субъекта персональных данных считается полученным в установленном действующем законодательством и настоящей Политикой порядке и действует до момента направления Субъектом персональных данных соответствующего заявления (уведомления) о прекращении обработки персональных данных по юридическому адресу Оператора: юридический адрес
- 5.5.1. В случае отзыва Субъектом персональных данных согласия на обработку его персональных данных, Оператор должен прекратить их обработку или обеспечить прекращение такой обработки (если обработка осуществляется другим лицом, действующим по поручению Оператора) и в случае, если сохранение персональных данных более не требуется для целей их обработки, уничтожить персональные данные или обеспечить их уничтожение (если обработка персональных данных осуществляется третьим лицом, действующим по поручению Оператора) в срок, не превышающий 30 (Тридцати) дней с даты поступления указанного отзыва, если иное не предусмотрено договором, стороной которого, выгодоприобретателем или поручителем по которому является Субъект, иным соглашением между Оператором и Субъектом персональных данных, либо если Оператор не вправе осуществлять обработку персональных данных без согласия Субъекта персональных данных на основаниях, предусмотренных Федеральным законом № 152-ФЗ «О персональных данных» от 27.07.2006 г. или другими
-федеральными законами.
- 6. Согласие на получение рекламной информации
- 6.1. Согласие Субъекта персональных данных на получение рекламной информации,
-подтверждается посредством:
- 6.1.1. оформления заказа на Сервисе Оператора;
- 6.1.2. проставления на Сервисе в соответствующей форме отметки о согласии на обработку
-персональных данных;
- 6.1.3. сообщения персональных данных в устной форме, при обращении по телефону Оператора
-в процессе оформлении заказа, означает согласие Субъекта персональных данных на получение
-от Оператора и привлеченных Оператором третьих лиц, по сетям электросвязи (по предоставленным номеру мобильного телефона и адресу электронной почты) информационных сообщений, а в том числе информации коммерческого рекламного характера (рекламы).
- 6.2. Давая согласие, указанное в п. 6.1. настоящей Политики, Субъект персональных данных подтверждает, что действует по своей воле и в своем интересе, а также то, что указанные персональные данные являются достоверными.
+
+ 2.4. Персональные данные Субъектов получаются Оператором:
+
+
+ 2.4.1. посредством личной передачи Субъектом персональных данных при внесении
+ сведений в формы в электронном виде на Сервисе Оператора;
+
+
+ 2.4.2. посредством личной передачи Субъектом персональных данных посредством
+ сообщения в устной форме по телефону в процессе оформления заказа;
+
+
+ 2.4.3. иными способами, не противоречащими законодательству Российской
+ Федерации и требованиям международного законодательства о защите персональных
+ данных.
+
+
+ 2.5. Согласие на обработку персональных данных считается предоставленным
+ посредством совершения Субъектом персональных данных любого действия или
+ совокупности следующих действий:
+
+
+ 2.5.1. оформления заказа на Сервисе Оператора;
+
+
+ 2.5.2. проставления на Сервисе в соответствующей форме отметки о согласии на
+ обработку персональных данных;
+
+
+ 2.5.3. отправки посредством смс кода (сообщения) персональных данных при
+ авторизации/регистрации, оформления Заказа на Сервисе Оператора.
+
+
+ 3. Правовые основания обработки персональных данных
+
+
+ 3.1. Оператор обрабатывает персональные данные Субъекта на основании:
+
+
+ 3.1.1. Пользовательского соглашения об условиях доставки, размещенного на
+ Сервисе Оператора;
+
+
+ 3.1.2. Устава Оператора.
+
+
+ 3.2. Оператор обрабатывает персональные данные Субъекта только в случае их
+ отправки Субъектом через соответствующие электронные формы, расположенные на
+ Сервисе Оператора или переданные лично субъектом посредством телефонной связи
+ при оформлении Заказа. Отправляя свои персональные данные Оператору, Субъект
+ выражает свое согласие с настоящей Политикой.
+
+
+ 3.3. Оператор обрабатывает обезличенные данные о Пользователе в случае, если
+ Пользователь разрешил это в настройках браузера (включено сохранение файлов
+ «cookie» и использование технологии JavaScript) в соответствии с положениями
+ настоящей Политики.
+
+
+ 4. Порядок и условия обработки персональных данных
+
+
+ 4.1. Оператор осуществляет обработку персональных данных посредством
+ совершения любого действия (операции) или совокупности действий (операций),
+ включая следующие: сбор; запись; систематизация; накопление; хранение;
+ уточнение (обновление, изменение); извлечение; использование; передача;
+ обезличивание; блокирование; удаление; уничтожение.
+
+
+ 4.2. В соответствии с настоящей Политикой Оператор может осуществлять
+ обработку персональных данных самостоятельно, а также с привлечением третьих
+ лиц, которые привлекаются по поручению Оператора и осуществляют обработку для
+ выполнения указанных в настоящей Политики целей.
+
+
+ 4.3. В случае поручения обработки персональных данных третьему лицу, объем
+ передаваемых третьему лицу для обработки персональных данных и количество
+ используемых этим лицом способов обработки должны быть минимально необходимым
+ и для выполнения им своих обязанностей перед Оператором. В отношении обработки
+ персональных данных третьим лицом устанавливается обязанность такого лица
+ соблюдать конфиденциальность персональных данных и обеспечивать безопасность
+ персональных данных при их обработке.
+
+
+ 4.4. В процессе предоставления услуг, при осуществлении внутрихозяйственной
+ деятельности Оператор использует автоматизированную, с применением средств
+ вычислительной техники, так и неавтоматизированную, с применением бумажного
+ документооборота, обработку персональных данных.
+
+
+ 4.5. В отношении персональных данных Субъекта сохраняется конфиденциальность,
+ кроме случаев добровольного предоставления Субъектом своих персональных данных
+ неограниченному кругу лиц. В данном случае Субъект соглашается с тем, что
+ часть его персональных данных становится общедоступной.
+
+
+ 5. Сведения о реализуемых требованиях к защите персональных данных
+
+
+ 5.1. Оператор защищает персональные данные Субъекта в соответствии с
+ требованиями, предъявляемыми к защите такого рода информации, и несет
+ ответственность за использование безопасных методов защиты такой информации.
+
+
+ 5.2. Для защиты персональных данных Субъекта, обеспечения их надлежащего
+ использования и предотвращения несанкционированного и/или случайного доступа к
+ ним третьими лицами, Оператор применяет необходимые и достаточные технические
+ и административные меры. Предоставляемые Субъектом персональные данные
+ хранятся на серверах с ограниченным доступом, расположенных в охраняемых
+ помещениях.
+
+
+ 5.3. В случае подтверждения факта неточности персональных данных или
+ неправомерности их обработки, персональные данные подлежат их актуализации
+ Оператором, а обработка должна быть прекращена, соответственно.
+
+
+ 5.4. Субъект может в любой момент отозвать свое согласие на обработку
+ персональных данных при условии, что подобная процедура не нарушает требований
+ законодательства Российской Федерации.
+
+
+ 5.5. Согласие Субъекта персональных данных считается полученным в
+ установленном действующем законодательством и настоящей Политикой порядке и
+ действует до момента направления Субъектом персональных данных
+ соответствующего заявления (уведомления) о прекращении обработки персональных
+ данных по юридическому адресу Оператора:{' '}
+ юридический адрес
+
+
+ 5.5.1. В случае отзыва Субъектом персональных данных согласия на обработку его
+ персональных данных, Оператор должен прекратить их обработку или обеспечить
+ прекращение такой обработки (если обработка осуществляется другим лицом,
+ действующим по поручению Оператора) и в случае, если сохранение персональных
+ данных более не требуется для целей их обработки, уничтожить персональные
+ данные или обеспечить их уничтожение (если обработка персональных данных
+ осуществляется третьим лицом, действующим по поручению Оператора) в срок, не
+ превышающий 30 (Тридцати) дней с даты поступления указанного отзыва, если иное
+ не предусмотрено договором, стороной которого, выгодоприобретателем или
+ поручителем по которому является Субъект, иным соглашением между Оператором и
+ Субъектом персональных данных, либо если Оператор не вправе осуществлять
+ обработку персональных данных без согласия Субъекта персональных данных на
+ основаниях, предусмотренных Федеральным законом № 152-ФЗ «О персональных
+ данных» от 27.07.2006 г. или другими федеральными законами.
+
+
+ 6. Согласие на получение рекламной информации
+
+
+ 6.1. Согласие Субъекта персональных данных на получение рекламной информации,
+ подтверждается посредством:
+
+
+ 6.1.1. оформления заказа на Сервисе Оператора;
+
+
+ 6.1.2. проставления на Сервисе в соответствующей форме отметки о согласии на
+ обработку персональных данных;
+
+
+ 6.1.3. сообщения персональных данных в устной форме, при обращении по телефону
+ Оператора в процессе оформлении заказа, означает согласие Субъекта
+ персональных данных на получение от Оператора и привлеченных Оператором
+ третьих лиц, по сетям электросвязи (по предоставленным номеру мобильного
+ телефона и адресу электронной почты) информационных сообщений, а в том числе
+ информации коммерческого рекламного характера (рекламы).
+
+
+ 6.2. Давая согласие, указанное в п. 6.1. настоящей Политики, Субъект
+ персональных данных подтверждает, что действует по своей воле и в своем
+ интересе, а также то, что указанные персональные данные являются достоверными.
+
7. Возрастные ограничения
- 7.1.В соответствии с ГК РФ несовершеннолетние в возрасте от четырнадцати до восемнадцати лет вправе самостоятельно, без согласия родителей, усыновителей и попечителей, распоряжаться своими заработком, стипендией и иными доходами, а также совершать мелкие бытовые сделки и иные сделки, предусмотренные Гражданским кодексом Российской Федерации.
- 7.2.Только родители могут разрешать или запрещать ребенку предоставлять доступ к своему аккаунту приложениям и сайтам, включив функцию родительского контроля на устройстве.
- 7.3. В случае, если мы получим достоверные сведения о возрасте Пользователя - до 14 лет мы ограничим несовершеннолетнего от совершения им сделок, не предусмотренных законодательством РФ, путем блокировки возможности оформления заказов.
+
+ 7.1.В соответствии с ГК РФ несовершеннолетние в возрасте от четырнадцати до
+ восемнадцати лет вправе самостоятельно, без согласия родителей, усыновителей и
+ попечителей, распоряжаться своими заработком, стипендией и иными доходами, а
+ также совершать мелкие бытовые сделки и иные сделки, предусмотренные
+ Гражданским кодексом Российской Федерации.
+
+
+ 7.2.Только родители могут разрешать или запрещать ребенку предоставлять доступ
+ к своему аккаунту приложениям и сайтам, включив функцию родительского контроля
+ на устройстве.
+
+
+ 7.3. В случае, если мы получим достоверные сведения о возрасте Пользователя -
+ до 14 лет мы ограничим несовершеннолетнего от совершения им сделок, не
+ предусмотренных законодательством РФ, путем блокировки возможности оформления
+ заказов.
+
8. Заключительные положения
- 8.1. Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью телефонного номера номер поддержки , либо на почтовый адрес Оператора: юридический адрес юридический адрес
- 8.2. В настоящем документе будут отражены любые изменения Политики обработки персональных данных Оператором.
- 8.3. Настоящая Политика размещена по адресу: https://goodfood.acceleratorpracticum.ru/
+
+ 8.1. Пользователь может получить любые разъяснения по интересующим вопросам,
+ касающимся обработки его персональных данных, обратившись к Оператору с
+ помощью телефонного номера{' '}
+ номер поддержки , либо на
+ почтовый адрес Оператора:{' '}
+ юридический адрес юридический
+ адрес
+
+
+ 8.2. В настоящем документе будут отражены любые изменения Политики обработки
+ персональных данных Оператором.{' '}
+
+
+ 8.3. Настоящая Политика размещена по адресу:{' '}
+
+ https://goodfood.acceleratorpracticum.ru/
+ {' '}
+
>
diff --git a/src/pages/delivery-conditions/delivery-conditions.module.scss b/src/pages/delivery-conditions/delivery-conditions.module.scss
index 40adcbbc..f2833587 100644
--- a/src/pages/delivery-conditions/delivery-conditions.module.scss
+++ b/src/pages/delivery-conditions/delivery-conditions.module.scss
@@ -1 +1 @@
-@use '@pages/agreement/agreement.module.scss' as *;
\ No newline at end of file
+@use '@pages/agreement/agreement.module.scss' as *;
diff --git a/src/pages/delivery-conditions/index.tsx b/src/pages/delivery-conditions/index.tsx
index 22e10036..eba27077 100644
--- a/src/pages/delivery-conditions/index.tsx
+++ b/src/pages/delivery-conditions/index.tsx
@@ -10,180 +10,806 @@ const DeliveryConditions: React.FC = () => {
Термины и определения
- В настоящем соглашении, если из контекста не следует иное, нижеприведенные термины имеют следующие значения и являются её составной неотъемлемой частью: «Соглашение» - настоящий документ, размещенный в сети Интернет по адресу: https://goodfood.acceleratorpracticum.ru/ , являющееся открытым и общедоступным документом.
+ В настоящем соглашении, если из контекста не следует иное, нижеприведенные
+ термины имеют следующие значения и являются её составной неотъемлемой частью:
+ «Соглашение» - настоящий документ, размещенный в сети Интернет по адресу:{' '}
+
+ https://goodfood.acceleratorpracticum.ru/
+
+ , являющееся открытым и общедоступным документом.
- Действующая редакция Соглашения располагается в сети Интернет по адресу: https://goodfood.acceleratorpracticum.ru/delivery-conditions
+ Действующая редакция Соглашения располагается в сети Интернет по адресу:{' '}
+
+ https://goodfood.acceleratorpracticum.ru/delivery-conditions
+
- «Пользователь» - любое физическое лицо, надлежащим образом присоединившееся к настоящему Соглашению для использования сайта/приложения Компании и/или оформления Заказа.
+ «Пользователь» - любое физическое лицо, надлежащим образом присоединившееся к
+ настоящему Соглашению для использования сайта/приложения Компании и/или
+ оформления Заказа.
- «Компания» - Общество с ограниченной ответственностью «ГудФуд» (ОГРН номер, ИНН номер), оказывающая услуги Пользователю по осуществлению Заказа Товара и Доставки на условиях, предусмотренных в Пользовательском соглашении посредством Сайта: https://goodfood.acceleratorpracticum.ru/ (далее – Сайт) и/или мобильного приложения.
+ «Компания» - Общество с ограниченной ответственностью «ГудФуд» (ОГРН номер,
+ ИНН номер), оказывающая услуги Пользователю по осуществлению Заказа Товара и
+ Доставки на условиях, предусмотренных в Пользовательском соглашении
+ посредством Сайта:{' '}
+
+ https://goodfood.acceleratorpracticum.ru/
+ {' '}
+ (далее – Сайт) и/или мобильного приложения.
- «Исполнитель» - юридическое лицо или индивидуальный предприниматель, осуществляющее(ий) приготовление/реализацию Товара для Пользователя.
+ «Исполнитель» - юридическое лицо или индивидуальный предприниматель,
+ осуществляющее(ий) приготовление/реализацию Товара для Пользователя.
- «Доставщик» - лица, осуществляющие доставку Товара, Заказ которого оформлен Пользователем (за исключением случая осуществления доставки Товара Компанией Исполнителем).
+ «Доставщик» - лица, осуществляющие доставку Товара, Заказ которого оформлен
+ Пользователем (за исключением случая осуществления доставки Товара Компанией
+ Исполнителем).
- «Товар» - пищевая продукция и напитки (за исключением алкогольных), приготовление которых для Пользователей осуществляет Исполнитель в результате оформления Пользователем Заказа на Сайте и/или Приложении Компании. При упоминании в настоящем соглашении или иных документах Товара, имеется в виду как один Товар, так и несколько Товаров, если иное не следует из соглашения или соответствующего документа.
+ «Товар» - пищевая продукция и напитки (за исключением алкогольных),
+ приготовление которых для Пользователей осуществляет Исполнитель в результате
+ оформления Пользователем Заказа на Сайте и/или Приложении Компании. При
+ упоминании в настоящем соглашении или иных документах Товара, имеется в виду
+ как один Товар, так и несколько Товаров, если иное не следует из соглашения
+ или соответствующего документа.
- «Приложение» - мобильное приложение Компании, разработанное для удобства Пользователя с целью выбора Товара и оформления Заказа, размещенного на Сайте.
+ «Приложение» - мобильное приложение Компании, разработанное для удобства
+ Пользователя с целью выбора Товара и оформления Заказа, размещенного на Сайте.
- «Сайт» - официальная веб-страница Компании, расположенная по адресу: https://goodfood.acceleratorpracticum.ru/ посредством которой Пользователь получает информацию о Товаре и оформляет Заказ.
+ «Сайт» - официальная веб-страница Компании, расположенная по адресу:{' '}
+
+ https://goodfood.acceleratorpracticum.ru/
+ {' '}
+ посредством которой Пользователь получает информацию о Товаре и оформляет
+ Заказ.
- «Сервис» - Сайт и мобильное приложение Компании.
+ «Сервис» - Сайт и мобильное приложение Компании.
- «Регистрация» - процедура внесения Пользователем своих Персональных данных в определённую электронную форму на Сервисе с целью получения доступа к услугам в соответствии с положениями настоящего Соглашения. Регистрация может быть совершена Пользователем как отдельно, так и совместно с созданием Заказа на Сервисе.
+ «Регистрация» - процедура внесения Пользователем своих Персональных данных в
+ определённую электронную форму на Сервисе с целью получения доступа к услугам
+ в соответствии с положениями настоящего Соглашения. Регистрация может быть
+ совершена Пользователем как отдельно, так и совместно с созданием Заказа на
+ Сервисе.
- «Заказ» - оформленный Пользователем на Сервисе Заказ Товара и его Доставки, в результате которого Пользователь заключает договор о приготовлении Товара с Исполнителем, и договор доставки данного Товара на условиях, размещенных на Сервисе.
+ «Заказ» - оформленный Пользователем на Сервисе Заказ Товара и его Доставки, в
+ результате которого Пользователь заключает договор о приготовлении Товара с
+ Исполнителем, и договор доставки данного Товара на условиях, размещенных на
+ Сервисе.
- «Договор» - договор о приготовлении/ реализации Товара и/или его доставки, заключаемый между Пользователем и Исполнителем в результате оформления Заказа.
+ «Договор» - договор о приготовлении/ реализации Товара и/или его доставки,
+ заключаемый между Пользователем и Исполнителем в результате оформления Заказа.
- «Доставка» - услуга доставки Товара до адреса, указанного Пользователем, Заказ которого Пользователь оформил на Сайте или в Приложении,. Заключение с Пользователем договора о доставке осуществляет Доставщик, либо, непосредственно Исполнитель посредством оформления Пользователем Заказа на сервисе. Исполнитель или Доставщик имеют право привлекать третьих лиц для осуществления доставки Товара до Пользователя.
+ «Доставка» - услуга доставки Товара до адреса, указанного Пользователем, Заказ
+ которого Пользователь оформил на Сайте или в Приложении,. Заключение с
+ Пользователем договора о доставке осуществляет Доставщик, либо,
+ непосредственно Исполнитель посредством оформления Пользователем Заказа на
+ сервисе. Исполнитель или Доставщик имеют право привлекать третьих лиц для
+ осуществления доставки Товара до Пользователя.
- «Промокод» - определенная последовательность символов, при условии активации которой и соблюдении иных условий использования Промокода Пользователю предоставляется скидка на стоимость Товара и/или Доставки.
+ «Промокод» - определенная последовательность символов, при условии активации
+ которой и соблюдении иных условий использования Промокода Пользователю
+ предоставляется скидка на стоимость Товара и/или Доставки.
+
+
+ «Персональные данные» – личная информация (включая фамилию, имя, отчество,
+ дату рождения, почтовый адрес, контактный телефон и адрес электронной почты, а
+ также другую информацию, которую Пользователь сообщает, используя Сервис),
+ данную информацию Пользователь предоставляет осознанно и добровольно в момент
+ Регистрации на Сервисе и/или оформления Заказа на Сервисе в соответствии с
+ положениями настоящего Соглашения и Политики хранения и обработки персональных
+ данных.
+
+
+ «Пользовательское соглашение» — настоящий документ, размещенный в сети
+ Интернет по адресу:{' '}
+
+ ссылка на страницу с данным соглашением
+
+
+
+ «Политика обработки и хранения персональных данных» - документ, определяющий
+ порядок и условия осуществления обработки персональных данных Пользователей,
+ размещенный в сети Интернет по адресу:{' '}
+
+ https://goodfood.acceleratorpracticum.ru/
+
+
+
+ 1. Общие положения. Предмет соглашения
+
+
+ 1.1. Перед тем как начать использовать Сервис Пользователь обязан ознакомиться
+ с настоящим Соглашением и присоединиться к нему. Пользователь подтверждает,
+ что прочитал, понял и полностью согласен соблюдать настоящее Соглашение.
+
+
+ Обращаем Ваше внимание, что родители несут полную ответственность за жизнь и
+ здоровье своих детей (согласно ст.63, 65 Семейного кодекса РФ, ст.5.35. КоАП
+ РФ.). Употребление содержащихся в составе блюд морепродуктов, морской капусты,
+ а также имбиря и вассаби должно соответствовать определенному этапу
+ формирования детского организма, с учетом возможных аллергических реакций.
+ Рекомендуем до 7-8 летнего возраста приобретать для детей блюда из раздела
+ "Детское меню".
+
+
+ 1.2. Сервис предлагает Пользователю доступ к поиску и Заказу готовой еды на
+ условиях, предусмотренных настоящим Соглашением. В соответствии с настоящим
+ соглашением Пользователь при оформлении Заказа заключает Договор с Компанией,
+ в связи с чем возникают прямые договорные отношения с Исполнителем в части
+ приготовления и/или реализации Товара и осуществления Доставки.
+
+
+ 1.3. В соответствии со статьей 437 Гражданского Кодекса Российской Федерации
+ (далее по тексту соглашения - ГК РФ) настоящее соглашение является публичной
+ офертой, адресованной физическим лицам, и в случае принятия изложенных условий
+ в настоящем соглашении, физическое лицо обязуется произвести оплату Товара на
+ условиях, изложенных в настоящем соглашении.
+
+
+ В соответствии с пунктом 3 статьи 438 ГК РФ, момент окончательного
+ подтверждения Заказа Пользователем является акцептом оферты, что является
+ равносильным заключению Договора розничной купли-продажи Товара на условиях,
+ установленных в настоящем соглашении и на Сервисе.
+
+
+ 1.3.1. Компания или Исполнитель вправе отказаться от заключения публичного
+ договора при отсутствии технической (поломки оборудования, в том числе при
+ принятии/подтверждении заказа), физической (удаленность Потребителя от
+ территории оказания услуг) и иной возможности предоставить потребителю товары,
+ услуги надлежащего качества.
+
+
+ 1.4. Принимая условия настоящего Соглашения, Пользователь подтверждает свое
+ информированное и добровольное согласие на обработку его персональных данных,
+ предоставленных при регистрации, в том числе, но не ограничиваясь: для ответа
+ на обращения Пользователя в службу технической поддержки Сервиса, для
+ разрешения возможных претензий, для участия в стимулирующих, рекламных,
+ маркетинговых и иных мероприятиях, направленных на продвижение услуг Компании,
+ партнеров Исполнителя и иных третьих лиц. Компания вправе направлять
+ Пользователю информацию о функционировании Сервиса на адрес электронной почты,
+ на номер телефона, указанные Пользователем, а также направлять собственные или
+ принадлежащие Компании информационные, рекламные или иные сообщения, или
+ размещать соответствующую информацию в самом сервисе. Также Пользователь
+ подтверждает свое согласие на передачу указанных выше персональных данных
+ Компании и их обработку Компанией в целях исполнения настоящего Соглашения и
+ реализации функционирования Сервиса, а также разрешения претензий, связанных с
+ исполнением настоящего Соглашения.
+
+
+ Принимая условия настоящего Соглашения, Пользователь в том числе принимает
+ условия Политики хранения и обработки персональных данных.
+
+
+ 1.5. Настоящее соглашение может быть изменено Компанией в одностороннем
+ порядке. Уведомление пользователей, при внесении изменений в Соглашение,
+ происходит путем размещения новой редакции Соглашения по постоянному адресу{' '}
+
+ https://goodfood.acceleratorpracticum.ru/delivery-conditions
+ {' '}
+ не позднее, чем за 10 дней до вступления в силу соответствующих изменений.
+ Предыдущие редакции Соглашения хранятся в архиве документации Компании
+ Заказчика. При этом продолжение использования сервиса после внесения изменений
+ и/или дополнений в настоящее Соглашение, означает согласие Пользователя с
+ такими изменениями и/или дополнениями, в связи с чем Пользователь обязуется
+ регулярно отслеживать изменения в соответствующем разделе и в Соглашении,
+ размещенном на сайте{' '}
+
+ https://goodfood.acceleratorpracticum.ru/delivery-conditions
+ {' '}
+ Пользователь вправе отказаться от принятия изменений Соглашения, что означает
+ отказ Пользователя от использования сервиса.
+
+
+ 2. Права и обязанности Пользователя
+
+
+ 2.1. Пользователь обязуется надлежащим образом соблюдать условия настоящего
+ Соглашения.
+
+
+ 2.2. При заказе посредством Сервиса Пользователь обязуется сообщать
+ достоверную информацию о себе для надлежащего исполнения обязательств,
+ предусмотренных настоящим соглашением.
+
+
+ 2.3. Пользователь обязуется принимать надлежащие меры для обеспечения
+ сохранности его мобильного устройства и несет личную ответственность за
+ сохранность личных данных, указанных на сервисе, за безопасность своего логина
+ и пароля.
+
+
+ 2.4. Пользователь обязуется не использовать сервис для любых иных целей, кроме
+ как для целей, связанных с личным некоммерческим использованием.
+
+
+ 2.5. Пользователь обязуется, пользуясь сервисом, не вводить в заблуждение
+ других Пользователей и третьих лиц.
+
+
+ 2.6. Пользователь обязуется не использовать стороннее программное обеспечение
+ и другие технические средства, влияющие на работу сервиса и связанную с ней
+ систему.
- «Персональные данные» – личная информация (включая фамилию, имя, отчество, дату рождения, почтовый адрес, контактный телефон и адрес электронной почты, а также другую информацию, которую Пользователь сообщает, используя Сервис), данную информацию Пользователь предоставляет осознанно и добровольно в момент Регистрации на Сервисе и/или оформления Заказа на Сервисе в соответствии с положениями настоящего Соглашения и Политики хранения и обработки персональных данных.
- «Пользовательское соглашение» — настоящий документ, размещенный в сети Интернет по адресу: ссылка на страницу с данным соглашением
- «Политика обработки и хранения персональных данных» - документ, определяющий порядок и условия осуществления обработки персональных данных Пользователей, размещенный в сети Интернет по адресу: https://goodfood.acceleratorpracticum.ru/
- 1. Общие положения. Предмет соглашения
- 1.1. Перед тем как начать использовать Сервис Пользователь обязан ознакомиться с настоящим Соглашением и присоединиться к нему. Пользователь подтверждает, что прочитал, понял и полностью согласен соблюдать настоящее Соглашение.
- Обращаем Ваше внимание, что родители несут полную ответственность за жизнь и здоровье своих детей (согласно ст.63, 65 Семейного кодекса РФ, ст.5.35. КоАП РФ.). Употребление содержащихся в составе блюд морепродуктов, морской капусты, а также имбиря и вассаби должно соответствовать определенному этапу формирования детского организма, с учетом возможных аллергических реакций. Рекомендуем до 7-8 летнего возраста приобретать для детей блюда из раздела "Детское меню".
- 1.2. Сервис предлагает Пользователю доступ к поиску и Заказу готовой еды на условиях, предусмотренных настоящим Соглашением. В соответствии с настоящим соглашением Пользователь при оформлении Заказа заключает Договор с Компанией, в связи с чем возникают прямые договорные отношения с Исполнителем в части приготовления и/или реализации Товара и осуществления Доставки.
- 1.3. В соответствии со статьей 437 Гражданского Кодекса Российской Федерации (далее по тексту соглашения - ГК РФ) настоящее соглашение является публичной офертой, адресованной физическим лицам, и в случае принятия изложенных условий в настоящем соглашении, физическое лицо обязуется произвести оплату Товара на условиях, изложенных в настоящем соглашении.
- В соответствии с пунктом 3 статьи 438 ГК РФ, момент окончательного подтверждения Заказа Пользователем является акцептом оферты, что является равносильным заключению Договора розничной купли-продажи Товара на условиях, установленных в настоящем соглашении и на Сервисе.
- 1.3.1. Компания или Исполнитель вправе отказаться от заключения публичного договора при отсутствии технической (поломки оборудования, в том числе при принятии/подтверждении заказа), физической (удаленность Потребителя от территории оказания услуг) и иной возможности предоставить потребителю товары, услуги надлежащего качества.
- 1.4. Принимая условия настоящего Соглашения, Пользователь подтверждает свое информированное и добровольное согласие на обработку его персональных данных, предоставленных при регистрации, в том числе, но не ограничиваясь: для ответа на обращения Пользователя в службу технической поддержки Сервиса, для разрешения возможных претензий, для участия в стимулирующих, рекламных, маркетинговых и иных мероприятиях, направленных на продвижение услуг Компании, партнеров Исполнителя и иных третьих лиц. Компания вправе направлять Пользователю информацию о функционировании Сервиса на адрес электронной почты, на номер телефона, указанные Пользователем, а также направлять собственные или принадлежащие Компании информационные, рекламные или иные сообщения, или размещать соответствующую информацию в самом сервисе. Также Пользователь подтверждает свое согласие на передачу указанных выше персональных данных Компании и их обработку Компанией в целях исполнения настоящего Соглашения и реализации функционирования Сервиса, а также разрешения претензий, связанных с исполнением настоящего Соглашения.
- Принимая условия настоящего Соглашения, Пользователь в том числе принимает условия Политики хранения и обработки персональных данных.
- 1.5. Настоящее соглашение может быть изменено Компанией в одностороннем порядке. Уведомление пользователей, при внесении изменений в Соглашение, происходит путем размещения новой редакции Соглашения по постоянному адресу https://goodfood.acceleratorpracticum.ru/delivery-conditions не позднее, чем за 10 дней до вступления в силу соответствующих изменений. Предыдущие редакции Соглашения хранятся в архиве документации Компании Заказчика. При этом продолжение использования сервиса после внесения изменений и/или дополнений в настоящее Соглашение, означает согласие Пользователя с такими изменениями и/или дополнениями, в связи с чем Пользователь обязуется регулярно отслеживать изменения в соответствующем разделе и в Соглашении, размещенном на сайте https://goodfood.acceleratorpracticum.ru/delivery-conditions Пользователь вправе отказаться от принятия изменений Соглашения, что означает отказ Пользователя от использования сервиса.
- 2. Права и обязанности Пользователя
- 2.1. Пользователь обязуется надлежащим образом соблюдать условия настоящего Соглашения.
- 2.2. При заказе посредством Сервиса Пользователь обязуется сообщать достоверную информацию о себе для надлежащего исполнения обязательств, предусмотренных настоящим соглашением.
- 2.3. Пользователь обязуется принимать надлежащие меры для обеспечения сохранности его мобильного устройства и несет личную ответственность за сохранность личных данных, указанных на сервисе, за безопасность своего логина и пароля.
- 2.4. Пользователь обязуется не использовать сервис для любых иных целей, кроме как для целей, связанных с личным некоммерческим использованием.
- 2.5. Пользователь обязуется, пользуясь сервисом, не вводить в заблуждение других Пользователей и третьих лиц.
- 2.6. Пользователь обязуется не использовать стороннее программное обеспечение и другие технические средства, влияющие на работу сервиса и связанную с ней систему.
3. Права и обязанности Компании
- 3.1. Компания вправе заблокировать доступ Пользователя к Сервису в случае обнаружения нарушений Пользователем обязанностей, указанных в разделе 2 настоящего Соглашения.
- 3.2. Компания оставляет за собой право в любой момент расторгнуть настоящее Соглашение по организационным или техническим причинам в одностороннем порядке, заблокировав возможность его использования.
- 3.3. Компания осуществляет обработку персональных данных пользователя в целях исполнения и на условиях настоящего Соглашения.
+
+ 3.1. Компания вправе заблокировать доступ Пользователя к Сервису в случае
+ обнаружения нарушений Пользователем обязанностей, указанных в разделе 2
+ настоящего Соглашения.
+
+
+ 3.2. Компания оставляет за собой право в любой момент расторгнуть настоящее
+ Соглашение по организационным или техническим причинам в одностороннем
+ порядке, заблокировав возможность его использования.
+
+
+ 3.3. Компания осуществляет обработку персональных данных пользователя в целях
+ исполнения и на условиях настоящего Соглашения.
+
4. Оплата Товара и/или Доставки
- 4.1. Оплата Товара и/или Доставки в рамках оформленного Пользователем Заказа, может быть произведена Пользователем:
- 4.1.1. Непосредственно Исполнителю наличными денежными средствами или посредством банковской карты через терминал оплаты в момент доставки Товара. Указанный вид оплаты осуществляется без участия Компании и не регулируется положениям настоящего Соглашения.
- 4.1.2. Пользователю может быть доступна функция оплаты Доставщику (либо третьему лицу, привлекаемому Доставщиком, в случае, если Доставщик привлекает такое третье лицо для осуществления Доставки Товара до Пользователя) наличными денежными средствами в случае осуществления Доставки Товара Доставщиком. В этом случае Доставщик (либо привлекаемое им третье лицо) в части приема денежных средств за Товар действует по поручению Исполнителя.
- 4.1.3. Пользователю может быть доступна функция безналичной оплаты в интерфейсе Сервиса с Привязанной банковской карты (п. 4.3. настоящего соглашения). В этом случае оплата происходит через авторизационный сервер Процессингового центра Банка с использованием Банковских кредитных карт следующих платежных систем: МИР VISA InternationalMasterCard World Wide К оплате принимаются все виды платежных карточек VISA, за исключением Visa Electron. В большинстве случаев карта Visa Electron не применима для оплаты через интернет, за исключением карт, выпущенных отдельными банками. О возможность оплаты картой Visa Electron вам нужно выяснять у банка-эмитента вашей карты.
- Доставщика с привлечением уполномоченного оператора по приему платежей или оператора электронных денежных средств и является получателем платежа в качестве агента Исполнителя и/или Доставщика (далее – «безналичная оплата»). Компания не гарантирует отсутствие ошибок и сбоев в работе сервиса в отношении предоставления возможности безналичной оплаты. Компания вправе
- 4.1.3.2. Для оплаты Заказа Покупатель перенаправляется на платежный шлюз Банка для ввода реквизитов карты. Соединение с платежным шлюзом и передача информации осуществляется в защищенном режиме с использованием протокола шифрования SSL.
- В случае если банк Покупателя поддерживает технологию безопасного проведения интернет-платежей Verified By Visa или MasterCard Secure Code для проведения платежа также может потребоваться ввод специального пароля. Проведение платежей по банковским картам осуществляется в строгом соответствии с требованиями платежных систем Visa Int. и MasterCard Europe Sprl. Введенная информация не будет предоставлена третьим лицам за исключением случаев, предусмотренных законодательством РФ.
- Способы и возможность получения паролей для совершения интернет-платежей Пользователь может уточнить в банке, выпустившем карту.
- 4.1.4. Компания не гарантирует отсутствие ошибок и сбоев в работе Сервиса в отношении предоставления возможности оплаты наличными или безналичными денежными средствами. Выбор соответствующей формы оплаты производится Пользователем в интерфейсе Сервиса. При этом, доступный Пользователю в конкретный момент времени способ оплаты Товара и/или Доставки определяется с учетом технических, временных, материальных и/или иных факторов.
- 4.2. Прием денежных средств Компанией в случае, предусмотренном п. 4.1.3. настоящего соглашения, осуществляется исключительно в связи с тем, что Компания посредством предоставления возможности оформления Заказа на сервисе участвует в реализации Исполнителем Пользователю Товаров, в оплату которых принимаются денежные средства. При этом прием денежных средств, в случае, предусмотренном п. 4.1.2. настоящего соглашения, осуществляется Доставщиком, привлекаемым Исполнителем (либо третьим лицом, привлекаемым Доставщиком), в связи с доставкой Товара Пользователю.Компания и Доставщик не являются платежными агентами при проведении расчетов в соответствии с настоящим соглашением согласно пп. 1, 4 ч. 2 ст. 1 Федерального закона от 03.06.2009 № 103-ФЗ «О деятельности по приему платежей физических лиц, осуществляемой платежными агентами».
- 4.3. Привязанная банковская карта может указываться Пользователем в интерфейсе сервиса, при этом Пользователь указывает следующие данные:
- - Hаименование владельца банковской карты;
- - номер банковской карты;
- - срок окончания действия банковской карты, месяц/год;
- - CVV код для карт Visa / CVC код для Master Card.
- Если на банковской карте код CVC / CVV отсутствует, то, возможно, карта не пригодна для CNP транзакций (т.е. таких транзакций, при которых сама карта не присутствует, а используются её реквизиты), и в данном случае следует обратиться в банк для получения подробной информации.
- Если данные банковской карты верны, действительны и использование данной карты в рамках сервиса технически возможно, указанная банковская карта приобретает статус Привязанной и может быть использована для безналичной оплаты. Все Привязанные карты отображаются в интерфейсе сервиса.
- 4.4. Безналичная оплата осуществляется Пользователем с участием уполномоченного оператора по приему платежей или оператора электронных денежных средств и регулируется правилами международных платежных систем, банков (в том числе банка-эмитента Привязанной карты) и других участников расчетов.
- 4.5. При указании своих данных согласно п. 4.3. настоящего соглашения и дальнейшем использовании Привязанной карты Пользователь подтверждает и гарантирует указание им достоверной и полной информации о действительной банковской карте, выданной на его имя, соблюдение им правил международных платежных систем и требований банка-эмитента, выпустившего Привязанную карту, в том числе в отношении порядка проведения безналичных расчетов.
- 4.6. Пользователь понимает и соглашается, что все действия, совершенные в рамках сервиса после авторизации с помощью логина и пароля, присвоенных ему при регистрации на Сервисе, в том числе по безналичной оплате с использованием Привязанной банковской карты, считаются совершенными Пользователем.
- 4.7. В случае несогласия Пользователя с фактом и/или суммой безналичной оплаты, а также получения некачественного, некомплектного Товара, либо несоответствия полученного Товара заказанному, Пользователь вправе обратиться к Компании по реквизитам, указанным в настоящем соглашении, в течение 2 календарных дней с указанием событий, послуживших причиной обращения.
- При получении Товара Пользователь проверяет его соответствие Заказу, комплектность и отсутствие претензий к внешнему виду доставленного Товара. В случае выявления несоответствий Пользователь вправе потребовать замены на Товары надлежащего качества сразу в момент получения, уведомив об этом Доставщика, либо в течение 5 минут после получения Товара, уведомив об этом Компанию; либо потребовать произвести возврат Пользователю ранее оплаченных денежных средств за данный Товар из Заказа.
- Возврат денежных средств Пользователю производится на банковскую карту Пользователя в течение 5 (Пяти) банковских дней, начиная со следующего банковского дня с момента выставления законного и обоснованного требования о возврате денежных средств.
- Компания/Исполнитель вправе отказать Пользователю в обмене Товара или возврате денежных средств по своему усмотрению, если будет иметь доказательства неправомерных действий со стороны Пользователя. Также Компания/Исполнитль вправе привлечь Пользователя к ответственности в судебном порядке.
- 4.8. По вопросам, связанным с оплатой Товара и/или Доставки способами, предусмотренными пп. 4.1.1., 4.1.2. настоящего соглашения, Пользователь вправе обратиться к Исполнителю и/или к Доставщику.
- 4.9. Компания оставляет за собой право в любой момент потребовать от Пользователя подтверждения данных, указанных им в рамках Сервиса, в том числе данных Привязанной карты, и запросить в связи с этим подтверждающие документы (в частности, документы, удостоверяющие личность), непредставление которых, по усмотрению Компании, может быть приравнено к предоставлению недостоверной информации и повлечь последствия, предусмотренные настоящим соглашением.
- 5. Гарантии и ответственность сторон
- 5.1. Пользователь гарантирует, что не будет предпринимать каких-либо действий, направленных на причинение ущерба Исполнителю, Компании, операторам сотовой мобильной связи, правообладателям или иным лицам.
- 5.2. В случае нарушения правил использования сервиса, указанных в разделе 2 настоящего Соглашения, Пользователь обязуется возместить вред, причиненный такими действиями.
- 5.3. Если Пользователем не доказано обратное, любые действия, совершенные с использованием его мобильного устройства, считаются совершенными соответствующим Пользователем.
- 5.4. Компания не является уполномоченной организацией по смыслу Закона РФ от 07.02.1992 г. № 2300-1 «О защите прав потребителей», и не осуществляет рассмотрение и удовлетворение претензий Пользователей в отношении Товара и/или Доставки ненадлежащего качества, Заказ которого (которых) оформлен Пользователем на Сервисе.При обращении Пользователя к Компании по вопросам, касающимся Договора, заключаемого в результате оформления Заказа, в том числе с претензиями относительно исполнения данного Договора, Компания вправе передать соответствующую информацию Исполнителю и/или Доставщику, а также передать Пользователю информацию, полученную от Исполнителя и/или Доставщика по данным вопросам.
- 5.5. Пользователь при оформлении Заказа, в соответствии с условиями настоящего Соглашения, подтверждает личное ознакомление с ингредиентами, входящими в состав Заказа, которые могут быть несовместимыми с организмом Пользователя в силу индивидуальных особенностей, в частности, могут вызвать отторжение или аллергическую реакцию.
- Пользователь при наступлении указанного в настоящем пункте случая - индивидуальной несовместимости Продукции с организмом Пользователя, подтверждает, что такой случай индивидуальной несовместимости не связан с качеством продукции и соблюдения всех норм, хранения, транспортировки и приготовления Заказа, а является индивидуальной особенностью организма Пользователя о которой Исполнителю не могло быть известно, в связи с чем, Исполнитель не несет ответственности при наступлении такого случая.
+
+ 4.1. Оплата Товара и/или Доставки в рамках оформленного Пользователем Заказа,
+ может быть произведена Пользователем:
+
+
+ 4.1.1. Непосредственно Исполнителю наличными денежными средствами или
+ посредством банковской карты через терминал оплаты в момент доставки Товара.
+ Указанный вид оплаты осуществляется без участия Компании и не регулируется
+ положениям настоящего Соглашения.
+
+
+ 4.1.2. Пользователю может быть доступна функция оплаты Доставщику (либо
+ третьему лицу, привлекаемому Доставщиком, в случае, если Доставщик привлекает
+ такое третье лицо для осуществления Доставки Товара до Пользователя) наличными
+ денежными средствами в случае осуществления Доставки Товара Доставщиком. В
+ этом случае Доставщик (либо привлекаемое им третье лицо) в части приема
+ денежных средств за Товар действует по поручению Исполнителя.
+
+
+ 4.1.3. Пользователю может быть доступна функция безналичной оплаты в
+ интерфейсе Сервиса с Привязанной банковской карты (п. 4.3. настоящего
+ соглашения). В этом случае оплата происходит через авторизационный сервер
+ Процессингового центра Банка с использованием Банковских кредитных карт
+ следующих платежных систем: МИР VISA InternationalMasterCard World Wide К
+ оплате принимаются все виды платежных карточек VISA, за исключением Visa
+ Electron. В большинстве случаев карта Visa Electron не применима для оплаты
+ через интернет, за исключением карт, выпущенных отдельными банками. О
+ возможность оплаты картой Visa Electron вам нужно выяснять у банка-эмитента
+ вашей карты.
+
+
+ Доставщика с привлечением уполномоченного оператора по приему платежей или
+ оператора электронных денежных средств и является получателем платежа в
+ качестве агента Исполнителя и/или Доставщика (далее – «безналичная оплата»).
+ Компания не гарантирует отсутствие ошибок и сбоев в работе сервиса в отношении
+ предоставления возможности безналичной оплаты. Компания вправе
+
+
+ 4.1.3.2. Для оплаты Заказа Покупатель перенаправляется на платежный шлюз Банка
+ для ввода реквизитов карты. Соединение с платежным шлюзом и передача
+ информации осуществляется в защищенном режиме с использованием протокола
+ шифрования SSL.
+
+
+ В случае если банк Покупателя поддерживает технологию безопасного проведения
+ интернет-платежей Verified By Visa или MasterCard Secure Code для проведения
+ платежа также может потребоваться ввод специального пароля. Проведение
+ платежей по банковским картам осуществляется в строгом соответствии с
+ требованиями платежных систем Visa Int. и MasterCard Europe Sprl. Введенная
+ информация не будет предоставлена третьим лицам за исключением случаев,
+ предусмотренных законодательством РФ.
+
+
+ Способы и возможность получения паролей для совершения интернет-платежей
+ Пользователь может уточнить в банке, выпустившем карту.
+
+
+ 4.1.4. Компания не гарантирует отсутствие ошибок и сбоев в работе Сервиса в
+ отношении предоставления возможности оплаты наличными или безналичными
+ денежными средствами. Выбор соответствующей формы оплаты производится
+ Пользователем в интерфейсе Сервиса. При этом, доступный Пользователю в
+ конкретный момент времени способ оплаты Товара и/или Доставки определяется с
+ учетом технических, временных, материальных и/или иных факторов.
+
+
+ 4.2. Прием денежных средств Компанией в случае, предусмотренном п. 4.1.3.
+ настоящего соглашения, осуществляется исключительно в связи с тем, что
+ Компания посредством предоставления возможности оформления Заказа на сервисе
+ участвует в реализации Исполнителем Пользователю Товаров, в оплату которых
+ принимаются денежные средства. При этом прием денежных средств, в случае,
+ предусмотренном п. 4.1.2. настоящего соглашения, осуществляется Доставщиком,
+ привлекаемым Исполнителем (либо третьим лицом, привлекаемым Доставщиком), в
+ связи с доставкой Товара Пользователю.Компания и Доставщик не являются
+ платежными агентами при проведении расчетов в соответствии с настоящим
+ соглашением согласно пп. 1, 4 ч. 2 ст. 1 Федерального закона от 03.06.2009 №
+ 103-ФЗ «О деятельности по приему платежей физических лиц, осуществляемой
+ платежными агентами».
+
+
+ 4.3. Привязанная банковская карта может указываться Пользователем в интерфейсе
+ сервиса, при этом Пользователь указывает следующие данные:
+
+
+ - Hаименование владельца банковской карты;
+
+
+ - номер банковской карты;
+
+
+ - срок окончания действия банковской карты, месяц/год;
+
+
+ - CVV код для карт Visa / CVC код для Master Card.
+
+
+ Если на банковской карте код CVC / CVV отсутствует, то, возможно, карта не
+ пригодна для CNP транзакций (т.е. таких транзакций, при которых сама карта не
+ присутствует, а используются её реквизиты), и в данном случае следует
+ обратиться в банк для получения подробной информации.
+
+
+ Если данные банковской карты верны, действительны и использование данной карты
+ в рамках сервиса технически возможно, указанная банковская карта приобретает
+ статус Привязанной и может быть использована для безналичной оплаты. Все
+ Привязанные карты отображаются в интерфейсе сервиса.
+
+
+ 4.4. Безналичная оплата осуществляется Пользователем с участием
+ уполномоченного оператора по приему платежей или оператора электронных
+ денежных средств и регулируется правилами международных платежных систем,
+ банков (в том числе банка-эмитента Привязанной карты) и других участников
+ расчетов.
+
+
+ 4.5. При указании своих данных согласно п. 4.3. настоящего соглашения и
+ дальнейшем использовании Привязанной карты Пользователь подтверждает и
+ гарантирует указание им достоверной и полной информации о действительной
+ банковской карте, выданной на его имя, соблюдение им правил международных
+ платежных систем и требований банка-эмитента, выпустившего Привязанную карту,
+ в том числе в отношении порядка проведения безналичных расчетов.
+
+
+ 4.6. Пользователь понимает и соглашается, что все действия, совершенные в
+ рамках сервиса после авторизации с помощью логина и пароля, присвоенных ему
+ при регистрации на Сервисе, в том числе по безналичной оплате с использованием
+ Привязанной банковской карты, считаются совершенными Пользователем.
+
+
+ 4.7. В случае несогласия Пользователя с фактом и/или суммой безналичной
+ оплаты, а также получения некачественного, некомплектного Товара, либо
+ несоответствия полученного Товара заказанному, Пользователь вправе обратиться
+ к Компании по реквизитам, указанным в настоящем соглашении, в течение 2
+ календарных дней с указанием событий, послуживших причиной обращения.
+
+
+ При получении Товара Пользователь проверяет его соответствие Заказу,
+ комплектность и отсутствие претензий к внешнему виду доставленного Товара. В
+ случае выявления несоответствий Пользователь вправе потребовать замены на
+ Товары надлежащего качества сразу в момент получения, уведомив об этом
+ Доставщика, либо в течение 5 минут после получения Товара, уведомив об этом
+ Компанию; либо потребовать произвести возврат Пользователю ранее оплаченных
+ денежных средств за данный Товар из Заказа.
+
+
+ Возврат денежных средств Пользователю производится на банковскую карту
+ Пользователя в течение 5 (Пяти) банковских дней, начиная со следующего
+ банковского дня с момента выставления законного и обоснованного требования о
+ возврате денежных средств.
+
+
+ Компания/Исполнитель вправе отказать Пользователю в обмене Товара или возврате
+ денежных средств по своему усмотрению, если будет иметь доказательства
+ неправомерных действий со стороны Пользователя. Также Компания/Исполнитль
+ вправе привлечь Пользователя к ответственности в судебном порядке.
+
+
+ 4.8. По вопросам, связанным с оплатой Товара и/или Доставки способами,
+ предусмотренными пп. 4.1.1., 4.1.2. настоящего соглашения, Пользователь вправе
+ обратиться к Исполнителю и/или к Доставщику.
+
+
+ 4.9. Компания оставляет за собой право в любой момент потребовать от
+ Пользователя подтверждения данных, указанных им в рамках Сервиса, в том числе
+ данных Привязанной карты, и запросить в связи с этим подтверждающие документы
+ (в частности, документы, удостоверяющие личность), непредставление которых, по
+ усмотрению Компании, может быть приравнено к предоставлению недостоверной
+ информации и повлечь последствия, предусмотренные настоящим соглашением.
+
+
+ 5. Гарантии и ответственность сторон
+
+
+ 5.1. Пользователь гарантирует, что не будет предпринимать каких-либо действий,
+ направленных на причинение ущерба Исполнителю, Компании, операторам сотовой
+ мобильной связи, правообладателям или иным лицам.
+
+
+ 5.2. В случае нарушения правил использования сервиса, указанных в разделе 2
+ настоящего Соглашения, Пользователь обязуется возместить вред, причиненный
+ такими действиями.
+
+
+ 5.3. Если Пользователем не доказано обратное, любые действия, совершенные с
+ использованием его мобильного устройства, считаются совершенными
+ соответствующим Пользователем.
+
+
+ 5.4. Компания не является уполномоченной организацией по смыслу Закона РФ от
+ 07.02.1992 г. № 2300-1 «О защите прав потребителей», и не осуществляет
+ рассмотрение и удовлетворение претензий Пользователей в отношении Товара и/или
+ Доставки ненадлежащего качества, Заказ которого (которых) оформлен
+ Пользователем на Сервисе.При обращении Пользователя к Компании по вопросам,
+ касающимся Договора, заключаемого в результате оформления Заказа, в том числе
+ с претензиями относительно исполнения данного Договора, Компания вправе
+ передать соответствующую информацию Исполнителю и/или Доставщику, а также
+ передать Пользователю информацию, полученную от Исполнителя и/или Доставщика
+ по данным вопросам.
+
+
+ 5.5. Пользователь при оформлении Заказа, в соответствии с условиями настоящего
+ Соглашения, подтверждает личное ознакомление с ингредиентами, входящими в
+ состав Заказа, которые могут быть несовместимыми с организмом Пользователя в
+ силу индивидуальных особенностей, в частности, могут вызвать отторжение или
+ аллергическую реакцию.
+
+
+ Пользователь при наступлении указанного в настоящем пункте случая -
+ индивидуальной несовместимости Продукции с организмом Пользователя,
+ подтверждает, что такой случай индивидуальной несовместимости не связан с
+ качеством продукции и соблюдения всех норм, хранения, транспортировки и
+ приготовления Заказа, а является индивидуальной особенностью организма
+ Пользователя о которой Исполнителю не могло быть известно, в связи с чем,
+ Исполнитель не несет ответственности при наступлении такого случая.
+
6. Сайты третьих лиц
- 6.1. Сервис может содержать ссылки или представлять доступ на другие сайты в сети Интернет (сайты третьих лиц) и размещенный на данных сайтах контент, являющийся результатом интеллектуальной деятельности третьих лиц и охраняемый в соответствии с законодательством Российской Федерации. Указанные сайты и размещенный на них контент не проверяются Компанией на соответствие требованиям законодательства Российской Федерации.
- 6.2. Компания не несет ответственность за любую информацию или контент, размещенные на сайтах третьих лиц, к которым Пользователь получает доступ посредством сервиса, включая, в том числе, любые мнения или утверждения, выраженные на сайтах третьих лиц.
- 6.3. Пользователь подтверждает, что с момента перехода Пользователя по ссылке, содержащейся на сервисе на сайт третьего лица, взаимоотношения Компании и Пользователя прекращаются, настоящее Соглашение в дальнейшем не распространяется на Пользователя, и Компания не несет ответственность за использование Пользователем контента, правомерность такого использования и качество контента, размещенного на сайтах третьих лиц.
- 7. Конфиденциальность и обработка персональных данных Пользователя.
- 7.1. Персональные данные Пользователя обрабатываются в соответствии Федеральным законом от 27.07.2006 г. № 152-ФЗ «О персональных данных» и Политикой хранения и обработки персональных данных.
- 7.2. При регистрации/оформлении заказа Пользователь предоставляет следующие данные: имя, адрес электронной почты, номер контактного телефона, адрес доставки заказа.
- 7.3. В целях исполнения настоящего соглашения Компания развивает, оптимизирует и внедряет новый функционал сервиса (включая продукты информационного, рекламного, развлекательного и иного характера), в т.ч. с участием аффилированных лиц и/или партнеров.
- Для обеспечения реализации указанных целей, а также в целях информирования Пользователей о своих услугах, продвижения товаров и услуг, проведения электронных и sms опросов, Получения Пользователем персонализированной (таргетированной) рекламы, контроля маркетинговых акций, клиентской поддержки, организации доставки товара Пользователям, проведения розыгрышей призов среди Пользователей, контроля удовлетворенности Пользователя и качества услуг, проверки, исследования и анализа таких данных Пользователь при регистрации/оформлении заказа соглашается и поручает Компании осуществлять с соблюдением применимого законодательства обработку данных, в т.ч. результатов автоматизированной обработки таких данных в виде целочисленных и/или текстовых значений и идентификаторов, их передачу аффилированным лицам и/или Исполнителям/Доставщикам во исполнение такого поручения на обработку, а также осуществлять сбор (получение) данных Пользователя и иных связанных с Пользователем данных от аффилированных лиц и/или Исполнителей.
- 7.4. Под данными, связанными с Пользователем, понимается информация о технических средствах (устройствах) и способах технологического взаимодействия с сервисом и/или сервисами аффилированных лиц и/или партнеров (в т. ч. IP-адрес хоста, вид операционной системы, тип браузера, географическое положение, данные о провайдере и иное), об активности Пользователя, а также иные данные, получаемые указанными способами.
- 7.5. Под обработкой данных понимается любое действие (операция) или совокупность действий (операций), совершаемых с использованием средств автоматизации или без использования таких средств с персональными данными Пользователя, включая сбор, запись, систематизацию, накопление, хранение, уточнение (обновление, изменение), сопоставление, извлечение, использование, передача аффилированным лицам Компании и/или Исполнителя в целях и на условиях настоящего Соглашения, обезличивание, блокирование, удаление, уничтожение.
- 7.6. Компания имеет право отправлять Пользователю от своего имени самостоятельно или с привлечением технических партнеров информационные, в том числе сервисные и рекламные сообщения, на электронную почту Пользователя, мобильный телефон (смс, телефонные звонки) или через используемые им сервисы Компании (социальные сети, мессенджеры и иные). Пользователь вправе отказаться от получения рекламной и другой информации без объяснения причин отказа. Сервисные сообщения, информирующие Пользователя о заказе и этапах его обработки, отправляются автоматически и не могут быть отклонены Пользователем.
- 7.7. Компания вправе использовать технологию «cookies». «Cookies» не содержат конфиденциальную информацию, и Компания вправе передавать информацию о «cookies» Исполнителям, агентам и третьим лицам, имеющим заключенные с Компанией договоры, для исполнения обязательств перед Пользователем и для целей статистики и оптимизации рекламных сообщений.
- 7.8. Компания получает информацию об ip-адресе посетителя Сайта https://goodfood.acceleratorpracticum.ru/ . Данная информация не используется для установления личности посетителя.
- 7.9. Компания не несет ответственности за сведения, предоставленные Пользователем на Сайте в общедоступной форме.
- 7.10. Компания вправе осуществлять записи телефонных разговоров с Пользователем. При этом Компания обязуется предотвращать попытки несанкционированного доступа к информации, полученной в ходе телефонных переговоров, и/или передачу ее третьим лицам, не имеющим непосредственного отношения к исполнению заказов в соответствие с п. 4 ст. 16 Федерального закона «Об информации, информационных технологиях и защите информации».
- 8. Регистрация на Сервисе, пароль и безопасность
- 8.1. Для получения права использования Пользователем сервиса Пользователю рекомендуется осуществить регистрацию учетной записи Пользователя на сервисе.
- 8.2. Регистрация Пользователя осуществляется следующим образом:
- а) ввести в форму сервиса абонентский номер телефона в федеральном формате (89ХХХХХХХХХ), указанный Пользователем при регистрации абонентский номер телефона будет использоваться в качестве имени Пользователя (логин) при использовании сервиса;
- б) ввести пароль, который придет на указанный номер мобильного телефона в виде sms-сообщения. В последующем пароль может быть изменен Пользователем в личном кабинете своего профиля;
- в) принять лицензионное соглашение – в случае работы с Приложением;
- г) принять настоящее соглашение-оферту, согласившись с её условиями;
- д) при желании дать свое согласие на получение информации (рекламы) о проводимых акциях Компании в соответствии с условиями настоящего соглашения.
- 8.3. Совершая действия по регистрации учетной записи Пользователя в Сервисе, Пользователь принимает условия настоящего Соглашения, в полном объеме и без каких-либо изъятий.
- 8.4. Регистрация Пользователя позволяет избежать несанкционированных действий третьих лиц от имени Пользователя и открывает последнему доступ к дополнительным сервисам. Передача Пользователем логина и пароля третьим лицам не допускается.
- 8.5. Заказ Товара осуществляется Пользователем как через сервис, так и по телефону.
- 9. Оформление и сроки выполнения заказа
- 9.1. Заказ Пользователя может быть оформлен по телефону и/или посредством заполнения электронной формы Заказа на Сервисе.
- 9.1.1. При оформлении Заказа Пользователь сам подтверждает, что ознакомлен с условиями настоящего соглашения и обязуется предоставить всю информацию, необходимую для надлежащего оформления и исполнения Заказа.
- 9.1.2. При оформлении Заказа через Сервис Пользователь заполняет электронную форму Заказа и отправляет сформированный Заказ путем подтверждения Заказа в электронной форме.
- 9.1.3. Для приема в обработку Заказа, который был оформлен Пользователем через Сервис Компании необходимо подтверждение Компании посредством телефонного звонка на контактный номер Пользователя в том, что данный Заказ получен, принят и передан в обработку Исполнителю. Заказ считается принятым в обработку, начиная с момента его подтверждения.
- 9.1.4. Если Заказ, который был оформлен Пользователем через сервис Компании, не был подтвержден со стороны Компании Пользователю, то Пользователь должен самостоятельно убедиться по телефону 8(812)383-0-383 в том, что его Заказ был получен, принят и передан в обработку Исполнителю.
- 9.2. Пользователь может заказать только те Товары, которые есть в наличии у Исполнителя в момент оформления Заказа.
- 9.2.1. Если у Исполнителя отсутствует необходимое количество или ассортимент заказанного Пользователем Товара, Компания информирует об этом Пользователя по телефону в течение 30 минут после получения Заказа от Пользователя. Пользователь вправе согласиться принять Товар в ином количестве или ассортименте, либо аннулировать свой Заказ. В случае неполучения ответа Пользователя Заказ Пользователя аннулируется в полном объеме.
- 9.3. Пользователь не имеет право изменить состав Заказа.
- 9.4. В случае возникновения у Пользователя дополнительных вопросов, касающихся характеристик Товара, перед оформлением Заказа, Пользователь должен обратиться к Компании по телефону 8(812)383-0-383 для получения необходимой информации.
- 9.5. Исполнитель получает информацию о Заказе Пользователя в течение 5 минут с момента приема Заказа Компанией. Исполнитель приступает к выполнению Заказа в порядке очередности всех Заказов, находящихся у него на исполнении.
- 9.6. Компания при оформлении Заказа от Пользователя информирует последнего о планируемом времени доставки Заказа по адресу Пользователя. Если при оформлении Заказа Пользователь не аннулировал Заказ и данный Заказ был оформлен Компанией, соответственно Пользователь согласен с обозначенным ему временем доставки Заказа и Заказ будет передан Исполнителю для выполнения.
+
+ 6.1. Сервис может содержать ссылки или представлять доступ на другие сайты в
+ сети Интернет (сайты третьих лиц) и размещенный на данных сайтах контент,
+ являющийся результатом интеллектуальной деятельности третьих лиц и охраняемый
+ в соответствии с законодательством Российской Федерации. Указанные сайты и
+ размещенный на них контент не проверяются Компанией на соответствие
+ требованиям законодательства Российской Федерации.
+
+
+ 6.2. Компания не несет ответственность за любую информацию или контент,
+ размещенные на сайтах третьих лиц, к которым Пользователь получает доступ
+ посредством сервиса, включая, в том числе, любые мнения или утверждения,
+ выраженные на сайтах третьих лиц.
+
+
+ 6.3. Пользователь подтверждает, что с момента перехода Пользователя по ссылке,
+ содержащейся на сервисе на сайт третьего лица, взаимоотношения Компании и
+ Пользователя прекращаются, настоящее Соглашение в дальнейшем не
+ распространяется на Пользователя, и Компания не несет ответственность за
+ использование Пользователем контента, правомерность такого использования и
+ качество контента, размещенного на сайтах третьих лиц.
+
+
+ 7. Конфиденциальность и обработка персональных данных Пользователя.
+
+
+ 7.1. Персональные данные Пользователя обрабатываются в соответствии
+ Федеральным законом от 27.07.2006 г. № 152-ФЗ «О персональных данных» и
+ Политикой хранения и обработки персональных данных.
+
+
+ 7.2. При регистрации/оформлении заказа Пользователь предоставляет следующие
+ данные: имя, адрес электронной почты, номер контактного телефона, адрес
+ доставки заказа.
+
+
+ 7.3. В целях исполнения настоящего соглашения Компания развивает, оптимизирует
+ и внедряет новый функционал сервиса (включая продукты информационного,
+ рекламного, развлекательного и иного характера), в т.ч. с участием
+ аффилированных лиц и/или партнеров.
+
+
+ Для обеспечения реализации указанных целей, а также в целях информирования
+ Пользователей о своих услугах, продвижения товаров и услуг, проведения
+ электронных и sms опросов, Получения Пользователем персонализированной
+ (таргетированной) рекламы, контроля маркетинговых акций, клиентской поддержки,
+ организации доставки товара Пользователям, проведения розыгрышей призов среди
+ Пользователей, контроля удовлетворенности Пользователя и качества услуг,
+ проверки, исследования и анализа таких данных Пользователь при
+ регистрации/оформлении заказа соглашается и поручает Компании осуществлять с
+ соблюдением применимого законодательства обработку данных, в т.ч. результатов
+ автоматизированной обработки таких данных в виде целочисленных и/или текстовых
+ значений и идентификаторов, их передачу аффилированным лицам и/или
+ Исполнителям/Доставщикам во исполнение такого поручения на обработку, а также
+ осуществлять сбор (получение) данных Пользователя и иных связанных с
+ Пользователем данных от аффилированных лиц и/или Исполнителей.
+
+
+ 7.4. Под данными, связанными с Пользователем, понимается информация о
+ технических средствах (устройствах) и способах технологического взаимодействия
+ с сервисом и/или сервисами аффилированных лиц и/или партнеров (в т. ч.
+ IP-адрес хоста, вид операционной системы, тип браузера, географическое
+ положение, данные о провайдере и иное), об активности Пользователя, а также
+ иные данные, получаемые указанными способами.
+
+
+ 7.5. Под обработкой данных понимается любое действие (операция) или
+ совокупность действий (операций), совершаемых с использованием средств
+ автоматизации или без использования таких средств с персональными данными
+ Пользователя, включая сбор, запись, систематизацию, накопление, хранение,
+ уточнение (обновление, изменение), сопоставление, извлечение, использование,
+ передача аффилированным лицам Компании и/или Исполнителя в целях и на условиях
+ настоящего Соглашения, обезличивание, блокирование, удаление, уничтожение.
+
+
+ 7.6. Компания имеет право отправлять Пользователю от своего имени
+ самостоятельно или с привлечением технических партнеров информационные, в том
+ числе сервисные и рекламные сообщения, на электронную почту Пользователя,
+ мобильный телефон (смс, телефонные звонки) или через используемые им сервисы
+ Компании (социальные сети, мессенджеры и иные). Пользователь вправе отказаться
+ от получения рекламной и другой информации без объяснения причин отказа.
+ Сервисные сообщения, информирующие Пользователя о заказе и этапах его
+ обработки, отправляются автоматически и не могут быть отклонены Пользователем.
+
+
+ 7.7. Компания вправе использовать технологию «cookies». «Cookies» не содержат
+ конфиденциальную информацию, и Компания вправе передавать информацию о
+ «cookies» Исполнителям, агентам и третьим лицам, имеющим заключенные с
+ Компанией договоры, для исполнения обязательств перед Пользователем и для
+ целей статистики и оптимизации рекламных сообщений.
+
+
+ 7.8. Компания получает информацию об ip-адресе посетителя Сайта{' '}
+
+ https://goodfood.acceleratorpracticum.ru/
+
+ . Данная информация не используется для установления личности посетителя.
+
+
+ 7.9. Компания не несет ответственности за сведения, предоставленные
+ Пользователем на Сайте в общедоступной форме.
+
+
+ 7.10. Компания вправе осуществлять записи телефонных разговоров с
+ Пользователем. При этом Компания обязуется предотвращать попытки
+ несанкционированного доступа к информации, полученной в ходе телефонных
+ переговоров, и/или передачу ее третьим лицам, не имеющим непосредственного
+ отношения к исполнению заказов в соответствие с п. 4 ст. 16 Федерального
+ закона «Об информации, информационных технологиях и защите информации».
+
+
+ 8. Регистрация на Сервисе, пароль и безопасность
+
+
+ 8.1. Для получения права использования Пользователем сервиса Пользователю
+ рекомендуется осуществить регистрацию учетной записи Пользователя на сервисе.
+
+
+ 8.2. Регистрация Пользователя осуществляется следующим образом:
+
+
+ а) ввести в форму сервиса абонентский номер телефона в федеральном формате
+ (89ХХХХХХХХХ), указанный Пользователем при регистрации абонентский номер
+ телефона будет использоваться в качестве имени Пользователя (логин) при
+ использовании сервиса;
+
+
+ б) ввести пароль, который придет на указанный номер мобильного телефона в виде
+ sms-сообщения. В последующем пароль может быть изменен Пользователем в личном
+ кабинете своего профиля;
+
+
+ в) принять лицензионное соглашение – в случае работы с Приложением;
+
+
+ г) принять настоящее соглашение-оферту, согласившись с её условиями;
+
+
+ д) при желании дать свое согласие на получение информации (рекламы) о
+ проводимых акциях Компании в соответствии с условиями настоящего соглашения.
+
+
+ 8.3. Совершая действия по регистрации учетной записи Пользователя в Сервисе,
+ Пользователь принимает условия настоящего Соглашения, в полном объеме и без
+ каких-либо изъятий.
+
+
+ 8.4. Регистрация Пользователя позволяет избежать несанкционированных действий
+ третьих лиц от имени Пользователя и открывает последнему доступ к
+ дополнительным сервисам. Передача Пользователем логина и пароля третьим лицам
+ не допускается.
+
+
+ 8.5. Заказ Товара осуществляется Пользователем как через сервис, так и по
+ телефону.
+
+
+ 9. Оформление и сроки выполнения заказа
+
+
+ 9.1. Заказ Пользователя может быть оформлен по телефону и/или посредством
+ заполнения электронной формы Заказа на Сервисе.
+
+
+ 9.1.1. При оформлении Заказа Пользователь сам подтверждает, что ознакомлен с
+ условиями настоящего соглашения и обязуется предоставить всю информацию,
+ необходимую для надлежащего оформления и исполнения Заказа.
+
+
+ 9.1.2. При оформлении Заказа через Сервис Пользователь заполняет электронную
+ форму Заказа и отправляет сформированный Заказ путем подтверждения Заказа в
+ электронной форме.
+
+
+ 9.1.3. Для приема в обработку Заказа, который был оформлен Пользователем через
+ Сервис Компании необходимо подтверждение Компании посредством телефонного
+ звонка на контактный номер Пользователя в том, что данный Заказ получен,
+ принят и передан в обработку Исполнителю. Заказ считается принятым в
+ обработку, начиная с момента его подтверждения.
+
+
+ 9.1.4. Если Заказ, который был оформлен Пользователем через сервис Компании,
+ не был подтвержден со стороны Компании Пользователю, то Пользователь должен
+ самостоятельно убедиться по телефону 8(812)383-0-383 в том, что его Заказ был
+ получен, принят и передан в обработку Исполнителю.
+
+
+ 9.2. Пользователь может заказать только те Товары, которые есть в наличии у
+ Исполнителя в момент оформления Заказа.
+
+
+ 9.2.1. Если у Исполнителя отсутствует необходимое количество или ассортимент
+ заказанного Пользователем Товара, Компания информирует об этом Пользователя по
+ телефону в течение 30 минут после получения Заказа от Пользователя.
+ Пользователь вправе согласиться принять Товар в ином количестве или
+ ассортименте, либо аннулировать свой Заказ. В случае неполучения ответа
+ Пользователя Заказ Пользователя аннулируется в полном объеме.
+
+
+ 9.3. Пользователь не имеет право изменить состав Заказа.
+
+
+ 9.4. В случае возникновения у Пользователя дополнительных вопросов, касающихся
+ характеристик Товара, перед оформлением Заказа, Пользователь должен обратиться
+ к Компании по телефону 8(812)383-0-383 для получения необходимой информации.
+
+
+ 9.5. Исполнитель получает информацию о Заказе Пользователя в течение 5 минут с
+ момента приема Заказа Компанией. Исполнитель приступает к выполнению Заказа в
+ порядке очередности всех Заказов, находящихся у него на исполнении.
+
+
+ 9.6. Компания при оформлении Заказа от Пользователя информирует последнего о
+ планируемом времени доставки Заказа по адресу Пользователя. Если при
+ оформлении Заказа Пользователь не аннулировал Заказ и данный Заказ был
+ оформлен Компанией, соответственно Пользователь согласен с обозначенным ему
+ временем доставки Заказа и Заказ будет передан Исполнителю для выполнения.
+
10. Доставка Товара
- 10.1. В случае если Товар не был передан Пользователю по вине последнего, отказа Пользователя от приемки и/или оплаты им Товара, ложного вызова, логин Пользователя (абонентский номер телефона) подлежит блокированию и в дальнейшем Заказы от данного Пользователя по телефону и/или через сервис не принимаются.
- 10.2. Доставка Товара осуществляется по фактическому адресу, указанному Пользователем при оформлении Заказа через сервис и/или по телефону Компании.
- 10.2.1. Средний срок доставки оформленного Заказа составляет 45-60 минут. Данное время может быть увеличено в виду погодных условий, ситуации на дороге, загруженностью на кухне Исполнителя.
- 10.2.2. Возможность доставки Товара за пределы зоны доставки Пользователь обязан предварительно согласовать с Исполнителем. Исполнитель вправе отказать в доставке Заказа, если он не входит в пределы зоны доставки.
- 10.2.3. Компания не несет ответственности за соблюдение/несоблюдение Исполнителем и/или Доставщиком своих обязательств перед Пользователями, а также за достоверность информации, предоставленной такими службами. Компания со своей стороны способствует урегулированию различных ситуаций, возникающих между Пользователем и Исполнителем и/или Доставкой, но не гарантирует положительное и окончательное их решение для той, или иной Стороны.
- 10.3. Доставка осуществляется при условии, что Пользователь сделает Заказ на сумму минимального заказа. Сумма минимального заказа определяется Исполнителем и в одностороннем порядке и указывается на Сервисе Компанией.
+
+ 10.1. В случае если Товар не был передан Пользователю по вине последнего,
+ отказа Пользователя от приемки и/или оплаты им Товара, ложного вызова, логин
+ Пользователя (абонентский номер телефона) подлежит блокированию и в дальнейшем
+ Заказы от данного Пользователя по телефону и/или через сервис не принимаются.
+
+
+ 10.2. Доставка Товара осуществляется по фактическому адресу, указанному
+ Пользователем при оформлении Заказа через сервис и/или по телефону Компании.
+
+
+ 10.2.1. Средний срок доставки оформленного Заказа составляет 45-60 минут.
+ Данное время может быть увеличено в виду погодных условий, ситуации на дороге,
+ загруженностью на кухне Исполнителя.
+
+
+ 10.2.2. Возможность доставки Товара за пределы зоны доставки Пользователь
+ обязан предварительно согласовать с Исполнителем. Исполнитель вправе отказать
+ в доставке Заказа, если он не входит в пределы зоны доставки.
+
+
+ 10.2.3. Компания не несет ответственности за соблюдение/несоблюдение
+ Исполнителем и/или Доставщиком своих обязательств перед Пользователями, а
+ также за достоверность информации, предоставленной такими службами. Компания
+ со своей стороны способствует урегулированию различных ситуаций, возникающих
+ между Пользователем и Исполнителем и/или Доставкой, но не гарантирует
+ положительное и окончательное их решение для той, или иной Стороны.
+
+
+ 10.3. Доставка осуществляется при условии, что Пользователь сделает Заказ на
+ сумму минимального заказа. Сумма минимального заказа определяется Исполнителем
+ и в одностороннем порядке и указывается на Сервисе Компанией.
+
11. Форс-мажор
- 11. Любая из Сторон в соответствии с настоящим соглашением, освобождается от ответственности за полное или частичное неисполнение своих обязательств по настоящему соглашению, если это неисполнение было вызвано обстоятельствами непреодолимой силы. Обстоятельства непреодолимой силы означают чрезвычайные события и обстоятельства, которые Стороны не могли ни предвидеть, ни предотвратить разумными средствами. Такие чрезвычайные события или обстоятельства включают в себя, в частности: забастовки, наводнения, пожары, землетрясения и иные стихийные бедствия, войны, военные действия и т.д.
- 12. Допущения при производстве продукции, согласно технологическим картам.
- 12.1. Запеченные роллы: запекание – это процесс приготовления роллов и суши, и не является его температурной характеристикой;
- 12.2. Роллы с крабовым мясом: допускается попадание кусочков хитина/осколков панциря не более 5 мм;
- 12.3. Лосось: допускаются разные оттенки лосося с широкими прожилками при смене поставщиков на производстве;
- 12.4. Темный цвет у авокадо: цвет у авокадо зависит от окисляемости продукта, по стандартам изготовления цвет не влияет на вкусовые ощущения;
- 12.5. Мидии: допускается попадание мяса краба в блюда с мидиями и кусочков жемчуга;
- 12.6. Неочищенный картофель: при приготовлении некоторых блюд, в том числе супов, используется молодой картофель в мундире;
- 12.7. Мидии в сливочном соусе: допускается попадание закрытых мидий не более 10% от общего количества;
- 12.8. Французский томатный суп с морепродуктами: допускается попадание закрытых мидий;
- 12.9. Допускается попадание кожуры от семечек в блюдах с ростками подсолнуха;
+
+ 11. Любая из Сторон в соответствии с настоящим соглашением, освобождается от
+ ответственности за полное или частичное неисполнение своих обязательств по
+ настоящему соглашению, если это неисполнение было вызвано обстоятельствами
+ непреодолимой силы. Обстоятельства непреодолимой силы означают чрезвычайные
+ события и обстоятельства, которые Стороны не могли ни предвидеть, ни
+ предотвратить разумными средствами. Такие чрезвычайные события или
+ обстоятельства включают в себя, в частности: забастовки, наводнения, пожары,
+ землетрясения и иные стихийные бедствия, войны, военные действия и т.д.
+
+
+ 12. Допущения при производстве продукции, согласно технологическим картам.
+
+
+ 12.1. Запеченные роллы: запекание – это процесс приготовления роллов и суши, и
+ не является его температурной характеристикой;
+
+
+ 12.2. Роллы с крабовым мясом: допускается попадание кусочков хитина/осколков
+ панциря не более 5 мм;
+
+
+ 12.3. Лосось: допускаются разные оттенки лосося с широкими прожилками при
+ смене поставщиков на производстве;
+
+
+ 12.4. Темный цвет у авокадо: цвет у авокадо зависит от окисляемости продукта,
+ по стандартам изготовления цвет не влияет на вкусовые ощущения;
+
+
+ 12.5. Мидии: допускается попадание мяса краба в блюда с мидиями и кусочков
+ жемчуга;
+
+
+ 12.6. Неочищенный картофель: при приготовлении некоторых блюд, в том числе
+ супов, используется молодой картофель в мундире;
+
+
+ 12.7. Мидии в сливочном соусе: допускается попадание закрытых мидий не более
+ 10% от общего количества;
+
+
+ 12.8. Французский томатный суп с морепродуктами: допускается попадание
+ закрытых мидий;
+
+
+ 12.9. Допускается попадание кожуры от семечек в блюдах с ростками подсолнуха;
+
13. Заключительные положения
- 13. Настоящее Соглашение вступает в силу для Пользователя с момента регистрации на Сервисе и/или оформления Заказа и действует до тех пор, пока не будет изменено или расторгнуто по инициативе Пользователя или Компании.
- 13.1. Настоящее Соглашение составлено на русском языке.
- 13.2. Если какое-либо из положений настоящего Соглашения будет признано недействительным, это не оказывает влияния на действительность или применимость остальных положений настоящего Соглашения.
- 13.3. Дальнейшее использование сервиса означает, что Пользователь принял на себя ответственность за безусловное соблюдение настоящего Соглашения.
+
+ 13. Настоящее Соглашение вступает в силу для Пользователя с момента
+ регистрации на Сервисе и/или оформления Заказа и действует до тех пор, пока не
+ будет изменено или расторгнуто по инициативе Пользователя или Компании.
+
+
+ 13.1. Настоящее Соглашение составлено на русском языке.
+
+
+ 13.2. Если какое-либо из положений настоящего Соглашения будет признано
+ недействительным, это не оказывает влияния на действительность или
+ применимость остальных положений настоящего Соглашения.
+
+
+ 13.3. Дальнейшее использование сервиса означает, что Пользователь принял на
+ себя ответственность за безусловное соблюдение настоящего Соглашения.
+
14. Информация о Компании
ООО «ГудФуд»
ИНН/КПП:
ОГРН:
- Телефон: 8-800-000-444-333
- Адрес для переписки:
- Дата публикации последней редакции Соглашения об условиях доставки 20.11.2023 г.
- Дата вступления в силу последней редакции Соглашения об условиях доставки 20.11.2023 г.
-
+
+ Телефон: 8-800-000-444-333
+
+
+ Адрес для переписки:
+
+
+ Дата публикации последней редакции Соглашения об условиях доставки 20.11.2023
+ г.
+
+
+ Дата вступления в силу последней редакции Соглашения об условиях доставки
+ 20.11.2023 г.
+
+
>
);
From 3c50c62dcd5e2277ba2bbcbe41f0c39d586d2e0e Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 10:48:04 +0300
Subject: [PATCH 078/141] feat: (rebase) add data to state object in navigate()
---
src/pages/checkout/index.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index 0f65ee88..b5f38c9e 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -122,7 +122,9 @@ const Checkout: React.FC = () => {
api
.usersOrderCreate(formData)
.then((res) => {
- navigate(URLS.CART_SUCCESS, { state: { order: res.order_number } });
+ navigate(URLS.CART_SUCCESS, {
+ state: { orderNumber: res.order_number, orderId: res.id },
+ });
loadCartData();
})
.catch((error) => {
From 91747bd930bfe859ab341c96445eece192b49faa Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 10:53:21 +0300
Subject: [PATCH 079/141] feat: add styles to position button
---
.../checkout/checkout-success/checkout-success.module.scss | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/pages/checkout/checkout-success/checkout-success.module.scss b/src/pages/checkout/checkout-success/checkout-success.module.scss
index 08bf80da..e1552023 100644
--- a/src/pages/checkout/checkout-success/checkout-success.module.scss
+++ b/src/pages/checkout/checkout-success/checkout-success.module.scss
@@ -24,6 +24,7 @@
.checkoutSuccess__title {
padding: 0 0 38px;
+ text-align: center;
@media screen and (width <= 768px) {
text-align: left;
@@ -33,7 +34,7 @@
}
.checkoutSuccess__paragraph {
- padding: 0 0 74px;
+ padding: 40px 0 74px;
margin: 0;
text-align: center;
font-size: 22px;
@@ -41,7 +42,7 @@
line-height: 140%;
@media screen and (width <= 768px) {
- padding: 0 0 32px;
+ padding: 20px 0 32px;
text-align: left;
font-size: 14px;
}
From fb23ff37ff179c237d0c84bca78dc724fdedd72c Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 11:01:15 +0300
Subject: [PATCH 080/141] feat: add api for online payment
---
src/services/api.ts | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/services/api.ts b/src/services/api.ts
index c30a5a69..ebeb15e6 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -234,6 +234,7 @@ class Api {
});
}
+ /* ------------------------------- Order ------------------------------- */
usersOrderList() {
return this._request(`order/`, {
method: 'GET',
@@ -260,6 +261,21 @@ class Api {
});
}
+ usersOrderPay(id: number) {
+ const headers: HeadersInit = {
+ 'Content-Type': 'application/json',
+ };
+ const token = Cookies.get('token');
+ if (token) {
+ headers.Authorization = `Token ${token}`;
+ }
+ return this._request(`order/${id}/pay/`, {
+ method: 'POST',
+ headers,
+ // credentials: 'include',
+ });
+ }
+
usersOrderRead(userId: string, id: number) {
return this._request(`users/${userId}/order/${id}/`, {
method: 'GET',
From e2989e0b298b464b241028e5bd6314884090cc35 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 11:02:48 +0300
Subject: [PATCH 081/141] feat: (rebase) add button for online payment
---
src/pages/checkout/checkout-success/index.tsx | 61 +++++++++++++++----
1 file changed, 48 insertions(+), 13 deletions(-)
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 6caa5d0e..05ca99b3 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -1,38 +1,73 @@
-import React from 'react';
+import React, { useState } from 'react';
import { useLocation, useNavigate } from 'react-router';
import { Link } from 'react-router-dom';
import OurBlock from '@components/our-block';
import OrderStatusTracker from '@components/order-status-tracker';
import { URLS } from '@data/constants';
+import Button from '@components/button';
+import api from '@services/api';
+import { useAuth } from '@hooks/use-auth';
+import { usePopup } from '@hooks/use-popup';
import styles from './checkout-success.module.scss';
+type Order = {
+ orderNumber: string;
+ orderId: number;
+};
+
const CheckoutSuccess: React.FC = () => {
- const [order, setOrder] = React.useState('');
+ const [order, setOrder] = React.useState
({ orderNumber: '', orderId: 0 });
+ const [paymentUrl, setPaymentUrl] = useState('');
const location = useLocation();
const navigate = useNavigate();
+ const { isLoggedIn } = useAuth();
+ const { handleOpenPopup } = usePopup();
- React.useEffect(
- () =>
- location.state?.order
- ? setOrder(location.state.order)
- : navigate('/', { replace: true }),
- [location, navigate]
- );
+ React.useEffect(() => {
+ location.state?.orderId ? setOrder(location.state) : navigate('/', { replace: true });
+ }, [location, navigate]);
+
+ React.useEffect(() => {
+ if (order.orderId !== 0) {
+ api
+ .usersOrderPay(order.orderId)
+ .then(({ checkout_session_url }) => setPaymentUrl(checkout_session_url));
+ }
+ }, [order.orderId]);
+
+ const handlePayment = () => {
+ if (paymentUrl) return window.location.assign(paymentUrl);
+ };
return (
- Заказ №{order} успешно оформлен!
+ Заказ №{order.orderNumber} успешно оформлен!
+
Мы уже приступили к его сборке.
За статусом заказа можно следить в{' '}
-
- личном кабинете
-
+ {isLoggedIn ? (
+
+ личном кабинете
+
+ ) : (
+ handleOpenPopup('openPopupLogin')}
+ className={styles.checkoutSuccess__link}
+ to="/profile"
+ >
+ личном кабинете
+
+ )}
.
From c9342e3cffa80cc90a09973bed313639238a46f5 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 12:05:05 +0300
Subject: [PATCH 082/141] feat: (rebase) add info for unauthorized user
---
.../checkout-success.module.scss | 6 +++-
src/pages/checkout/checkout-success/index.tsx | 31 +++++++++----------
2 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/src/pages/checkout/checkout-success/checkout-success.module.scss b/src/pages/checkout/checkout-success/checkout-success.module.scss
index e1552023..74a1f0c7 100644
--- a/src/pages/checkout/checkout-success/checkout-success.module.scss
+++ b/src/pages/checkout/checkout-success/checkout-success.module.scss
@@ -33,7 +33,7 @@
}
}
-.checkoutSuccess__paragraph {
+.checkoutSuccess__textContainer {
padding: 40px 0 74px;
margin: 0;
text-align: center;
@@ -48,6 +48,10 @@
}
}
+.checkoutSuccess__text {
+ margin: 0;
+}
+
.checkoutSuccess__advice {
padding: 0 0 44px;
margin: 0;
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 05ca99b3..19f92e27 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -8,7 +8,6 @@ import { URLS } from '@data/constants';
import Button from '@components/button';
import api from '@services/api';
import { useAuth } from '@hooks/use-auth';
-import { usePopup } from '@hooks/use-popup';
import styles from './checkout-success.module.scss';
@@ -23,7 +22,6 @@ const CheckoutSuccess: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const { isLoggedIn } = useAuth();
- const { handleOpenPopup } = usePopup();
React.useEffect(() => {
location.state?.orderId ? setOrder(location.state) : navigate('/', { replace: true });
@@ -52,24 +50,23 @@ const CheckoutSuccess: React.FC = () => {
buttonText="Оплатить онлайн"
buttonStyle="green-button"
>
-
- Мы уже приступили к его сборке.
- За статусом заказа можно следить в{' '}
+
+
Мы уже приступили к его сборке.
{isLoggedIn ? (
-
- личном кабинете
-
+ <>
+
+ За статусом заказа можно следить в
+
+
+ личном кабинете.
+
+ >
) : (
-
handleOpenPopup('openPopupLogin')}
- className={styles.checkoutSuccess__link}
- to="/profile"
- >
- личном кабинете
-
+
+ История заказов доступна для зарегистрированных пользователей.
+
)}
- .
-
+
From 611a054d0ce6a10dad2efa90a8443e474e9f579d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 13:17:34 +0300
Subject: [PATCH 083/141] feat: add error info for user
---
.../checkout-success.module.scss | 27 ++++++++++++++--
src/pages/checkout/checkout-success/index.tsx | 32 ++++++++++++-------
2 files changed, 45 insertions(+), 14 deletions(-)
diff --git a/src/pages/checkout/checkout-success/checkout-success.module.scss b/src/pages/checkout/checkout-success/checkout-success.module.scss
index 74a1f0c7..6e2c7444 100644
--- a/src/pages/checkout/checkout-success/checkout-success.module.scss
+++ b/src/pages/checkout/checkout-success/checkout-success.module.scss
@@ -34,7 +34,7 @@
}
.checkoutSuccess__textContainer {
- padding: 40px 0 74px;
+ padding: 5px 0 74px;
margin: 0;
text-align: center;
font-size: 22px;
@@ -42,7 +42,7 @@
line-height: 140%;
@media screen and (width <= 768px) {
- padding: 20px 0 32px;
+ padding: 0 0 32px;
text-align: left;
font-size: 14px;
}
@@ -82,3 +82,26 @@
padding-right: 0;
}
}
+
+.checkoutSuccess__buttonContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ width: max-content;
+}
+
+.checkoutSuccess__error {
+ padding-top: 10px;
+ min-height: 25px;
+ text-align: center;
+ color: $error-color;
+ font-size: 13px;
+ font-weight: 300;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 12px;
+ padding-top: 3px;
+ min-height: 17px;
+ }
+}
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 19f92e27..50e984e9 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -18,7 +18,8 @@ type Order = {
const CheckoutSuccess: React.FC = () => {
const [order, setOrder] = React.useState({ orderNumber: '', orderId: 0 });
- const [paymentUrl, setPaymentUrl] = useState('');
+ const [paymentError, setPaymentError] = useState('');
+ const [isDisabled, setIsDisabled] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const { isLoggedIn } = useAuth();
@@ -27,16 +28,19 @@ const CheckoutSuccess: React.FC = () => {
location.state?.orderId ? setOrder(location.state) : navigate('/', { replace: true });
}, [location, navigate]);
- React.useEffect(() => {
+ const handlePayment = () => {
+ setIsDisabled(true);
if (order.orderId !== 0) {
api
.usersOrderPay(order.orderId)
- .then(({ checkout_session_url }) => setPaymentUrl(checkout_session_url));
+ .then(({ checkout_session_url }) => {
+ window.location.assign(checkout_session_url);
+ })
+ .catch(({ errors }) => {
+ setPaymentError(errors[0].detail);
+ })
+ .finally(() => setIsDisabled(false));
}
- }, [order.orderId]);
-
- const handlePayment = () => {
- if (paymentUrl) return window.location.assign(paymentUrl);
};
return (
@@ -45,11 +49,15 @@ const CheckoutSuccess: React.FC = () => {
Заказ №{order.orderNumber} успешно оформлен!
-
+
+
+ {paymentError}
+
Мы уже приступили к его сборке.
{isLoggedIn ? (
From 16255c49215a080bce4f734cf6e9a571ec13d07b Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 12:16:30 +0300
Subject: [PATCH 084/141] refactor: decrease code nesting
---
src/pages/checkout/checkout-success/index.tsx | 23 ++++++++++---------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 50e984e9..7e762627 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -30,17 +30,18 @@ const CheckoutSuccess: React.FC = () => {
const handlePayment = () => {
setIsDisabled(true);
- if (order.orderId !== 0) {
- api
- .usersOrderPay(order.orderId)
- .then(({ checkout_session_url }) => {
- window.location.assign(checkout_session_url);
- })
- .catch(({ errors }) => {
- setPaymentError(errors[0].detail);
- })
- .finally(() => setIsDisabled(false));
- }
+
+ if (order.orderId === 0) return;
+
+ api
+ .usersOrderPay(order.orderId)
+ .then(({ checkout_session_url }) => {
+ window.location.assign(checkout_session_url);
+ })
+ .catch(({ errors }) => {
+ setPaymentError(errors[0].detail);
+ })
+ .finally(() => setIsDisabled(false));
};
return (
From c566fe58da34a3981ef7eedba2522e6c148090c8 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 13:17:55 +0300
Subject: [PATCH 085/141] refactor: change tags to self-closed ones
---
src/pages/checkout/checkout-success/index.tsx | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 7e762627..37592942 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -56,7 +56,7 @@ const CheckoutSuccess: React.FC = () => {
buttonText="Оплатить онлайн"
buttonStyle="green-button"
disabled={isDisabled}
- >
+ />
{paymentError}
@@ -66,9 +66,8 @@ const CheckoutSuccess: React.FC = () => {
За статусом заказа можно следить в
-
- личном кабинете.
-
+
+ личном кабинете.
>
) : (
From cdb853cc96d7278384a109e9f2756ef6dc472662 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 13:20:24 +0300
Subject: [PATCH 086/141] refactor: delete unused comments
---
src/services/api.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/services/api.ts b/src/services/api.ts
index ebeb15e6..96c6aeff 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -272,7 +272,6 @@ class Api {
return this._request(`order/${id}/pay/`, {
method: 'POST',
headers,
- // credentials: 'include',
});
}
From ac5908cfbd603b316f16d1411ecdea1809305b07 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 13:50:26 +0300
Subject: [PATCH 087/141] feat: add PaymentButton component
---
src/components/payment-button/index.tsx | 43 +++++++++++++++++++
.../payment-button/payment-button.module.scss | 17 ++++++++
2 files changed, 60 insertions(+)
create mode 100644 src/components/payment-button/index.tsx
create mode 100644 src/components/payment-button/payment-button.module.scss
diff --git a/src/components/payment-button/index.tsx b/src/components/payment-button/index.tsx
new file mode 100644
index 00000000..6a54f71e
--- /dev/null
+++ b/src/components/payment-button/index.tsx
@@ -0,0 +1,43 @@
+import { useState } from 'react';
+import api from '@services/api';
+import Button from '@components/button';
+import styles from './payment-button.module.scss';
+
+type PaymentButtonProps = {
+ orderId: number;
+};
+
+const PaymentButton: React.FC = ({ orderId }) => {
+ const [isDisabled, setIsDisabled] = useState(false);
+ const [paymentError, setPaymentError] = useState('');
+
+ const handlePayment = () => {
+ setIsDisabled(true);
+
+ if (orderId === 0) return;
+
+ api
+ .usersOrderPay(orderId)
+ .then(({ checkout_session_url }) => {
+ window.location.assign(checkout_session_url);
+ })
+ .catch(({ errors }) => {
+ console.log(errors);
+ setPaymentError(errors[0].detail);
+ })
+ .finally(() => setIsDisabled(false));
+ };
+ return (
+
+
+ {paymentError}
+
+ );
+};
+
+export default PaymentButton;
diff --git a/src/components/payment-button/payment-button.module.scss b/src/components/payment-button/payment-button.module.scss
new file mode 100644
index 00000000..bf376620
--- /dev/null
+++ b/src/components/payment-button/payment-button.module.scss
@@ -0,0 +1,17 @@
+@use '@scss/variables' as *;
+
+.errorText {
+ padding-top: 10px;
+ min-height: 25px;
+ text-align: center;
+ color: $error-color;
+ font-size: 13px;
+ font-weight: 300;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ font-size: 12px;
+ padding-top: 3px;
+ min-height: 17px;
+ }
+}
From 359d2390409df30fbd654b855f0ae3bd9c4114d5 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 13:53:14 +0300
Subject: [PATCH 088/141] feat: add PaymentButton to ProfileOrder
---
.../profile-components/profile-order/index.tsx | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/components/profile-components/profile-order/index.tsx b/src/components/profile-components/profile-order/index.tsx
index 4e264a90..4c1a70e0 100644
--- a/src/components/profile-components/profile-order/index.tsx
+++ b/src/components/profile-components/profile-order/index.tsx
@@ -1,6 +1,7 @@
-import styles from './profile-order.module.scss';
-import OrderStatus from '../order-status';
import clsx from 'clsx';
+import PaymentButton from '@components/payment-button';
+import OrderStatus from '../order-status';
+import styles from './profile-order.module.scss';
type OrderStatusType =
| 'Ordered'
@@ -27,6 +28,7 @@ type Product = {
type CommonOrder = {
id: number;
+ is_paid: boolean;
order_number?: string;
ordering_date?: string;
total_price?: string;
@@ -57,7 +59,6 @@ const ProfileOrder = ({
products,
} = order;
- console.log(status);
let payment_method_ru =
payment_method === 'Payment at the point of delivery'
? 'Банковской картой'
@@ -130,7 +131,11 @@ const ProfileOrder = ({
{`Способ получения: ${delivery_method_ru}`}
-
+ {order.is_paid ? (
+
+ ) : (
+
+ )}
From abce6b6b488b6b0912fce2e330c8a72ffa40a69f Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 14:20:08 +0300
Subject: [PATCH 089/141] feat: make PaymentButton narrower
---
src/components/payment-button/index.tsx | 1 +
src/components/payment-button/payment-button.module.scss | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/src/components/payment-button/index.tsx b/src/components/payment-button/index.tsx
index 6a54f71e..50416c3d 100644
--- a/src/components/payment-button/index.tsx
+++ b/src/components/payment-button/index.tsx
@@ -34,6 +34,7 @@ const PaymentButton: React.FC = ({ orderId }) => {
buttonText="Оплатить онлайн"
buttonStyle="green-button"
disabled={isDisabled}
+ classNames={styles['green-button__type_narrow']}
/>
{paymentError}
diff --git a/src/components/payment-button/payment-button.module.scss b/src/components/payment-button/payment-button.module.scss
index bf376620..8b097f32 100644
--- a/src/components/payment-button/payment-button.module.scss
+++ b/src/components/payment-button/payment-button.module.scss
@@ -15,3 +15,7 @@
min-height: 17px;
}
}
+
+.green-button__type_narrow {
+ max-width: 140px;
+}
From 6376f4f3e5882fa5ed35ef7d3380187bb4f12633 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 14:21:43 +0300
Subject: [PATCH 090/141] feat: add PaymentButton to ProfileOrderMobile
---
.../profile-order-mobile/index.tsx | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/components/profile-components/profile-order-mobile/index.tsx b/src/components/profile-components/profile-order-mobile/index.tsx
index ddf2a7a2..25d578dd 100644
--- a/src/components/profile-components/profile-order-mobile/index.tsx
+++ b/src/components/profile-components/profile-order-mobile/index.tsx
@@ -1,8 +1,8 @@
+import clsx from 'clsx';
import ProductCard from '@components/product-card';
-import styles from './profile-order-mobile.module.scss';
+import PaymentButton from '@components/payment-button';
import OrderStatus from '../order-status';
-import clsx from 'clsx';
-// import { OrderList } from '@services/generated-api/data-contracts';
+import styles from './profile-order-mobile.module.scss';
type OrderStatusType =
| 'Ordered'
@@ -29,6 +29,7 @@ type Product = {
type CommonOrder = {
id: number;
+ is_paid: boolean;
order_number?: string;
ordering_date?: string;
total_price?: string;
@@ -114,7 +115,11 @@ const ProfileOrderMobile = ({
{`${total_price} руб.`}
-
+ {order.is_paid ? (
+
+ ) : (
+
+ )}
);
From 236eca67d38cc081860540cbed15b886c48dab59 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 15:37:05 +0300
Subject: [PATCH 091/141] feat: use PaymentButton in CheckoutSuccess component
---
src/components/payment-button/index.tsx | 7 ++--
.../payment-button/payment-button.module.scss | 7 ++++
src/pages/checkout/checkout-success/index.tsx | 33 ++-----------------
3 files changed, 14 insertions(+), 33 deletions(-)
diff --git a/src/components/payment-button/index.tsx b/src/components/payment-button/index.tsx
index 50416c3d..ccd34753 100644
--- a/src/components/payment-button/index.tsx
+++ b/src/components/payment-button/index.tsx
@@ -5,9 +5,10 @@ import styles from './payment-button.module.scss';
type PaymentButtonProps = {
orderId: number;
+ isCheckoutPage?: boolean;
};
-const PaymentButton: React.FC
= ({ orderId }) => {
+const PaymentButton: React.FC = ({ orderId, isCheckoutPage }) => {
const [isDisabled, setIsDisabled] = useState(false);
const [paymentError, setPaymentError] = useState('');
@@ -22,13 +23,13 @@ const PaymentButton: React.FC = ({ orderId }) => {
window.location.assign(checkout_session_url);
})
.catch(({ errors }) => {
- console.log(errors);
setPaymentError(errors[0].detail);
})
.finally(() => setIsDisabled(false));
};
+
return (
-
+
{
const [order, setOrder] = React.useState({ orderNumber: '', orderId: 0 });
- const [paymentError, setPaymentError] = useState('');
- const [isDisabled, setIsDisabled] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const { isLoggedIn } = useAuth();
@@ -28,37 +25,13 @@ const CheckoutSuccess: React.FC = () => {
location.state?.orderId ? setOrder(location.state) : navigate('/', { replace: true });
}, [location, navigate]);
- const handlePayment = () => {
- setIsDisabled(true);
-
- if (order.orderId === 0) return;
-
- api
- .usersOrderPay(order.orderId)
- .then(({ checkout_session_url }) => {
- window.location.assign(checkout_session_url);
- })
- .catch(({ errors }) => {
- setPaymentError(errors[0].detail);
- })
- .finally(() => setIsDisabled(false));
- };
-
return (
Заказ №{order.orderNumber} успешно оформлен!
-
-
- {paymentError}
-
+
Мы уже приступили к его сборке.
{isLoggedIn ? (
From 1fa8612543d3ca3a7e57258feb246b1417d46193 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 30 Dec 2023 21:30:35 +0300
Subject: [PATCH 092/141] fix: change fields in type to optional
---
src/services/generated-api/data-contracts.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index d11acf2e..e1d99bf9 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -334,14 +334,14 @@ export interface Product {
discontinued?: boolean;
producer: ProducerLight;
/** Measure unit */
- measure_unit?: 'grams' | 'milliliters' | 'items';
+ measure_unit: 'grams' | 'milliliters' | 'items';
/**
* Amount
* Number of grams, milliliters or items
* @min 0
* @max 32767
*/
- amount?: number;
+ amount: number;
/**
* Price
* Price per one product unit
From 18eea7cfc769f0494eda0886f56705f18cc8d798 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 30 Dec 2023 21:31:47 +0300
Subject: [PATCH 093/141] feat: add utility function
---
src/utils/utils.ts | 46 +++++++++++++++++++++++++++-------------------
1 file changed, 27 insertions(+), 19 deletions(-)
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index d4fd39f6..a20cd2f6 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -18,24 +18,32 @@ export function declOfNum(n: number, titles: [string, string, string]) {
];
}
-export function toMeasureUnit(
- measureUnit: string | undefined | null,
- weight: number | null
-) {
- let newMeasureUnit = 'шт';
- let newWeight = weight;
+export const translateMeasureUnit = (measureUnit: string, amount: number) => {
+ const translatedMeasureObj = { measureUnit, amount };
- if (newWeight != null) {
- if (measureUnit === 'milliliters') {
- newMeasureUnit = 'мл';
- } else if (measureUnit === 'grams') {
- newMeasureUnit = 'гр';
- if (newWeight > 999) {
- newMeasureUnit = 'кг';
- newWeight = newWeight / 1000;
- }
- }
+ switch (true) {
+ case !measureUnit || !amount:
+ translatedMeasureObj.measureUnit = 'шт';
+ translatedMeasureObj.amount = 1;
+ return translatedMeasureObj;
+ case amount > 499 && measureUnit === 'grams':
+ translatedMeasureObj.measureUnit = 'кг';
+ translatedMeasureObj.amount = amount / 1000;
+ return translatedMeasureObj;
+ case amount > 499 && measureUnit === 'milliliters':
+ translatedMeasureObj.measureUnit = 'л';
+ translatedMeasureObj.amount = amount / 1000;
+ return translatedMeasureObj;
+ case measureUnit === 'grams':
+ translatedMeasureObj.measureUnit = 'гр';
+ return translatedMeasureObj;
+ case measureUnit === 'milliliters':
+ translatedMeasureObj.measureUnit = 'мл';
+ return translatedMeasureObj;
+ case measureUnit === 'items':
+ translatedMeasureObj.measureUnit = 'шт';
+ return translatedMeasureObj;
+ default:
+ return translatedMeasureObj;
}
-
- return { newMeasureUnit, newWeight };
-}
+};
From db7d6281f58e23b74621df9459337ece95822f68 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 30 Dec 2023 21:32:41 +0300
Subject: [PATCH 094/141] feat: use utility function in product card
---
src/components/product-card/index.tsx | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/src/components/product-card/index.tsx b/src/components/product-card/index.tsx
index 2511e490..3791b25f 100644
--- a/src/components/product-card/index.tsx
+++ b/src/components/product-card/index.tsx
@@ -10,14 +10,14 @@ import { usePopup } from '@hooks/use-popup';
import { useCart } from '@hooks/use-cart-context.ts';
import CheckIcon from '@images/check.svg?react';
import api from '@services/api';
-import { toMeasureUnit } from '@utils/utils';
+import { translateMeasureUnit } from '@utils/utils';
type ProductCardProps = {
cardName: string;
price: number;
final_price?: number;
weight: number;
- measureUnit?: string;
+ measureUnit: string;
cardImage: string;
category?: string;
idCard: number;
@@ -75,7 +75,10 @@ const ProductCard: React.FC
= ({
return updateCart([{ id: idCard, quantity: 1 }]);
};
- const { newMeasureUnit, newWeight } = toMeasureUnit(measureUnit, weight);
+ const { measureUnit: newMeasureUnit, amount } = translateMeasureUnit(
+ measureUnit,
+ weight
+ );
return (
@@ -101,7 +104,7 @@ const ProductCard: React.FC
= ({
{`${newWeight} ${newMeasureUnit}`}
+ >{`${amount + newMeasureUnit}`}
From cc20ab40aebdc553de0ee01be47dd83489b5a48d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 30 Dec 2023 21:34:02 +0300
Subject: [PATCH 095/141] feat: (rebase) use utility function in product page
---
src/pages/product/index.tsx | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx
index 2889058b..c9e7046a 100644
--- a/src/pages/product/index.tsx
+++ b/src/pages/product/index.tsx
@@ -13,6 +13,7 @@ import { usePopup } from '@hooks/use-popup';
import { useCart } from '@hooks/use-cart-context.ts';
import { useNavigate } from 'react-router-dom';
import plural from '@components/ratings-and-reviews-components/utils/pluralizer';
+import { translateMeasureUnit } from '@utils/utils';
const Product: React.FC = () => {
const { cartData, updateCart, deleteCart } = useCart();
@@ -21,20 +22,22 @@ const Product: React.FC = () => {
const [isLoaded, setIsLoaded] = React.useState
(false);
const [productItem, setProductItem] = React.useState(null);
const [reviewsAmount, setReviewsAmount] = React.useState(0);
+ const [measureObj, setMeasureObj] = React.useState({ amount: 0, measureUnit: '' });
const { isLoggedIn } = useAuth();
const { handleOpenPopup } = usePopup();
const navigate = useNavigate();
const { id } = useParams();
- //Нужно с бэка получать в будщем:
- let newMeasureUnit = 'шт';
- if (productItem != null) {
- if (productItem.measure_unit === 'milliliters') {
- newMeasureUnit = 'мл';
- } else if (productItem.measure_unit === 'grams') {
- newMeasureUnit = 'гр';
+
+ useEffect(() => {
+ if (productItem !== null) {
+ const translatedMeasureObj = translateMeasureUnit(
+ productItem.measure_unit,
+ productItem.amount
+ );
+ setMeasureObj(translatedMeasureObj);
}
- }
+ }, [productItem]);
useEffect(() => {
if (id !== undefined) {
@@ -143,7 +146,7 @@ const Product: React.FC = () => {
- {productItem.price} руб. / {newMeasureUnit}
+ {productItem.price} руб. / {measureObj.amount + measureObj.measureUnit}
Date: Sat, 30 Dec 2023 21:35:23 +0300
Subject: [PATCH 096/141] feat: use utility function in shopping cart page
---
src/components/shopping-item/index.tsx | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/components/shopping-item/index.tsx b/src/components/shopping-item/index.tsx
index f6bd2b66..d67b0654 100644
--- a/src/components/shopping-item/index.tsx
+++ b/src/components/shopping-item/index.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import styles from './shopping-item.module.scss';
import { Link } from 'react-router-dom';
import { useCart } from '@hooks/use-cart-context.ts';
+import { translateMeasureUnit } from '@utils/utils';
type ShoppingItemProps = {
product: {
@@ -12,12 +13,18 @@ type ShoppingItemProps = {
quantity: number;
final_price: number;
total_price: number;
+ amount: number;
+ measure_unit: string;
};
};
const ShoppingItem: React.FC = (props) => {
const { addItemToCart, removeItemFromCart, deleteCart } = useCart();
const { product } = props;
+ const { measureUnit, amount } = translateMeasureUnit(
+ product.measure_unit,
+ product.amount
+ );
const handleDeleteClick = () => {
deleteCart(product.id);
@@ -43,7 +50,9 @@ const ShoppingItem: React.FC = (props) => {
-
{product.name}
+
{`${product.name}, ${
+ amount + measureUnit
+ }`}
Date: Sun, 31 Dec 2023 10:03:31 +0300
Subject: [PATCH 097/141] feat: use utility function in recipe
---
.../ingredients-list-popup/index.tsx | 7 ++--
.../ingredients-list/index.tsx | 40 ++++++++-----------
src/pages/recipe/index.tsx | 26 +++++++++++-
3 files changed, 45 insertions(+), 28 deletions(-)
diff --git a/src/components/recipes-components/ingredients-list-popup/index.tsx b/src/components/recipes-components/ingredients-list-popup/index.tsx
index cbdbbfc6..46683f47 100644
--- a/src/components/recipes-components/ingredients-list-popup/index.tsx
+++ b/src/components/recipes-components/ingredients-list-popup/index.tsx
@@ -11,6 +11,7 @@ type RecipeIngredientsProps = {
name: string;
need_to_buy: number;
quantity_in_recipe: number;
+ quantity_in_recipe_measure?: string;
}[];
};
@@ -25,9 +26,9 @@ const IngredientsListPopup: React.FC = ({ ingredients })
{ingredient?.name}
- {`${ingredient?.quantity_in_recipe} ${ingredient?.measure_unit}`}
+
+ {ingredient.quantity_in_recipe_measure}
+
);
})}
diff --git a/src/components/recipes-components/ingredients-list/index.tsx b/src/components/recipes-components/ingredients-list/index.tsx
index ca8f416e..b9892dfa 100644
--- a/src/components/recipes-components/ingredients-list/index.tsx
+++ b/src/components/recipes-components/ingredients-list/index.tsx
@@ -13,6 +13,7 @@ type RecipeIngredientsProps = {
name: string;
need_to_buy: number;
quantity_in_recipe: number;
+ quantity_in_recipe_measure?: string;
}[];
};
@@ -30,31 +31,22 @@ const IngredientsList: React.FC = ({ ingredients }) => {
{`${ingredients.length} ${numeralizeWord}`}
- {ingredients.map((ingredient, index) => {
- if (ingredient.measure_unit === 'items') {
- ingredient.measure_unit = 'шт';
- } else if (ingredient.measure_unit === 'grams') {
- ingredient.measure_unit = 'гр';
- } else if (ingredient.measure_unit === 'milliliters') {
- ingredient.measure_unit = 'мл';
- }
- return (
-
-
-
-
-
{`${ingredient.name}, ${ingredient.amount}${ingredient.measure_unit}`}
-
{`${ingredient?.quantity_in_recipe} ${ingredient.measure_unit}`}
+ {ingredients.map((ingredient, index) => (
+
+
+
- );
- })}
+
{`${ingredient.name}, ${
+ ingredient.amount + ingredient.measure_unit
+ }`}
+
+ {ingredient.quantity_in_recipe_measure}
+
+
+ ))}
);
diff --git a/src/pages/recipe/index.tsx b/src/pages/recipe/index.tsx
index cf83ad34..e1911ec9 100644
--- a/src/pages/recipe/index.tsx
+++ b/src/pages/recipe/index.tsx
@@ -12,6 +12,7 @@ import { useCart } from '@hooks/use-cart-context';
import RecipeInfo from '@components/recipes-components/recipe-info';
import { usePopup } from '@hooks/use-popup';
import PopupRecipe from '@components/popups/popup-recipe';
+import { translateMeasureUnit } from '@utils/utils';
type ReceipeIngredientInfoProps = {
amount: number;
@@ -22,6 +23,7 @@ type ReceipeIngredientInfoProps = {
name: string;
need_to_buy: number;
quantity_in_recipe: number;
+ quantity_in_recipe_measure?: string;
};
type ReceipeInfoProps = {
@@ -62,7 +64,29 @@ const Recipe: React.FC = () => {
const recipeId: number = parseInt(id, 10);
const fetchReceiptAndProducts = async () => {
- const recipe = await api.getRecipeById(recipeId);
+ const recipe: ReceipeInfoProps = await api.getRecipeById(recipeId);
+
+ recipe.ingredients.map((ingredient, id) => {
+ const ingredientMeasureUnit = ingredient.measure_unit;
+
+ const { measureUnit, amount } = translateMeasureUnit(
+ ingredientMeasureUnit,
+ ingredient.amount
+ );
+
+ const { measureUnit: newMeasureUnit, amount: newAmount } = translateMeasureUnit(
+ ingredientMeasureUnit,
+ ingredient.quantity_in_recipe
+ );
+
+ recipe.ingredients[id].amount = amount;
+ recipe.ingredients[id].measure_unit = measureUnit;
+ recipe.ingredients[id].quantity_in_recipe = newAmount;
+ recipe.ingredients[id].quantity_in_recipe_measure = `${
+ newAmount + newMeasureUnit
+ }`;
+ });
+
setRecipeByLines(recipe.text.split('\n'));
setNumeralizeWord(declOfNum(recipe.cooking_time, ['минута', 'минуты', 'минут']));
setRecipeInfo(recipe);
From 1deddebc89538fd64e976686a4f67cc5677d1656 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Fri, 5 Jan 2024 20:51:05 +0300
Subject: [PATCH 098/141] feat: add types and object for measure units
---
src/utils/utils.ts | 33 +++++++++++++++++++++++++++------
1 file changed, 27 insertions(+), 6 deletions(-)
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index a20cd2f6..f6830849 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -18,30 +18,51 @@ export function declOfNum(n: number, titles: [string, string, string]) {
];
}
+type translationsType = {
+ grams: {
+ singular: 'гр';
+ plural: 'кг';
+ };
+ milliliters: {
+ singular: 'мл';
+ plural: 'л';
+ };
+ items: {
+ singular: 'шт';
+ plural: 'шт';
+ };
+};
+
+export const translations: translationsType = {
+ grams: { singular: 'гр', plural: 'кг' },
+ milliliters: { singular: 'мл', plural: 'л' },
+ items: { singular: 'шт', plural: 'шт' },
+};
+
export const translateMeasureUnit = (measureUnit: string, amount: number) => {
const translatedMeasureObj = { measureUnit, amount };
switch (true) {
case !measureUnit || !amount:
- translatedMeasureObj.measureUnit = 'шт';
+ translatedMeasureObj.measureUnit = translations.items.singular;
translatedMeasureObj.amount = 1;
return translatedMeasureObj;
case amount > 499 && measureUnit === 'grams':
- translatedMeasureObj.measureUnit = 'кг';
+ translatedMeasureObj.measureUnit = translations.grams.plural;
translatedMeasureObj.amount = amount / 1000;
return translatedMeasureObj;
case amount > 499 && measureUnit === 'milliliters':
- translatedMeasureObj.measureUnit = 'л';
+ translatedMeasureObj.measureUnit = translations.milliliters.plural;
translatedMeasureObj.amount = amount / 1000;
return translatedMeasureObj;
case measureUnit === 'grams':
- translatedMeasureObj.measureUnit = 'гр';
+ translatedMeasureObj.measureUnit = translations.grams.singular;
return translatedMeasureObj;
case measureUnit === 'milliliters':
- translatedMeasureObj.measureUnit = 'мл';
+ translatedMeasureObj.measureUnit = translations.milliliters.singular;
return translatedMeasureObj;
case measureUnit === 'items':
- translatedMeasureObj.measureUnit = 'шт';
+ translatedMeasureObj.measureUnit = translations.items.singular;
return translatedMeasureObj;
default:
return translatedMeasureObj;
From 6199e66174324bb559c6c028bab6970280c4766c Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 10:25:50 +0300
Subject: [PATCH 099/141] feat: split logic into functions and move it out of
useEffect
---
src/pages/recipe/index.tsx | 114 ++++++++++++++++++++++++-------------
1 file changed, 73 insertions(+), 41 deletions(-)
diff --git a/src/pages/recipe/index.tsx b/src/pages/recipe/index.tsx
index e1911ec9..103876f0 100644
--- a/src/pages/recipe/index.tsx
+++ b/src/pages/recipe/index.tsx
@@ -1,18 +1,19 @@
-import React, { useState } from 'react';
-import styles from './recipe.module.scss';
-import Breadcrumbs from '@components/breadcrumbs';
-import IngredientsList from '@components/recipes-components/ingredients-list';
-import { declOfNum } from '@utils/utils';
+import React, { useState, useEffect } from 'react';
+import { useParams } from 'react-router';
import clsx from 'clsx';
-import api from '@services/api.ts';
+
+import Breadcrumbs from '@components/breadcrumbs';
import Preloader from '@components/preloader';
-import { useEffect } from 'react';
-import { useParams } from 'react-router';
-import { useCart } from '@hooks/use-cart-context';
import RecipeInfo from '@components/recipes-components/recipe-info';
-import { usePopup } from '@hooks/use-popup';
+import IngredientsList from '@components/recipes-components/ingredients-list';
import PopupRecipe from '@components/popups/popup-recipe';
+
+import api from '@services/api.ts';
+import { declOfNum } from '@utils/utils';
import { translateMeasureUnit } from '@utils/utils';
+import { useCart } from '@hooks/use-cart-context';
+import { usePopup } from '@hooks/use-popup';
+import styles from './recipe.module.scss';
type ReceipeIngredientInfoProps = {
amount: number;
@@ -56,6 +57,56 @@ const Recipe: React.FC = () => {
carbonhydrates: 0,
kcal: 0,
});
+ const [ingredients, setIngredients] = useState([
+ {
+ amount: 0,
+ final_price: 0,
+ id: 0,
+ ingredient_photo: '',
+ measure_unit: '',
+ name: '',
+ need_to_buy: 0,
+ quantity_in_recipe: 0,
+ quantity_in_recipe_measure: '',
+ },
+ ]);
+
+ const updateIngredientMeasureUnits = (ingredients: ReceipeIngredientInfoProps[]) => {
+ return ingredients.map((ingredient) => {
+ const ingredientMeasureUnit = ingredient.measure_unit;
+ const { measureUnit, amount } = translateMeasureUnit(
+ ingredientMeasureUnit,
+ ingredient.amount
+ );
+
+ const { measureUnit: newMeasureUnit, amount: newAmount } = translateMeasureUnit(
+ ingredientMeasureUnit,
+ ingredient.quantity_in_recipe
+ );
+ return {
+ ...ingredient,
+ amount,
+ measure_unit: measureUnit,
+ quantity_in_recipe: newAmount,
+ quantity_in_recipe_measure: `${newAmount} ${newMeasureUnit}`,
+ };
+ });
+ };
+
+ const getRecipeByLines = (recipeText: string) => {
+ return recipeText.split('\n');
+ };
+ const getNumeralizeWord = (cookingTime: number) => {
+ return declOfNum(cookingTime, ['минута', 'минуты', 'минут']);
+ };
+ const extractRecipeNutrients = (recipe: ReceipeInfoProps) => {
+ return {
+ proteins: recipe.proteins,
+ fats: recipe.fats,
+ carbonhydrates: recipe.carbohydrates,
+ kcal: recipe.kcal,
+ };
+ };
useEffect(() => {
if (!id) {
@@ -63,39 +114,20 @@ const Recipe: React.FC = () => {
}
const recipeId: number = parseInt(id, 10);
+
const fetchReceiptAndProducts = async () => {
const recipe: ReceipeInfoProps = await api.getRecipeById(recipeId);
- recipe.ingredients.map((ingredient, id) => {
- const ingredientMeasureUnit = ingredient.measure_unit;
-
- const { measureUnit, amount } = translateMeasureUnit(
- ingredientMeasureUnit,
- ingredient.amount
- );
-
- const { measureUnit: newMeasureUnit, amount: newAmount } = translateMeasureUnit(
- ingredientMeasureUnit,
- ingredient.quantity_in_recipe
- );
-
- recipe.ingredients[id].amount = amount;
- recipe.ingredients[id].measure_unit = measureUnit;
- recipe.ingredients[id].quantity_in_recipe = newAmount;
- recipe.ingredients[id].quantity_in_recipe_measure = `${
- newAmount + newMeasureUnit
- }`;
- });
-
- setRecipeByLines(recipe.text.split('\n'));
- setNumeralizeWord(declOfNum(recipe.cooking_time, ['минута', 'минуты', 'минут']));
+ const recipeByLines = getRecipeByLines(recipe.text);
+ const numeralizeWord = getNumeralizeWord(recipe.cooking_time);
+ const updatedIngredients = updateIngredientMeasureUnits(recipe.ingredients);
+ const nutrients = extractRecipeNutrients(recipe);
+
setRecipeInfo(recipe);
- setRecipeNutrients({
- proteins: recipe.proteins,
- fats: recipe.fats,
- carbonhydrates: recipe.carbohydrates,
- kcal: recipe.kcal,
- });
+ setRecipeByLines(recipeByLines);
+ setNumeralizeWord(numeralizeWord);
+ setIngredients(updatedIngredients);
+ setRecipeNutrients(nutrients);
};
fetchReceiptAndProducts().finally(() => setIsLoading(false));
@@ -123,7 +155,7 @@ const Recipe: React.FC = () => {
{`${recipeInfo.cooking_time} ${numeralizeWord}`}
-
+
{
)}
-
+
);
};
From 7d33a6b37e984b3488f3d2d15da5f0165b0ac111 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 11:59:24 +0300
Subject: [PATCH 100/141] refactor: move recipe types to separate file
---
src/components/popups/popup-recipe/index.tsx | 18 ++--------
.../ingredients-list-popup/index.tsx | 15 +-------
.../ingredients-list/index.tsx | 17 ++-------
.../products-list-popup/index.tsx | 18 ++--------
src/components/recipes-components/types.ts | 30 ++++++++++++++++
src/pages/recipe/index.tsx | 35 ++++---------------
6 files changed, 44 insertions(+), 89 deletions(-)
create mode 100644 src/components/recipes-components/types.ts
diff --git a/src/components/popups/popup-recipe/index.tsx b/src/components/popups/popup-recipe/index.tsx
index 11e8d8ea..5b188342 100644
--- a/src/components/popups/popup-recipe/index.tsx
+++ b/src/components/popups/popup-recipe/index.tsx
@@ -1,22 +1,10 @@
import React from 'react';
-import styles from './popup-recipe.module.scss';
import Popup from '@components/popup';
-import { usePopup } from '@hooks/use-popup';
import IngredientsListPopup from '@components/recipes-components/ingredients-list-popup';
import ProductsListPopup from '@components/recipes-components/products-list-popup';
-
-type RecipeIngredientsProps = {
- ingredients: {
- amount: number;
- final_price: number;
- id: number;
- ingredient_photo: string;
- measure_unit: string;
- name: string;
- need_to_buy: number;
- quantity_in_recipe: number;
- }[];
-};
+import type { RecipeIngredientsProps } from '@components/recipes-components/types';
+import { usePopup } from '@hooks/use-popup';
+import styles from './popup-recipe.module.scss';
const PopupRecipe: React.FC = ({ ingredients }) => {
const {
diff --git a/src/components/recipes-components/ingredients-list-popup/index.tsx b/src/components/recipes-components/ingredients-list-popup/index.tsx
index 46683f47..242f9e1c 100644
--- a/src/components/recipes-components/ingredients-list-popup/index.tsx
+++ b/src/components/recipes-components/ingredients-list-popup/index.tsx
@@ -1,20 +1,7 @@
import React from 'react';
+import type { RecipeIngredientsProps } from '../types';
import styles from './ingredients-list-popup.module.scss';
-type RecipeIngredientsProps = {
- ingredients: {
- amount: number;
- final_price: number;
- id: number;
- ingredient_photo: string;
- measure_unit: string;
- name: string;
- need_to_buy: number;
- quantity_in_recipe: number;
- quantity_in_recipe_measure?: string;
- }[];
-};
-
const IngredientsListPopup: React.FC = ({ ingredients }) => {
return (
diff --git a/src/components/recipes-components/ingredients-list/index.tsx b/src/components/recipes-components/ingredients-list/index.tsx
index b9892dfa..2c305c02 100644
--- a/src/components/recipes-components/ingredients-list/index.tsx
+++ b/src/components/recipes-components/ingredients-list/index.tsx
@@ -1,21 +1,8 @@
import React from 'react';
+import clsx from 'clsx';
+import type { RecipeIngredientsProps } from '../types';
import { declOfNum } from '@utils/utils';
import styles from './ingredients-list.module.scss';
-import clsx from 'clsx';
-
-type RecipeIngredientsProps = {
- ingredients: {
- amount: number;
- final_price: number;
- id: number;
- ingredient_photo: string;
- measure_unit: string;
- name: string;
- need_to_buy: number;
- quantity_in_recipe: number;
- quantity_in_recipe_measure?: string;
- }[];
-};
const IngredientsList: React.FC
= ({ ingredients }) => {
const numOfIngredients = ingredients.length;
diff --git a/src/components/recipes-components/products-list-popup/index.tsx b/src/components/recipes-components/products-list-popup/index.tsx
index 0a107b7c..b5028648 100644
--- a/src/components/recipes-components/products-list-popup/index.tsx
+++ b/src/components/recipes-components/products-list-popup/index.tsx
@@ -1,25 +1,11 @@
import React, { useState } from 'react';
-import styles from './products-list-popup.module.scss';
import clsx from 'clsx';
import { useCart } from '@hooks/use-cart-context';
+import type { ReceipeIngredient, RecipeIngredientsProps } from '../types';
import closeIcon from '@images/profile/close.svg';
import plusIcon from '@images/plus_button.svg';
import minusIcon from '@images/minus_button.svg';
-
-type ReceipeIngredient = {
- amount: number;
- final_price: number;
- id: number;
- ingredient_photo: string;
- measure_unit: string;
- name: string;
- need_to_buy: number;
- quantity_in_recipe: number;
-};
-
-type RecipeIngredientsProps = {
- ingredients: ReceipeIngredient[];
-};
+import styles from './products-list-popup.module.scss';
const ProductsListPopup: React.FC = ({ ingredients }) => {
const [products, setProducts] = useState(Array);
diff --git a/src/components/recipes-components/types.ts b/src/components/recipes-components/types.ts
new file mode 100644
index 00000000..84904cfd
--- /dev/null
+++ b/src/components/recipes-components/types.ts
@@ -0,0 +1,30 @@
+export type ReceipeIngredient = {
+ amount: number;
+ final_price: number;
+ id: number;
+ ingredient_photo: string;
+ measure_unit: string;
+ name: string;
+ need_to_buy: number;
+ quantity_in_recipe: number;
+ quantity_in_recipe_measure?: string;
+};
+
+export type RecipeIngredientsProps = {
+ ingredients: ReceipeIngredient[];
+};
+
+export type ReceipeInfoProps = {
+ author: number;
+ carbohydrates: number;
+ cooking_time: number;
+ fats: number;
+ id: number;
+ image: string;
+ ingredients: ReceipeIngredient[];
+ kcal: number;
+ name: string;
+ proteins: number;
+ text: string;
+ total_ingredients?: number;
+};
diff --git a/src/pages/recipe/index.tsx b/src/pages/recipe/index.tsx
index 103876f0..a5238cf0 100644
--- a/src/pages/recipe/index.tsx
+++ b/src/pages/recipe/index.tsx
@@ -8,6 +8,10 @@ import RecipeInfo from '@components/recipes-components/recipe-info';
import IngredientsList from '@components/recipes-components/ingredients-list';
import PopupRecipe from '@components/popups/popup-recipe';
+import type {
+ ReceipeIngredient,
+ ReceipeInfoProps,
+} from '@components/recipes-components/types';
import api from '@services/api.ts';
import { declOfNum } from '@utils/utils';
import { translateMeasureUnit } from '@utils/utils';
@@ -15,33 +19,6 @@ import { useCart } from '@hooks/use-cart-context';
import { usePopup } from '@hooks/use-popup';
import styles from './recipe.module.scss';
-type ReceipeIngredientInfoProps = {
- amount: number;
- final_price: number;
- id: number;
- ingredient_photo: string;
- measure_unit: string;
- name: string;
- need_to_buy: number;
- quantity_in_recipe: number;
- quantity_in_recipe_measure?: string;
-};
-
-type ReceipeInfoProps = {
- author: number;
- carbohydrates: number;
- cooking_time: number;
- fats: number;
- id: number;
- image: string;
- ingredients: ReceipeIngredientInfoProps[];
- kcal: number;
- name: string;
- proteins: number;
- text: string;
- total_ingredients?: number;
-};
-
const Recipe: React.FC = () => {
const { id } = useParams();
const { handleOpenPopup } = usePopup();
@@ -57,7 +34,7 @@ const Recipe: React.FC = () => {
carbonhydrates: 0,
kcal: 0,
});
- const [ingredients, setIngredients] = useState([
+ const [ingredients, setIngredients] = useState([
{
amount: 0,
final_price: 0,
@@ -71,7 +48,7 @@ const Recipe: React.FC = () => {
},
]);
- const updateIngredientMeasureUnits = (ingredients: ReceipeIngredientInfoProps[]) => {
+ const updateIngredientMeasureUnits = (ingredients: ReceipeIngredient[]) => {
return ingredients.map((ingredient) => {
const ingredientMeasureUnit = ingredient.measure_unit;
const { measureUnit, amount } = translateMeasureUnit(
From 3cefa5c6c3df138196aeb4e26541486fd42871b1 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 12:05:58 +0300
Subject: [PATCH 101/141] refactor: decrease code nesting
---
src/pages/product/index.tsx | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx
index c9e7046a..c9373fb9 100644
--- a/src/pages/product/index.tsx
+++ b/src/pages/product/index.tsx
@@ -30,13 +30,13 @@ const Product: React.FC = () => {
const { id } = useParams();
useEffect(() => {
- if (productItem !== null) {
- const translatedMeasureObj = translateMeasureUnit(
- productItem.measure_unit,
- productItem.amount
- );
- setMeasureObj(translatedMeasureObj);
- }
+ if (productItem === null) return;
+
+ const translatedMeasureObj = translateMeasureUnit(
+ productItem.measure_unit,
+ productItem.amount
+ );
+ setMeasureObj(translatedMeasureObj);
}, [productItem]);
useEffect(() => {
From 7aacfd59dcbd68a2625622736d3d18737384f008 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 22:01:53 +0300
Subject: [PATCH 102/141] feat: (rebase) add translated measure unit to orders
in profile
---
.../profile-order/index.tsx | 33 ++++++++++++++-----
1 file changed, 24 insertions(+), 9 deletions(-)
diff --git a/src/components/profile-components/profile-order/index.tsx b/src/components/profile-components/profile-order/index.tsx
index 4c1a70e0..70e4e781 100644
--- a/src/components/profile-components/profile-order/index.tsx
+++ b/src/components/profile-components/profile-order/index.tsx
@@ -2,6 +2,7 @@ import clsx from 'clsx';
import PaymentButton from '@components/payment-button';
import OrderStatus from '../order-status';
import styles from './profile-order.module.scss';
+import { translateMeasureUnit } from '@utils/utils';
type OrderStatusType =
| 'Ordered'
@@ -35,7 +36,7 @@ type CommonOrder = {
payment_method?: string;
delivery_method?: string;
status?: OrderStatusType;
- products: Array<{ product: Product; quantity: string }> | Product[];
+ products: Array<{ product: Product; quantity: number }> | Product[];
};
type Props = {
@@ -59,6 +60,16 @@ const ProfileOrder = ({
products,
} = order;
+ const getAmountWithMeasureUnit = (
+ unitOfMeasure: string,
+ size: number,
+ multiplier: number
+ ) => {
+ const totalAmount = size * multiplier;
+ const { measureUnit, amount } = translateMeasureUnit(unitOfMeasure, totalAmount);
+ return `${amount} ${measureUnit}`;
+ };
+
let payment_method_ru =
payment_method === 'Payment at the point of delivery'
? 'Банковской картой'
@@ -86,7 +97,7 @@ const ProfileOrder = ({
{isShowedProductsDetails ? (
- {(products as Array<{ product: Product; quantity: string }>).map((item) => (
+ {(products as Array<{ product: Product; quantity: number }>).map((item) => (
{item.product.name}
- {`${
- item.product.amount * Number(item.quantity)
- } ${item.product.measure_unit}`}
- {`${item.product.final_price} руб.`}
+
+ {getAmountWithMeasureUnit(
+ item.product.measure_unit,
+ item.product.amount,
+ item.quantity
+ )}
+
+ {`${
+ item.product.final_price * item.quantity
+ } руб.`}
))}
) : (
- {(products as Array<{ product: Product; quantity: string }>).map(
+ {(products as Array<{ product: Product; quantity: number }>).map(
(item, index) => (
{index < 5 && (
From 7ed19a80565d0d25e8204bdf936eb05de87ec9f6 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 22:33:56 +0300
Subject: [PATCH 103/141] feat: add link to product card from profile orders
---
.../profile-order/index.tsx | 27 +++++++++++++------
.../profile-order/profile-order.module.scss | 12 ++++++++-
2 files changed, 30 insertions(+), 9 deletions(-)
diff --git a/src/components/profile-components/profile-order/index.tsx b/src/components/profile-components/profile-order/index.tsx
index 70e4e781..cbfb2348 100644
--- a/src/components/profile-components/profile-order/index.tsx
+++ b/src/components/profile-components/profile-order/index.tsx
@@ -1,3 +1,4 @@
+import { Link } from 'react-router-dom';
import clsx from 'clsx';
import PaymentButton from '@components/payment-button';
import OrderStatus from '../order-status';
@@ -99,14 +100,24 @@ const ProfileOrder = ({
{(products as Array<{ product: Product; quantity: number }>).map((item) => (
-
-
-
- {item.product.name}
+
+
+
+
+
+
+ {item.product.name}
+
{getAmountWithMeasureUnit(
item.product.measure_unit,
diff --git a/src/components/profile-components/profile-order/profile-order.module.scss b/src/components/profile-components/profile-order/profile-order.module.scss
index 2ffc34a8..4967cd15 100644
--- a/src/components/profile-components/profile-order/profile-order.module.scss
+++ b/src/components/profile-components/profile-order/profile-order.module.scss
@@ -56,7 +56,6 @@
&__name {
justify-self: start;
margin: 0;
- grid-column: 2;
text-align: left;
}
@@ -89,8 +88,19 @@
object-fit: cover;
width: 100%;
height: 100%;
+ }
+
+ &__linkImage {
+ cursor: pointer;
grid-column: 1;
}
+
+ &__linkText {
+ cursor: pointer;
+ text-decoration: none;
+ color: $active-text-color;
+ grid-column: 2;
+ }
}
.order-details {
From 2632fcec4236a7750c4fa68c476f5ede8d127466 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 22:35:56 +0300
Subject: [PATCH 104/141] fix: change translation for order status
---
src/components/profile-components/order-status/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/profile-components/order-status/index.tsx b/src/components/profile-components/order-status/index.tsx
index 01137a6d..b5c35902 100644
--- a/src/components/profile-components/order-status/index.tsx
+++ b/src/components/profile-components/order-status/index.tsx
@@ -33,7 +33,7 @@ const statusObj = {
style: styles.canceled,
},
Ordered: {
- text: 'Оплачен',
+ text: 'Заказан',
image: orderedIcon,
style: styles.ordered,
},
From ef21dcc12b8fe06d075579f7da9f02419e42fe35 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 22:57:15 +0300
Subject: [PATCH 105/141] feat: add link to product from shopping cart
---
src/components/shopping-item/index.tsx | 11 ++++++++---
.../shopping-item/shopping-item.module.scss | 4 ++++
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/components/shopping-item/index.tsx b/src/components/shopping-item/index.tsx
index d67b0654..e759faa2 100644
--- a/src/components/shopping-item/index.tsx
+++ b/src/components/shopping-item/index.tsx
@@ -50,9 +50,14 @@ const ShoppingItem: React.FC = (props) => {
-
{`${product.name}, ${
- amount + measureUnit
- }`}
+
+
{`${product.name}, ${
+ amount + measureUnit
+ }`}
+
Date: Sun, 7 Jan 2024 20:51:08 +0300
Subject: [PATCH 106/141] fix: change button to div because of nesting-button
error
---
src/components/profile-components/profile-order/index.tsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/components/profile-components/profile-order/index.tsx b/src/components/profile-components/profile-order/index.tsx
index cbfb2348..df9614a0 100644
--- a/src/components/profile-components/profile-order/index.tsx
+++ b/src/components/profile-components/profile-order/index.tsx
@@ -87,8 +87,7 @@ const ProfileOrder = ({
const date = ordering_date && new Date(ordering_date).toLocaleDateString();
return (
<>
-
@@ -164,7 +163,7 @@ const ProfileOrder = ({
)}
-
+
>
);
};
From 75f914a6e2a604d3bb495f5b9b9c3116ad476b4a Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 17:11:22 +0300
Subject: [PATCH 107/141] feat: make header fixed
---
src/components/navigation-bar/navigation-bar.module.scss | 2 +-
src/layouts/header/header.module.scss | 9 +++++++--
src/layouts/layout/layout.module.scss | 5 +++++
3 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/components/navigation-bar/navigation-bar.module.scss b/src/components/navigation-bar/navigation-bar.module.scss
index 49869906..b5a7b039 100644
--- a/src/components/navigation-bar/navigation-bar.module.scss
+++ b/src/components/navigation-bar/navigation-bar.module.scss
@@ -11,7 +11,7 @@
display: flex;
justify-content: space-between;
align-items: center;
- z-index: 1000;
+ z-index: 150;
transition: background-color 0.7s;
}
diff --git a/src/layouts/header/header.module.scss b/src/layouts/header/header.module.scss
index 4b269fd1..61cd2c94 100644
--- a/src/layouts/header/header.module.scss
+++ b/src/layouts/header/header.module.scss
@@ -3,11 +3,16 @@
@use '@scss/base.scss' as *;
.header {
- position: relative;
+ position: fixed;
+ top: 0;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: #fff;
width: 100%;
+ max-width: 1280px;
padding: 24px 0;
box-shadow: 2px 2px 20px 0 rgb(0 0 0 / 10%);
- z-index: 2;
+ z-index: 100;
}
.header__container {
diff --git a/src/layouts/layout/layout.module.scss b/src/layouts/layout/layout.module.scss
index 8485c1dd..8391345f 100644
--- a/src/layouts/layout/layout.module.scss
+++ b/src/layouts/layout/layout.module.scss
@@ -11,4 +11,9 @@
.layout__main {
flex: 1;
min-height: 77vh;
+ padding-top: 100px;
+
+ @media screen and (width < 1024px) {
+ padding-top: 80px;
+ }
}
From a28a9929bb70bc9b4f46be567f38455e77d6237a Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 17:22:32 +0300
Subject: [PATCH 108/141] feat: add scroll to top for pages when loading
---
src/layouts/layout/layout.tsx | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/layouts/layout/layout.tsx b/src/layouts/layout/layout.tsx
index 50121d18..63ab51be 100644
--- a/src/layouts/layout/layout.tsx
+++ b/src/layouts/layout/layout.tsx
@@ -1,4 +1,5 @@
-import React, { ReactNode } from 'react';
+import React, { ReactNode, useEffect } from 'react';
+import { useLocation } from 'react-router';
import Header from '../header';
import Footer from '../footer';
import style from './layout.module.scss';
@@ -8,6 +9,13 @@ type LayoutProps = {
};
const Layout: React.FC = ({ children }) => {
+ const location = useLocation();
+ const page = location.pathname;
+
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, [page]);
+
return (
From 94362335893d52ccb1116a23d90faa41e5e0bffb Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sat, 6 Jan 2024 17:26:50 +0300
Subject: [PATCH 109/141] refactor: delete unused scroll
---
src/pages/catalog/index.tsx | 2 --
src/pages/category/index.tsx | 4 ----
src/pages/product/index.tsx | 4 ----
3 files changed, 10 deletions(-)
diff --git a/src/pages/catalog/index.tsx b/src/pages/catalog/index.tsx
index b09c52eb..66628c59 100644
--- a/src/pages/catalog/index.tsx
+++ b/src/pages/catalog/index.tsx
@@ -28,8 +28,6 @@ const Catalog: React.FC = () => {
.finally(() => {
setIsLoading(false);
});
-
- window.scrollTo(0, 0);
}, []);
return (
diff --git a/src/pages/category/index.tsx b/src/pages/category/index.tsx
index d9bc78f2..277c9708 100644
--- a/src/pages/category/index.tsx
+++ b/src/pages/category/index.tsx
@@ -51,10 +51,6 @@ const Category: React.FC = () => {
});
}, [category, navigate]);
- useEffect(() => {
- window.scrollTo(0, 0);
- }, []);
-
const changeCheckboxState = (e: ChangeEvent
) => {
if (e.target.name === 'vegetarian') {
setCheckboxState((prev) => ({ ...prev, vegetarian: !prev.vegetarian }));
diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx
index c9373fb9..752b2eeb 100644
--- a/src/pages/product/index.tsx
+++ b/src/pages/product/index.tsx
@@ -73,10 +73,6 @@ const Product: React.FC = () => {
}
}, [cartData, id]);
- useEffect(() => {
- window.scrollTo(0, 0);
- }, []);
-
const handleAddCartClick = () => {
if (isLoaded || !productItem) return;
if (id !== undefined) {
From 0f783dd307660cc8becdc5d71c8bae89e0fa9313 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 20:58:23 +0300
Subject: [PATCH 110/141] refactor: use object destructuring
---
src/layouts/layout/layout.tsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/layouts/layout/layout.tsx b/src/layouts/layout/layout.tsx
index 63ab51be..87c85325 100644
--- a/src/layouts/layout/layout.tsx
+++ b/src/layouts/layout/layout.tsx
@@ -9,12 +9,11 @@ type LayoutProps = {
};
const Layout: React.FC = ({ children }) => {
- const location = useLocation();
- const page = location.pathname;
+ const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
- }, [page]);
+ }, [pathname]);
return (
From 11ee2c0059a3f339ca07d4427b7a26c844b99581 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 19:38:15 +0300
Subject: [PATCH 111/141] feat: add checkbox for agreement
---
src/pages/checkout/checkout.module.scss | 16 +++++++++--
src/pages/checkout/index.tsx | 38 ++++++++++++++++---------
2 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/src/pages/checkout/checkout.module.scss b/src/pages/checkout/checkout.module.scss
index 9465cb01..c130cb8b 100644
--- a/src/pages/checkout/checkout.module.scss
+++ b/src/pages/checkout/checkout.module.scss
@@ -405,6 +405,7 @@
}
/* stylelint-disable-next-line selector-attribute-quotes */
+.orderse input[type='checkbox'],
.execution__form input[type='radio'] {
display: none;
}
@@ -417,6 +418,10 @@
user-select: none;
}
+.orderse .execution__item {
+ padding-left: 30px;
+}
+
.execution__item label::after {
content: '';
display: inline-block;
@@ -435,13 +440,19 @@
}
}
+.execution__item label[for='agreement']::after {
+ bottom: 50%;
+ left: 0;
+}
+
/* stylelint-disable-next-line selector-attribute-quotes */
+.execution__item input[type='checkbox']:checked + label::after,
.execution__item input[type='radio']:checked + label::after {
background: url('@images/Radio_button.svg');
background-position: center;
background-size: 16px;
background-repeat: no-repeat;
- border: 1px solid #285718;
+ border: 1px solid $green-primary-700;
width: 16px;
height: 16px;
@@ -451,7 +462,7 @@
background-size: 10px;
/* stylelint-disable-next-line declaration-block-no-shorthand-property-overrides */
background: none;
- background-color: #285718;
+ background-color: $green-primary-700;
}
}
@@ -462,6 +473,7 @@
}
/* stylelint-disable-next-line selector-attribute-quotes */
+.execution__item input[type='checkbox']:disabled + label::after,
.execution__item input[type='radio']:disabled + label::after {
opacity: 0.5;
transition: opacity 0.5s ease-in-out;
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index b5f38c9e..25b124a4 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
-import { useLocation, useNavigate } from 'react-router-dom';
+import { Link, useLocation, useNavigate } from 'react-router-dom';
import styles from './checkout.module.scss';
import api from '@services/api.ts';
import Input from '@ui/input';
@@ -42,6 +42,7 @@ const Checkout: React.FC = () => {
const userAddresses = user?.addresses as unknown[] as Address[];
const [comment, setComment] = React.useState
('');
const [popupText, setPopupText] = useState('');
+ const [isAgreed, setIsAgreed] = useState(false);
const openInfoPopup = (text: string) => {
setPopupText(text);
@@ -158,6 +159,10 @@ const Checkout: React.FC = () => {
setPopupText('');
};
+ const handleAgreementChange = () => {
+ setIsAgreed(!isAgreed);
+ };
+
return (
@@ -334,7 +339,6 @@ const Checkout: React.FC = () => {
name="time"
value="9.00-12.00"
id="time_early-morning"
- className={styles.execution__radio}
onChange={handleTimeChange}
checked={selectedTime === '9.00-12.00'}
/>
@@ -347,7 +351,6 @@ const Checkout: React.FC = () => {
name="time"
value="12.00-15.00"
id="time_lunch"
- className={styles.execution__radio}
onChange={handleTimeChange}
checked={selectedTime === '12.00-15.00'}
/>
@@ -360,7 +363,6 @@ const Checkout: React.FC = () => {
name="time"
value="15.00-18.00"
id="time_day"
- className={styles.execution__radio}
onChange={handleTimeChange}
checked={selectedTime === '15.00-18.00'}
/>
@@ -373,7 +375,6 @@ const Checkout: React.FC = () => {
name="time"
value="18.00-21.00"
id="time_evening"
- className={styles.execution__radio}
onChange={handleTimeChange}
checked={selectedTime === '18.00-21.00'}
/>
@@ -391,7 +392,6 @@ const Checkout: React.FC = () => {
name="payment"
value="Online"
id="payment_online"
- className={styles.execution__radio}
onChange={handlePaymentChange}
/>
Оплата онлайн
@@ -404,7 +404,6 @@ const Checkout: React.FC = () => {
name="payment"
value="In getting by cash"
id="payment_cash"
- className={styles.execution__radio}
onChange={handlePaymentChange}
/>
Оплата курьеру
@@ -417,7 +416,6 @@ const Checkout: React.FC = () => {
name="payment"
value="Payment at the point of delivery"
id="payment_offline"
- className={styles.execution__radio}
onChange={handlePaymentChange}
/>
Оплата в пункте выдачи
@@ -476,11 +474,25 @@ const Checkout: React.FC = () => {
>
Оформить заказ
-
- Нажимая на кнопку «Оформить заказ», вы соглашаетесь
- с условиями обработки персональных данных, а также
- с условиями продажи.
-
+
+
+
+ Я согласен с
+
+ условиями обработки персональных данных
+
+ и
+
+ условиями продажи
+
+
+
From 397ad77dc0b7692fad908b80869aeb3f0ca2bc1b Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 19:51:07 +0300
Subject: [PATCH 112/141] feat: add popup in case agreement not selected
---
src/data/constants.ts | 2 ++
src/pages/checkout/index.tsx | 2 ++
2 files changed, 4 insertions(+)
diff --git a/src/data/constants.ts b/src/data/constants.ts
index 7c1f70cb..c767dc23 100644
--- a/src/data/constants.ts
+++ b/src/data/constants.ts
@@ -36,4 +36,6 @@ export const popupInfoText = {
enterAddress: 'Пожалуйста, введите адрес',
errorShort: 'Ошибка при создании заказа: ',
errorLong: 'Ошибка при создании заказа: ',
+ selectAgreement:
+ 'Для оформления заказа необходимо согласие с условиями обработки персональных данных и условиями продажи.',
};
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index 25b124a4..b420a1b3 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -81,6 +81,8 @@ const Checkout: React.FC = () => {
return openInfoPopup(popupInfoText.enterAddress);
case !isLoggedIn && !selectedPayment:
return openInfoPopup(popupInfoText.choosePaymentMethod);
+ case !isAgreed:
+ return openInfoPopup(popupInfoText.selectAgreement);
default:
return true;
}
From 1ec4df7920d59187924c27afcc8bcfaca089a73d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 21:29:47 +0300
Subject: [PATCH 113/141] feat: add breadcrumbs to checkout page
---
src/components/breadcrumbs/index.tsx | 1 +
src/pages/checkout/checkout.module.scss | 1 -
src/pages/checkout/index.tsx | 14 +++++++++-----
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/components/breadcrumbs/index.tsx b/src/components/breadcrumbs/index.tsx
index 6c677d2c..55e91ba8 100644
--- a/src/components/breadcrumbs/index.tsx
+++ b/src/components/breadcrumbs/index.tsx
@@ -32,6 +32,7 @@ const Breadcrumbs: React.FC = ({ productName, category }) => {
recipes: 'Рецепты',
contacts: 'Контакты',
orders: 'Мои заказы',
+ order: 'Оформление заказа',
};
if (category) {
diff --git a/src/pages/checkout/checkout.module.scss b/src/pages/checkout/checkout.module.scss
index c130cb8b..866b08c8 100644
--- a/src/pages/checkout/checkout.module.scss
+++ b/src/pages/checkout/checkout.module.scss
@@ -4,7 +4,6 @@
.order {
margin: 0 auto;
width: calc(100% - 128px * 2);
- padding-top: 148px;
display: flex;
flex-direction: column;
justify-content: center;
diff --git a/src/pages/checkout/index.tsx b/src/pages/checkout/index.tsx
index b420a1b3..b56ba375 100644
--- a/src/pages/checkout/index.tsx
+++ b/src/pages/checkout/index.tsx
@@ -1,15 +1,18 @@
import React, { useState } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
-import styles from './checkout.module.scss';
-import api from '@services/api.ts';
+
+import PopupCheckoutResponse from '@components/popups/popup-checkout-response';
+import Breadcrumbs from '@components/breadcrumbs';
import Input from '@ui/input';
+
+import api from '@services/api.ts';
+import { pickupPointAddresses, URLS, popupInfoText } from '@data/constants';
import { useFormAndValidation } from '@hooks/use-form-and-validation.ts';
import { usePopup } from '@hooks/use-popup';
-import PopupCheckoutResponse from '@components/popups/popup-checkout-response';
-import { OrderPostAdd } from '@services/generated-api/data-contracts.ts';
import { useAuth } from '@hooks/use-auth.ts';
import { useCart } from '@hooks/use-cart-context.ts';
-import { pickupPointAddresses, URLS, popupInfoText } from '@data/constants';
+import type { OrderPostAdd } from '@services/generated-api/data-contracts.ts';
+import styles from './checkout.module.scss';
type Address = {
id: number;
@@ -167,6 +170,7 @@ const Checkout: React.FC = () => {
return (
+
Оформление заказа
From f32433f4ba06ef143fdd502722b4e46204b164b6 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Sun, 7 Jan 2024 23:22:00 +0300
Subject: [PATCH 114/141] fix: put text inside Link
---
src/pages/checkout/checkout-success/index.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/pages/checkout/checkout-success/index.tsx b/src/pages/checkout/checkout-success/index.tsx
index 9502aecc..2607b63d 100644
--- a/src/pages/checkout/checkout-success/index.tsx
+++ b/src/pages/checkout/checkout-success/index.tsx
@@ -39,8 +39,9 @@ const CheckoutSuccess: React.FC = () => {
За статусом заказа можно следить в
-
- личном кабинете.
+
+ личном кабинете.
+
>
) : (
From 4601ac0870f0717cd42ccbadd91fd5abd16a5c0c Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Mon, 8 Jan 2024 16:05:55 +0200
Subject: [PATCH 115/141] fix: fix navigation-bar display on large screens
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Убрал отображение Navigation Bar для широких экранов
closes #230
---
src/components/navigation-bar/navigation-bar.module.scss | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/components/navigation-bar/navigation-bar.module.scss b/src/components/navigation-bar/navigation-bar.module.scss
index b5a7b039..13c519bf 100644
--- a/src/components/navigation-bar/navigation-bar.module.scss
+++ b/src/components/navigation-bar/navigation-bar.module.scss
@@ -8,11 +8,15 @@
width: 100%;
background-color: #fff;
padding: 0;
- display: flex;
+ display: none;
justify-content: space-between;
align-items: center;
z-index: 150;
transition: background-color 0.7s;
+
+ @media screen and (width <= 768px) {
+ display: flex;
+ }
}
.burger-icon {
From 19e6f78e00f95cab4eff34a3fcc6c71961cc3708 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 12:34:04 +0300
Subject: [PATCH 116/141] fix: change category name in accordance with the
backend
---
src/components/top-selling-this-week/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/top-selling-this-week/index.tsx b/src/components/top-selling-this-week/index.tsx
index 55503beb..c0479597 100644
--- a/src/components/top-selling-this-week/index.tsx
+++ b/src/components/top-selling-this-week/index.tsx
@@ -62,7 +62,7 @@ const TopSellingThisWeek: React.FC = () => {
findTopThreeProducts();
}
if (buttonId === 2) {
- const slugs = ['fruits', 'vegetables'];
+ const slugs = ['fruits', 'vegetables-and-herbs'];
findTopThreeProducts(slugs);
}
if (buttonId === 3) {
From 942531cb4fffb6ba9d590154d00692ead51f9414 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 12:38:20 +0300
Subject: [PATCH 117/141] fix: change translation to order status
---
src/components/profile-components/order-status/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/profile-components/order-status/index.tsx b/src/components/profile-components/order-status/index.tsx
index b5c35902..398e8a91 100644
--- a/src/components/profile-components/order-status/index.tsx
+++ b/src/components/profile-components/order-status/index.tsx
@@ -33,7 +33,7 @@ const statusObj = {
style: styles.canceled,
},
Ordered: {
- text: 'Заказан',
+ text: 'Оформлен',
image: orderedIcon,
style: styles.ordered,
},
From 61fbb5933183e31f22f5c5b61d3b2c6499c7c353 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 11:34:05 +0300
Subject: [PATCH 118/141] feat: add links from recipe to product page
---
src/components/popups/popup-recipe/index.tsx | 6 +--
.../ingredients-list-popup/index.tsx | 10 +++-
.../ingredients-list/index.tsx | 13 +++--
.../products-list-popup/index.tsx | 12 ++++-
src/components/recipes-components/types.ts | 1 +
src/pages/recipe/index.tsx | 47 ++++++++++++++++---
6 files changed, 72 insertions(+), 17 deletions(-)
diff --git a/src/components/popups/popup-recipe/index.tsx b/src/components/popups/popup-recipe/index.tsx
index 5b188342..a969d58c 100644
--- a/src/components/popups/popup-recipe/index.tsx
+++ b/src/components/popups/popup-recipe/index.tsx
@@ -6,7 +6,7 @@ import type { RecipeIngredientsProps } from '@components/recipes-components/type
import { usePopup } from '@hooks/use-popup';
import styles from './popup-recipe.module.scss';
-const PopupRecipe: React.FC = ({ ingredients }) => {
+const PopupRecipe: React.FC = ({ ingredients, handleClick }) => {
const {
popupState: { openPopupRecipe },
handleClosePopup,
@@ -21,8 +21,8 @@ const PopupRecipe: React.FC = ({ ingredients }) => {
Выберите товары для добавления в корзину
diff --git a/src/components/recipes-components/ingredients-list-popup/index.tsx b/src/components/recipes-components/ingredients-list-popup/index.tsx
index 242f9e1c..9d346363 100644
--- a/src/components/recipes-components/ingredients-list-popup/index.tsx
+++ b/src/components/recipes-components/ingredients-list-popup/index.tsx
@@ -2,7 +2,10 @@ import React from 'react';
import type { RecipeIngredientsProps } from '../types';
import styles from './ingredients-list-popup.module.scss';
-const IngredientsListPopup: React.FC
= ({ ingredients }) => {
+const IngredientsListPopup: React.FC = ({
+ ingredients,
+ handleClick,
+}) => {
return (
Из рецепта:
@@ -10,7 +13,10 @@ const IngredientsListPopup: React.FC
= ({ ingredients })
{ingredients?.map((ingredient, index) => {
return (
-
+ handleClick(ingredient.id)}
+ className={styles['popup-ingredients__name']}
+ >
{ingredient?.name}
diff --git a/src/components/recipes-components/ingredients-list/index.tsx b/src/components/recipes-components/ingredients-list/index.tsx
index 2c305c02..6210a0ee 100644
--- a/src/components/recipes-components/ingredients-list/index.tsx
+++ b/src/components/recipes-components/ingredients-list/index.tsx
@@ -4,7 +4,10 @@ import type { RecipeIngredientsProps } from '../types';
import { declOfNum } from '@utils/utils';
import styles from './ingredients-list.module.scss';
-const IngredientsList: React.FC = ({ ingredients }) => {
+const IngredientsList: React.FC = ({
+ ingredients,
+ handleClick,
+}) => {
const numOfIngredients = ingredients.length;
const numeralizeWord = declOfNum(numOfIngredients, [
'ингредиент',
@@ -24,11 +27,13 @@ const IngredientsList: React.FC = ({ ingredients }) => {
handleClick(ingredient.id)}
/>
- {`${ingredient.name}, ${
- ingredient.amount + ingredient.measure_unit
- }`}
+ handleClick(ingredient.id)}
+ className={styles.ingredient__name}
+ >{`${ingredient.name}, ${ingredient.amount + ingredient.measure_unit}`}
{ingredient.quantity_in_recipe_measure}
diff --git a/src/components/recipes-components/products-list-popup/index.tsx b/src/components/recipes-components/products-list-popup/index.tsx
index b5028648..e3552094 100644
--- a/src/components/recipes-components/products-list-popup/index.tsx
+++ b/src/components/recipes-components/products-list-popup/index.tsx
@@ -7,7 +7,10 @@ import plusIcon from '@images/plus_button.svg';
import minusIcon from '@images/minus_button.svg';
import styles from './products-list-popup.module.scss';
-const ProductsListPopup: React.FC = ({ ingredients }) => {
+const ProductsListPopup: React.FC = ({
+ ingredients,
+ handleClick,
+}) => {
const [products, setProducts] = useState(Array);
const { updateCart, error, reset, successText, cartUpdating } = useCart();
@@ -56,10 +59,15 @@ const ProductsListPopup: React.FC = ({ ingredients }) =>
key={product.name}
>
-
+
handleClick(product.id)}
+ src={product.ingredient_photo}
+ alt={product.name}
+ />
handleClick(product.id)}
>{`${product.name}, ${product.amount}${product.measure_unit}`}
diff --git a/src/components/recipes-components/types.ts b/src/components/recipes-components/types.ts
index 84904cfd..317190be 100644
--- a/src/components/recipes-components/types.ts
+++ b/src/components/recipes-components/types.ts
@@ -12,6 +12,7 @@ export type ReceipeIngredient = {
export type RecipeIngredientsProps = {
ingredients: ReceipeIngredient[];
+ handleClick: (id: number, idAndCategories?: (string | number | undefined)[][]) => void;
};
export type ReceipeInfoProps = {
diff --git a/src/pages/recipe/index.tsx b/src/pages/recipe/index.tsx
index a5238cf0..0f937f49 100644
--- a/src/pages/recipe/index.tsx
+++ b/src/pages/recipe/index.tsx
@@ -1,5 +1,5 @@
-import React, { useState, useEffect } from 'react';
-import { useParams } from 'react-router';
+import React, { useState, useEffect, useCallback } from 'react';
+import { useNavigate, useParams } from 'react-router';
import clsx from 'clsx';
import Breadcrumbs from '@components/breadcrumbs';
@@ -12,6 +12,7 @@ import type {
ReceipeIngredient,
ReceipeInfoProps,
} from '@components/recipes-components/types';
+import type { Product } from '@services/generated-api/data-contracts';
import api from '@services/api.ts';
import { declOfNum } from '@utils/utils';
import { translateMeasureUnit } from '@utils/utils';
@@ -21,12 +22,16 @@ import styles from './recipe.module.scss';
const Recipe: React.FC = () => {
const { id } = useParams();
- const { handleOpenPopup } = usePopup();
+ const { handleOpenPopup, handleClosePopup } = usePopup();
+ const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(true);
const [recipeInfo, setRecipeInfo] = useState
(Object);
const [recipeByLines, setRecipeByLines] = useState(['']);
const [numeralizeWord, setNumeralizeWord] = useState('');
+ const [productsIdAndCategories, setProductsIdAndCategories] = useState<
+ (string | number | undefined)[][]
+ >([[]]);
const { reset } = useCart();
const [recipeNutrients, setRecipeNutrients] = useState({
proteins: 0,
@@ -85,6 +90,32 @@ const Recipe: React.FC = () => {
};
};
+ const fetchProducts = useCallback(async (products: ReceipeIngredient[]) => {
+ const updatedProductsPromises = await products.map((product) => {
+ return api.productsRead(product.id);
+ });
+ const updatedProducts: Product[] = await Promise.all(updatedProductsPromises);
+
+ return updatedProducts.map((product) => {
+ const categoryName = product.category?.category_slug;
+ return [product.id, categoryName];
+ });
+ }, []);
+
+ const handleClick = (
+ id: number,
+ idAndCategories: (string | number | undefined)[][] = productsIdAndCategories
+ ) => {
+ for (const idAndCategory of idAndCategories) {
+ if (idAndCategory.includes(id)) {
+ handleClosePopup('openPopupRecipe');
+ const id = idAndCategory[0];
+ const category = idAndCategory[1];
+ return navigate(`/catalog/${category}/${id}`);
+ }
+ }
+ };
+
useEffect(() => {
if (!id) {
return;
@@ -99,7 +130,11 @@ const Recipe: React.FC = () => {
const numeralizeWord = getNumeralizeWord(recipe.cooking_time);
const updatedIngredients = updateIngredientMeasureUnits(recipe.ingredients);
const nutrients = extractRecipeNutrients(recipe);
+ const fetchedProductsIdAndCategories = await fetchProducts(updatedIngredients);
+ if (fetchedProductsIdAndCategories !== undefined) {
+ setProductsIdAndCategories(fetchedProductsIdAndCategories);
+ }
setRecipeInfo(recipe);
setRecipeByLines(recipeByLines);
setNumeralizeWord(numeralizeWord);
@@ -108,7 +143,7 @@ const Recipe: React.FC = () => {
};
fetchReceiptAndProducts().finally(() => setIsLoading(false));
- }, [id]);
+ }, [fetchProducts, id]);
const handleAddToCart = () => {
reset();
@@ -132,7 +167,7 @@ const Recipe: React.FC = () => {
{`${recipeInfo.cooking_time} ${numeralizeWord}`}
-
+
{
)}
-
+
);
};
From f190fd172efee22d447bfcb4005e0fb2068fa562 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 11:40:40 +0300
Subject: [PATCH 119/141] feat: add cursor pointer
---
.../ingredients-list-popup/ingredients-list-popup.module.scss | 1 +
.../ingredients-list/ingredients-list.module.scss | 2 ++
.../products-list-popup/products-list-popup.module.scss | 2 ++
3 files changed, 5 insertions(+)
diff --git a/src/components/recipes-components/ingredients-list-popup/ingredients-list-popup.module.scss b/src/components/recipes-components/ingredients-list-popup/ingredients-list-popup.module.scss
index 3d90ea41..651ff19a 100644
--- a/src/components/recipes-components/ingredients-list-popup/ingredients-list-popup.module.scss
+++ b/src/components/recipes-components/ingredients-list-popup/ingredients-list-popup.module.scss
@@ -36,6 +36,7 @@
&__name {
font-size: 20px;
line-height: 140%;
+ cursor: pointer;
}
&__weight {
diff --git a/src/components/recipes-components/ingredients-list/ingredients-list.module.scss b/src/components/recipes-components/ingredients-list/ingredients-list.module.scss
index bf5dd705..33a4f9b8 100644
--- a/src/components/recipes-components/ingredients-list/ingredients-list.module.scss
+++ b/src/components/recipes-components/ingredients-list/ingredients-list.module.scss
@@ -38,6 +38,7 @@
height: 74px;
overflow: hidden;
border-radius: 16px;
+ cursor: pointer;
& > img {
width: 100%;
@@ -50,6 +51,7 @@
font-size: 20px;
line-height: 140%;
margin: 0;
+ cursor: pointer;
}
&__weight {
diff --git a/src/components/recipes-components/products-list-popup/products-list-popup.module.scss b/src/components/recipes-components/products-list-popup/products-list-popup.module.scss
index 3e49c9c4..06056cde 100644
--- a/src/components/recipes-components/products-list-popup/products-list-popup.module.scss
+++ b/src/components/recipes-components/products-list-popup/products-list-popup.module.scss
@@ -62,6 +62,7 @@
height: 74px;
border-radius: 16px;
overflow: hidden;
+ cursor: pointer;
& > img {
width: 100%;
@@ -74,6 +75,7 @@
font-size: 20px;
line-height: 140%;
margin: 0;
+ cursor: pointer;
}
&__price {
From 838dfc72d844bda78c108593f1ed48d037a5ab6d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 13:33:40 +0300
Subject: [PATCH 120/141] refactor: put types in separate file
---
.../profile-order-mobile/index.tsx | 38 +-----------------
.../profile-order/index.tsx | 38 +-----------------
src/pages/profile/profile-orders/index.tsx | 39 +------------------
src/pages/profile/types.ts | 34 ++++++++++++++++
src/services/generated-api/data-contracts.ts | 2 +-
5 files changed, 41 insertions(+), 110 deletions(-)
create mode 100644 src/pages/profile/types.ts
diff --git a/src/components/profile-components/profile-order-mobile/index.tsx b/src/components/profile-components/profile-order-mobile/index.tsx
index 25d578dd..d6443934 100644
--- a/src/components/profile-components/profile-order-mobile/index.tsx
+++ b/src/components/profile-components/profile-order-mobile/index.tsx
@@ -2,43 +2,9 @@ import clsx from 'clsx';
import ProductCard from '@components/product-card';
import PaymentButton from '@components/payment-button';
import OrderStatus from '../order-status';
+import type { CommonOrder, Product } from '@pages/profile/types';
import styles from './profile-order-mobile.module.scss';
-type OrderStatusType =
- | 'Ordered'
- | 'In processing'
- | 'Collecting'
- | 'Gathered'
- | 'In delivering'
- | 'Delivered'
- | 'Completed';
-
-type Product = {
- amount: number;
- final_price: number;
- id: number;
- measure_unit: string;
- name: string;
- quantity: string;
- photo: string;
- category: {
- category_name: string;
- category_slug: string;
- };
-};
-
-type CommonOrder = {
- id: number;
- is_paid: boolean;
- order_number?: string;
- ordering_date?: string;
- total_price?: string;
- payment_method?: string;
- delivery_method?: string;
- status?: OrderStatusType;
- products: Array<{ product: Product; quantity: string }> | Product[];
-};
-
type Props = {
readonly isShowedProductsDetails?: boolean;
readonly showDetails?: () => void;
@@ -85,7 +51,7 @@ const ProfileOrderMobile = ({
{isShowedProductsDetails && (
- {(products as Array<{ product: Product; quantity: string }>).map((item) => (
+ {(products as Array<{ product: Product; quantity: number }>).map((item) => (
| Product[];
-};
+import type { CommonOrder, Product } from '@pages/profile/types';
+import styles from './profile-order.module.scss';
type Props = {
readonly isShowedProductsDetails?: boolean;
diff --git a/src/pages/profile/profile-orders/index.tsx b/src/pages/profile/profile-orders/index.tsx
index cc903b77..1ec600d1 100644
--- a/src/pages/profile/profile-orders/index.tsx
+++ b/src/pages/profile/profile-orders/index.tsx
@@ -1,46 +1,11 @@
import { useEffect, useState } from 'react';
+import api from '@services/api';
import ProfileOrder from '@components/profile-components/profile-order';
import ProfileOrderMobile from '@components/profile-components/profile-order-mobile';
import ReturnBackButton from '@components/profile-components/return-back-button';
import { useProfile } from '@hooks/use-profile';
+import type { CommonOrder } from '../types';
import styles from './profile-orders.module.scss';
-import api from '@services/api';
-// import type { Product } from '@services/generated-api/data-contracts';
-// import { OrderList } from '@services/generated-api/data-contracts.ts';
-
-type OrderStatusType =
- | 'Ordered'
- | 'In processing'
- | 'Collecting'
- | 'Gathered'
- | 'In delivering'
- | 'Delivered'
- | 'Completed';
-
-type Product = {
- amount: number;
- final_price: number;
- id: number;
- measure_unit: string;
- name: string;
- quantity: string;
- photo: string;
- category: {
- category_name: string;
- category_slug: string;
- };
-};
-
-type CommonOrder = {
- id: number;
- order_number?: string;
- ordering_date?: string;
- total_price?: string;
- payment_method?: string;
- delivery_method?: string;
- status?: OrderStatusType;
- products: Array<{ product: Product; quantity: string }> | Product[];
-};
export default function ProfileOrders() {
const [isOpenDetails, setIsOpenDetails] = useState();
diff --git a/src/pages/profile/types.ts b/src/pages/profile/types.ts
new file mode 100644
index 00000000..f0023e56
--- /dev/null
+++ b/src/pages/profile/types.ts
@@ -0,0 +1,34 @@
+type OrderStatusType =
+ | 'Ordered'
+ | 'In processing'
+ | 'Collecting'
+ | 'Gathered'
+ | 'In delivering'
+ | 'Delivered'
+ | 'Completed';
+
+export type Product = {
+ amount: number;
+ final_price: number;
+ id: number;
+ measure_unit: string;
+ name: string;
+ quantity: string;
+ photo: string;
+ category: {
+ category_name: string;
+ category_slug: string;
+ };
+};
+
+export type CommonOrder = {
+ id: number;
+ is_paid: boolean;
+ order_number?: string;
+ ordering_date?: string;
+ total_price?: string;
+ payment_method?: string;
+ delivery_method?: string;
+ status?: OrderStatusType;
+ products: Array<{ product: Product; quantity: number }> | Product[];
+};
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index e1d99bf9..85fbae13 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -1009,7 +1009,7 @@ export interface OrderList {
/** Payment Method */
payment_method?: 'Payment at the point of delivery' | 'In getting by cash' | 'Online';
/** Is paid */
- is_paid?: boolean;
+ is_paid: boolean;
/** Delivery Method */
delivery_method?: 'Point of delivery' | 'By courier';
/** Address */
From 7bc10097f5ebc510f5bb7a9e615516c1298df848 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 15:57:46 +0300
Subject: [PATCH 121/141] feat: (after rebase) add correct statuses to orders
list in profile
---
src/assets/images/profile/check-status.svg | 3 ++
.../profile-components/order-status/index.tsx | 21 ++++++--------
.../order-status/order-status.module.scss | 4 +++
.../profile-order-mobile/index.tsx | 28 +++++++------------
.../profile-order/index.tsx | 28 +++++++------------
src/pages/profile/profile-orders/index.tsx | 2 +-
src/pages/profile/{ => utils}/types.ts | 6 ++--
src/pages/profile/utils/utils.ts | 23 +++++++++++++++
8 files changed, 62 insertions(+), 53 deletions(-)
create mode 100644 src/assets/images/profile/check-status.svg
rename src/pages/profile/{ => utils}/types.ts (87%)
create mode 100644 src/pages/profile/utils/utils.ts
diff --git a/src/assets/images/profile/check-status.svg b/src/assets/images/profile/check-status.svg
new file mode 100644
index 00000000..5144adb7
--- /dev/null
+++ b/src/assets/images/profile/check-status.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/components/profile-components/order-status/index.tsx b/src/components/profile-components/order-status/index.tsx
index 398e8a91..bdc14a9e 100644
--- a/src/components/profile-components/order-status/index.tsx
+++ b/src/components/profile-components/order-status/index.tsx
@@ -1,19 +1,14 @@
-import styles from './order-status.module.scss';
+import clsx from 'clsx';
import doneIcon from '@images/profile/home-status.svg';
import canceledIcon from '@images/profile/cancel-status.svg';
import orderedIcon from '@images/profile/payment-status.svg';
import deliveredIcon from '@images/profile/car-status.svg';
-import clsx from 'clsx';
+import checkedIcon from '@images/profile/check-status.svg';
+import { OrderStatusType } from '@pages/profile/utils/types';
+import styles from './order-status.module.scss';
type Props = {
- status?:
- | 'Ordered'
- | 'In processing'
- | 'Collecting'
- | 'Gathered'
- | 'In delivering'
- | 'Delivered'
- | 'Completed';
+ status?: OrderStatusType;
};
const statusObj = {
@@ -34,12 +29,12 @@ const statusObj = {
},
Ordered: {
text: 'Оформлен',
- image: orderedIcon,
- style: styles.ordered,
+ image: checkedIcon,
+ style: styles.checked,
},
Gathered: { text: '', image: '', style: '' },
'In processing': { text: '', image: '', style: '' },
- 'In delivering': { text: '', image: '', style: '' },
+ 'In delivering': { text: 'Оплачен', image: orderedIcon, style: styles.ordered },
Collecting: { text: '', image: '', style: '' },
};
diff --git a/src/components/profile-components/order-status/order-status.module.scss b/src/components/profile-components/order-status/order-status.module.scss
index cce1c642..6310459a 100644
--- a/src/components/profile-components/order-status/order-status.module.scss
+++ b/src/components/profile-components/order-status/order-status.module.scss
@@ -36,6 +36,10 @@
border: 1px solid $tip-color;
}
+.checked {
+ border: 1px solid $green-primary-700;
+}
+
.icon {
line-height: 0;
width: 24px;
diff --git a/src/components/profile-components/profile-order-mobile/index.tsx b/src/components/profile-components/profile-order-mobile/index.tsx
index d6443934..b1aca348 100644
--- a/src/components/profile-components/profile-order-mobile/index.tsx
+++ b/src/components/profile-components/profile-order-mobile/index.tsx
@@ -2,7 +2,8 @@ import clsx from 'clsx';
import ProductCard from '@components/product-card';
import PaymentButton from '@components/payment-button';
import OrderStatus from '../order-status';
-import type { CommonOrder, Product } from '@pages/profile/types';
+import type { CommonOrder, Product } from '@pages/profile/utils/types';
+import { getDeliveryMethodRu, getPaymentMethodRu } from '@pages/profile/utils/utils';
import styles from './profile-order-mobile.module.scss';
type Props = {
@@ -26,19 +27,6 @@ const ProfileOrderMobile = ({
products,
} = order;
- let payment_method_ru =
- payment_method === 'Payment at the point of delivery'
- ? 'Банковской картой'
- : 'Наличные';
-
- let delivery_method_ru;
- if (delivery_method === 'Point of delivery') {
- delivery_method_ru = 'Самовывоз';
- } else {
- delivery_method_ru = 'Курьером';
- payment_method_ru += 'курьеру';
- }
-
const date = ordering_date && new Date(ordering_date).toLocaleDateString();
return (
Способ получения:
- {delivery_method_ru}
+
+ {getDeliveryMethodRu(delivery_method)}
+
Способ оплаты:
- {payment_method_ru}
+
+ {getPaymentMethodRu(payment_method)}
+
{`${total_price} руб.`}
- {order.is_paid ? (
-
+ {order.is_paid || payment_method !== 'Online' ? (
+
) : (
)}
diff --git a/src/components/profile-components/profile-order/index.tsx b/src/components/profile-components/profile-order/index.tsx
index e98e2c76..44af9dcf 100644
--- a/src/components/profile-components/profile-order/index.tsx
+++ b/src/components/profile-components/profile-order/index.tsx
@@ -3,7 +3,8 @@ import clsx from 'clsx';
import PaymentButton from '@components/payment-button';
import OrderStatus from '../order-status';
import { translateMeasureUnit } from '@utils/utils';
-import type { CommonOrder, Product } from '@pages/profile/types';
+import type { CommonOrder, Product } from '@pages/profile/utils/types';
+import { getDeliveryMethodRu, getPaymentMethodRu } from '@pages/profile/utils/utils';
import styles from './profile-order.module.scss';
type Props = {
@@ -37,19 +38,6 @@ const ProfileOrder = ({
return `${amount} ${measureUnit}`;
};
- let payment_method_ru =
- payment_method === 'Payment at the point of delivery'
- ? 'Банковской картой'
- : 'Наличные';
-
- let delivery_method_ru;
- if (delivery_method === 'Point of delivery') {
- delivery_method_ru = 'Самовывоз';
- } else {
- delivery_method_ru = 'Курьером';
- payment_method_ru += 'курьеру';
- }
-
const date = ordering_date && new Date(ordering_date).toLocaleDateString();
return (
<>
@@ -118,12 +106,16 @@ const ProfileOrder = ({
-
{`Способ оплаты: ${payment_method_ru}`}
-
{`Способ получения: ${delivery_method_ru}`}
+
{`Оплата: ${getPaymentMethodRu(
+ payment_method
+ )}`}
+
{`Получение: ${getDeliveryMethodRu(
+ delivery_method
+ )}`}
- {order.is_paid ? (
-
+ {order.is_paid || payment_method !== 'Online' ? (
+
) : (
)}
diff --git a/src/pages/profile/profile-orders/index.tsx b/src/pages/profile/profile-orders/index.tsx
index 1ec600d1..52e54461 100644
--- a/src/pages/profile/profile-orders/index.tsx
+++ b/src/pages/profile/profile-orders/index.tsx
@@ -4,7 +4,7 @@ import ProfileOrder from '@components/profile-components/profile-order';
import ProfileOrderMobile from '@components/profile-components/profile-order-mobile';
import ReturnBackButton from '@components/profile-components/return-back-button';
import { useProfile } from '@hooks/use-profile';
-import type { CommonOrder } from '../types';
+import type { CommonOrder } from '../utils/types';
import styles from './profile-orders.module.scss';
export default function ProfileOrders() {
diff --git a/src/pages/profile/types.ts b/src/pages/profile/utils/types.ts
similarity index 87%
rename from src/pages/profile/types.ts
rename to src/pages/profile/utils/types.ts
index f0023e56..cbd5cd17 100644
--- a/src/pages/profile/types.ts
+++ b/src/pages/profile/utils/types.ts
@@ -1,4 +1,4 @@
-type OrderStatusType =
+export type OrderStatusType =
| 'Ordered'
| 'In processing'
| 'Collecting'
@@ -27,8 +27,8 @@ export type CommonOrder = {
order_number?: string;
ordering_date?: string;
total_price?: string;
- payment_method?: string;
- delivery_method?: string;
+ payment_method: string;
+ delivery_method: string;
status?: OrderStatusType;
products: Array<{ product: Product; quantity: number }> | Product[];
};
diff --git a/src/pages/profile/utils/utils.ts b/src/pages/profile/utils/utils.ts
new file mode 100644
index 00000000..0deddb57
--- /dev/null
+++ b/src/pages/profile/utils/utils.ts
@@ -0,0 +1,23 @@
+export const getPaymentMethodRu = (paymentMethod: string) => {
+ switch (paymentMethod) {
+ case 'Payment at the point of delivery':
+ return 'при самовывозе';
+ case 'In getting by cash':
+ return 'курьеру';
+ case 'Online':
+ return 'онлайн';
+ default:
+ return '';
+ }
+};
+
+export const getDeliveryMethodRu = (deliveryMethod: string) => {
+ switch (deliveryMethod) {
+ case 'Point of delivery':
+ return 'самовывоз';
+ case 'By courier':
+ return 'курьером';
+ default:
+ return '';
+ }
+};
From 636713e6070438601e6039ace99efdaabad98173 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 23:19:19 +0300
Subject: [PATCH 122/141] feat: add ScrollToAnchorHash component
---
.../scroll-to-anchor-hash/index.tsx | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
create mode 100644 src/components/scroll-to-anchor-hash/index.tsx
diff --git a/src/components/scroll-to-anchor-hash/index.tsx b/src/components/scroll-to-anchor-hash/index.tsx
new file mode 100644
index 00000000..cd6b0af0
--- /dev/null
+++ b/src/components/scroll-to-anchor-hash/index.tsx
@@ -0,0 +1,28 @@
+import { useEffect, useRef } from 'react';
+import { useLocation } from 'react-router-dom';
+
+function ScrollToAnchorHash() {
+ const location = useLocation();
+ const lastHash = useRef('');
+
+ // listen to location change using useEffect with location as dependency
+ // https://jasonwatmore.com/react-router-v6-listen-to-location-route-change-without-history-listen
+ useEffect(() => {
+ if (location.hash) {
+ lastHash.current = location.hash.slice(1); // safe hash for further use after navigation
+ }
+
+ if (lastHash.current && document.getElementById(lastHash.current)) {
+ setTimeout(() => {
+ document
+ .getElementById(lastHash.current)
+ ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ lastHash.current = '';
+ }, 100);
+ }
+ }, [location]);
+
+ return null;
+}
+
+export default ScrollToAnchorHash;
From d6fb2ad547903f442878d491864790c3a70d9d34 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 23:20:57 +0300
Subject: [PATCH 123/141] feat: add id to top-selling section
---
src/pages/home/index.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx
index 4721f3fe..76df54e8 100644
--- a/src/pages/home/index.tsx
+++ b/src/pages/home/index.tsx
@@ -8,10 +8,12 @@ import TopSellingThisWeek from '@components/top-selling-this-week';
import AboutCompany from '@components/about-company/index.tsx';
import OurBlock from '@components/our-block';
import CatalogPromo from '@components/catalog-promo';
+import ScrollToAnchorHash from '@components/scroll-to-anchor-hash';
const Home: React.FC = () => {
return (
+
@@ -32,7 +34,7 @@ const Home: React.FC = () => {
-
+
From a9ed03cf726c10eaa0f59808675a17bdd2143cc7 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 23:25:14 +0300
Subject: [PATCH 124/141] feat: add function to close burger on links click
---
src/components/navigation-bar/index.tsx | 46 +++++++++++++++++++++----
src/layouts/header/index.tsx | 2 +-
2 files changed, 40 insertions(+), 8 deletions(-)
diff --git a/src/components/navigation-bar/index.tsx b/src/components/navigation-bar/index.tsx
index 6e4719da..042c6891 100644
--- a/src/components/navigation-bar/index.tsx
+++ b/src/components/navigation-bar/index.tsx
@@ -1,36 +1,68 @@
import React from 'react';
-import styles from './navigation-bar.module.scss';
import CustomNavLink from '@components/custom-nav-link';
+import { usePopup } from '@hooks/use-popup';
+import { useAuth } from '@hooks/use-auth';
+import styles from './navigation-bar.module.scss';
interface NavigationBarProps {
isOpen: boolean;
+ onClick: () => void;
}
-const NavigationBar: React.FC = ({ isOpen }) => {
+const NavigationBar: React.FC = ({ isOpen, onClick }) => {
+ const { isLoggedIn } = useAuth();
+ const { handleOpenPopup } = usePopup();
+
+ const handleClick = () => {
+ onClick();
+ handleOpenPopup('openPopupLogin');
+ };
+
return (
- Войти
+ {!isLoggedIn && (
+
+ Войти
+
+ )}
Каталог
-
+
О нас
-
+
Товары недели
-
+
Рецепты
-
+
Контакты
diff --git a/src/layouts/header/index.tsx b/src/layouts/header/index.tsx
index 83a8924c..c3917524 100644
--- a/src/layouts/header/index.tsx
+++ b/src/layouts/header/index.tsx
@@ -37,7 +37,7 @@ const Header: React.FC = () => {
-
+
);
From 234ebe0121bceb6710916d49fe889b76ac1ec082 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 23:25:54 +0300
Subject: [PATCH 125/141] fix: styles on burger
---
.../navigation-bar/navigation-bar.module.scss | 22 +++++++++++--------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/src/components/navigation-bar/navigation-bar.module.scss b/src/components/navigation-bar/navigation-bar.module.scss
index 13c519bf..7285e556 100644
--- a/src/components/navigation-bar/navigation-bar.module.scss
+++ b/src/components/navigation-bar/navigation-bar.module.scss
@@ -19,14 +19,14 @@
}
}
-.burger-icon {
- background: none;
- border: none;
- font-size: 24px;
- color: #fff;
- cursor: pointer;
- transition: color 0.7s;
-}
+// .burger-icon {
+// background: none;
+// border: none;
+// font-size: 24px;
+// color: #fff;
+// cursor: pointer;
+// transition: color 0.7s;
+// }
.navigation-bar__nav {
display: flex;
@@ -72,7 +72,7 @@
align-items: center;
flex-direction: column;
list-style: none;
- margin: 26px 0;
+ margin: 26px 0 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
@@ -111,6 +111,10 @@
background: var(--inactive, #b8b8b8);
}
+.navigation-bar__link:last-of-type::after {
+ width: 0;
+}
+
.navigation-bar__link_active {
color: $green-primary-700;
background-color: rgb(108 108 108 / 25%);
From 9a62b6133ec759bd92d071e9363fe91e31b6bb04 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 23:28:01 +0300
Subject: [PATCH 126/141] refactor: delete comments
---
src/components/navigation-bar/navigation-bar.module.scss | 9 ---------
src/components/scroll-to-anchor-hash/index.tsx | 4 +---
2 files changed, 1 insertion(+), 12 deletions(-)
diff --git a/src/components/navigation-bar/navigation-bar.module.scss b/src/components/navigation-bar/navigation-bar.module.scss
index 7285e556..15ec8966 100644
--- a/src/components/navigation-bar/navigation-bar.module.scss
+++ b/src/components/navigation-bar/navigation-bar.module.scss
@@ -19,15 +19,6 @@
}
}
-// .burger-icon {
-// background: none;
-// border: none;
-// font-size: 24px;
-// color: #fff;
-// cursor: pointer;
-// transition: color 0.7s;
-// }
-
.navigation-bar__nav {
display: flex;
flex-direction: column;
diff --git a/src/components/scroll-to-anchor-hash/index.tsx b/src/components/scroll-to-anchor-hash/index.tsx
index cd6b0af0..8e9824ed 100644
--- a/src/components/scroll-to-anchor-hash/index.tsx
+++ b/src/components/scroll-to-anchor-hash/index.tsx
@@ -5,11 +5,9 @@ function ScrollToAnchorHash() {
const location = useLocation();
const lastHash = useRef('');
- // listen to location change using useEffect with location as dependency
- // https://jasonwatmore.com/react-router-v6-listen-to-location-route-change-without-history-listen
useEffect(() => {
if (location.hash) {
- lastHash.current = location.hash.slice(1); // safe hash for further use after navigation
+ lastHash.current = location.hash.slice(1);
}
if (lastHash.current && document.getElementById(lastHash.current)) {
From 9d7295ac2d0cf387c379afef89fedc703a650288 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 10 Jan 2024 12:12:33 +0300
Subject: [PATCH 127/141] feat: make NavLink work with anchor id
---
src/components/custom-nav-link/index.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/components/custom-nav-link/index.tsx b/src/components/custom-nav-link/index.tsx
index a26ee13f..009c383c 100644
--- a/src/components/custom-nav-link/index.tsx
+++ b/src/components/custom-nav-link/index.tsx
@@ -17,7 +17,8 @@ const CustomNavLink: React.FC = ({
onClick,
}) => {
const location = useLocation();
- const isActive = location.pathname === to;
+ const pathnameWithHash = location.pathname.concat(location.hash);
+ const isActive = pathnameWithHash === to;
return (
Date: Wed, 10 Jan 2024 12:13:35 +0300
Subject: [PATCH 128/141] fix: corrected styles
---
src/components/navigation-bar/index.tsx | 4 ++++
.../navigation-bar/navigation-bar.module.scss | 19 +++++++------------
2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/src/components/navigation-bar/index.tsx b/src/components/navigation-bar/index.tsx
index 042c6891..088d69e3 100644
--- a/src/components/navigation-bar/index.tsx
+++ b/src/components/navigation-bar/index.tsx
@@ -41,6 +41,7 @@ const NavigationBar: React.FC = ({ isOpen, onClick }) => {
onClick={onClick}
to={'/'}
className={`${styles['navigation-bar__link']}`}
+ classNameActive={`${styles['navigation-bar__link_active']}`}
>
О нас
@@ -48,6 +49,7 @@ const NavigationBar: React.FC = ({ isOpen, onClick }) => {
onClick={onClick}
to="/#topSelling"
className={`${styles['navigation-bar__link']}`}
+ classNameActive={`${styles['navigation-bar__link_active']}`}
>
Товары недели
@@ -55,6 +57,7 @@ const NavigationBar: React.FC = ({ isOpen, onClick }) => {
onClick={onClick}
to={'/recipes'}
className={`${styles['navigation-bar__link']}`}
+ classNameActive={`${styles['navigation-bar__link_active']}`}
>
Рецепты
@@ -62,6 +65,7 @@ const NavigationBar: React.FC = ({ isOpen, onClick }) => {
onClick={onClick}
to={'/contacts'}
className={`${styles['navigation-bar__link']}`}
+ classNameActive={`${styles['navigation-bar__link_active']}`}
>
Контакты
diff --git a/src/components/navigation-bar/navigation-bar.module.scss b/src/components/navigation-bar/navigation-bar.module.scss
index 15ec8966..7c682eb6 100644
--- a/src/components/navigation-bar/navigation-bar.module.scss
+++ b/src/components/navigation-bar/navigation-bar.module.scss
@@ -26,10 +26,9 @@
width: 100%;
overflow: hidden;
max-height: 0;
- transition: max-height 0.7s ease-out;
+ transition: max-height 0.4s ease-out;
border-radius: 0 0 8px 8px;
- background: #f0f0f0;
- box-shadow: 0 1px 1px 0 rgb(97 99 96 / 30%);
+ box-shadow: 0 10px 10px 0 rgb(97 99 96 / 50%);
}
.navigation-bar__button {
@@ -48,7 +47,7 @@
pointer-events: auto;
transition: 0.3s;
cursor: pointer;
- margin-top: 28px;
+ margin: 20px 0 30px;
font-size: 13px;
font-weight: 400;
line-height: 140%;
@@ -63,7 +62,7 @@
align-items: center;
flex-direction: column;
list-style: none;
- margin: 26px 0 0;
+ margin: 0;
width: 100%;
padding: 0 20px;
box-sizing: border-box;
@@ -73,11 +72,11 @@
position: relative;
display: flex;
justify-content: center;
- align-items: flex-start;
+ align-items: center;
text-decoration: none;
padding: 10px;
height: 48px;
- transition: background-color 0.7s;
+ transition: background-color 0.4s;
width: 100%;
box-sizing: border-box;
color: var(--active, #1a1a1a);
@@ -86,10 +85,6 @@
font-style: normal;
font-weight: 400;
line-height: 140%;
-
- &:not(:first-child) {
- margin-top: 10px;
- }
}
.navigation-bar__link::after {
@@ -108,5 +103,5 @@
.navigation-bar__link_active {
color: $green-primary-700;
- background-color: rgb(108 108 108 / 25%);
+ background-color: $background-color-input-field;
}
From d6b868b53ea755b8565bf027fa3b84182242d84f Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 10 Jan 2024 12:18:36 +0300
Subject: [PATCH 129/141] feat: add link to cart in burger menu
---
src/components/navigation-bar/index.tsx | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/components/navigation-bar/index.tsx b/src/components/navigation-bar/index.tsx
index 088d69e3..073ccafe 100644
--- a/src/components/navigation-bar/index.tsx
+++ b/src/components/navigation-bar/index.tsx
@@ -37,6 +37,14 @@ const NavigationBar: React.FC = ({ isOpen, onClick }) => {
>
Каталог
+
+ Корзина
+
Date: Wed, 10 Jan 2024 13:39:25 +0300
Subject: [PATCH 130/141] feat: closing burger by clicking outside or by Escape
---
src/components/navigation-bar/index.tsx | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/src/components/navigation-bar/index.tsx b/src/components/navigation-bar/index.tsx
index 073ccafe..23400bd6 100644
--- a/src/components/navigation-bar/index.tsx
+++ b/src/components/navigation-bar/index.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
import CustomNavLink from '@components/custom-nav-link';
import { usePopup } from '@hooks/use-popup';
import { useAuth } from '@hooks/use-auth';
@@ -12,14 +12,37 @@ interface NavigationBarProps {
const NavigationBar: React.FC = ({ isOpen, onClick }) => {
const { isLoggedIn } = useAuth();
const { handleOpenPopup } = usePopup();
+ const burgerRef = useRef(null);
const handleClick = () => {
onClick();
handleOpenPopup('openPopupLogin');
};
+ const closeBurger = (e: MouseEvent) => {
+ if (isOpen && !burgerRef.current?.contains(e.target as Node)) {
+ onClick();
+ }
+ };
+ const closeBurgerOnEsc = (e: KeyboardEvent) => {
+ console.log(e);
+ if (isOpen && e.key === 'Escape') {
+ onClick();
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener('pointerdown', closeBurger);
+ document.addEventListener('keydown', closeBurgerOnEsc);
+ return () => {
+ document.removeEventListener('pointerdown', closeBurger);
+ document.removeEventListener('keydown', closeBurgerOnEsc);
+ };
+ });
+
return (
From b24053d6f655f410c3b2401d7fade661080f09c8 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 10 Jan 2024 21:02:57 +0300
Subject: [PATCH 131/141] feat: add new components
---
src/assets/images/circle-not-ok.svg | 4 ++
src/pages/payment/index.tsx | 22 ++++++++
src/pages/payment/payment-bad/index.tsx | 29 +++++++++++
.../payment-bad/payment-bad.module.scss | 52 +++++++++++++++++++
src/pages/payment/payment.module.scss | 33 ++++++++++++
5 files changed, 140 insertions(+)
create mode 100644 src/assets/images/circle-not-ok.svg
create mode 100644 src/pages/payment/index.tsx
create mode 100644 src/pages/payment/payment-bad/index.tsx
create mode 100644 src/pages/payment/payment-bad/payment-bad.module.scss
create mode 100644 src/pages/payment/payment.module.scss
diff --git a/src/assets/images/circle-not-ok.svg b/src/assets/images/circle-not-ok.svg
new file mode 100644
index 00000000..eed08ad3
--- /dev/null
+++ b/src/assets/images/circle-not-ok.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/pages/payment/index.tsx b/src/pages/payment/index.tsx
new file mode 100644
index 00000000..f9f2703f
--- /dev/null
+++ b/src/pages/payment/index.tsx
@@ -0,0 +1,22 @@
+import React, { ReactNode } from 'react';
+import OurBlock from '@components/our-block';
+import styles from './payment.module.scss';
+
+type PaymentProps = {
+ children?: ReactNode;
+ adviceText: string;
+};
+
+const Payment: React.FC = ({ children, adviceText }) => {
+ return (
+
+ );
+};
+
+export default Payment;
diff --git a/src/pages/payment/payment-bad/index.tsx b/src/pages/payment/payment-bad/index.tsx
new file mode 100644
index 00000000..573763eb
--- /dev/null
+++ b/src/pages/payment/payment-bad/index.tsx
@@ -0,0 +1,29 @@
+import Payment from '..';
+import PaymentButton from '@components/payment-button';
+import failIcon from '@images/circle-not-ok.svg';
+import styles from './payment-bad.module.scss';
+
+const PaymentBad = () => {
+ const text =
+ 'Пока решается проблема с оплатой, вы можете ознакомиться с рецептами из нашего блога';
+
+ return (
+
+
+
+ Платеж не прошел
+
+ Возможно это был временный сбой — просто попробуйте снова
+
+
+
+
+ );
+};
+
+export default PaymentBad;
diff --git a/src/pages/payment/payment-bad/payment-bad.module.scss b/src/pages/payment/payment-bad/payment-bad.module.scss
new file mode 100644
index 00000000..a006a8b2
--- /dev/null
+++ b/src/pages/payment/payment-bad/payment-bad.module.scss
@@ -0,0 +1,52 @@
+@use '@scss/variables' as *;
+
+.image {
+ width: 66px;
+ height: 66px;
+}
+
+.container {
+ padding: 108px 128px 100px;
+ max-width: 1024px;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ font-family: $ubuntu-font;
+
+ @media screen and (width <= 768px) {
+ padding: 40px 20px;
+ }
+}
+
+.title {
+ color: $active-text-color;
+ font-size: 36px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 140%;
+ text-align: center;
+ padding-top: 12px;
+
+ @media screen and (width <= 768px) {
+ font-size: 24px;
+ }
+}
+
+.info {
+ color: $active-text-color;
+ text-align: center;
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 140%;
+ margin: 0;
+ padding: 20px 0 40px;
+ max-width: 340px;
+
+ @media screen and (width <= 768px) {
+ font-size: 14px;
+ max-width: 225px;
+ }
+}
diff --git a/src/pages/payment/payment.module.scss b/src/pages/payment/payment.module.scss
new file mode 100644
index 00000000..66982e7e
--- /dev/null
+++ b/src/pages/payment/payment.module.scss
@@ -0,0 +1,33 @@
+@use '@scss/variables' as *;
+
+.payment {
+ display: flex;
+ flex-direction: column;
+ font-family: $ubuntu-font;
+}
+
+.payment__ourBlock {
+ padding: 100px 128px 0;
+ max-width: 1024px;
+
+ @media screen and (width <= 768px) {
+ padding: 40px 20px 0;
+ }
+
+ @media screen and (width <= 550px) {
+ padding-right: 0;
+ }
+}
+
+.payment__advice {
+ padding: 0 0 44px;
+ margin: 0;
+ font-size: 30px;
+ font-weight: 700;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ padding: 0 0 16px;
+ font-size: 18px;
+ }
+}
From d1b5b001e2150a3125bd1e828612fc13c107898f Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 10 Jan 2024 21:05:55 +0300
Subject: [PATCH 132/141] feat: add route for bad payment page
---
src/App.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/App.tsx b/src/App.tsx
index 34f3cc7a..f49a4337 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,6 +24,7 @@ import RecipeList from '@pages/recipe-list/index.tsx';
import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
+import PaymentBad from '@pages/payment/payment-bad/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -63,6 +64,7 @@ function App() {
} />
} />
+ } />
} />
From 3daf5e6647536945ba750f17472e9e607527a8b4 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 10 Jan 2024 21:08:31 +0300
Subject: [PATCH 133/141] fix: improve button style
---
src/components/Button/button.module.scss | 4 ++--
src/components/payment-button/index.tsx | 9 +++++++--
src/pages/checkout/checkout.module.scss | 2 +-
3 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/components/Button/button.module.scss b/src/components/Button/button.module.scss
index 98d1fe77..64a78094 100644
--- a/src/components/Button/button.module.scss
+++ b/src/components/Button/button.module.scss
@@ -14,7 +14,7 @@
color: #fff;
font-family: $ubuntu-font;
font-size: 14px;
- font-weight: 600;
+ font-weight: 400;
line-height: 20px;
cursor: pointer;
@@ -43,7 +43,7 @@
color: #fff;
font-family: $ubuntu-font;
font-size: 14px;
- font-weight: 600;
+ font-weight: 400;
line-height: 20px;
cursor: pointer;
}
diff --git a/src/components/payment-button/index.tsx b/src/components/payment-button/index.tsx
index ccd34753..b7cc8b1d 100644
--- a/src/components/payment-button/index.tsx
+++ b/src/components/payment-button/index.tsx
@@ -6,9 +6,14 @@ import styles from './payment-button.module.scss';
type PaymentButtonProps = {
orderId: number;
isCheckoutPage?: boolean;
+ buttonText?: string;
};
-const PaymentButton: React.FC = ({ orderId, isCheckoutPage }) => {
+const PaymentButton: React.FC = ({
+ orderId,
+ isCheckoutPage,
+ buttonText,
+}) => {
const [isDisabled, setIsDisabled] = useState(false);
const [paymentError, setPaymentError] = useState('');
@@ -32,7 +37,7 @@ const PaymentButton: React.FC = ({ orderId, isCheckoutPage }
Date: Wed, 10 Jan 2024 21:35:25 +0300
Subject: [PATCH 134/141] feat: add PaymentGood component
---
src/pages/payment/payment-good/index.tsx | 34 ++++++++++++
.../payment-good/payment-good.module.scss | 52 +++++++++++++++++++
2 files changed, 86 insertions(+)
create mode 100644 src/pages/payment/payment-good/index.tsx
create mode 100644 src/pages/payment/payment-good/payment-good.module.scss
diff --git a/src/pages/payment/payment-good/index.tsx b/src/pages/payment/payment-good/index.tsx
new file mode 100644
index 00000000..a03a9386
--- /dev/null
+++ b/src/pages/payment/payment-good/index.tsx
@@ -0,0 +1,34 @@
+import { Link } from 'react-router-dom';
+import Payment from '..';
+import OrderStatusTracker from '@components/order-status-tracker';
+import { useAuth } from '@hooks/use-auth';
+import successIcon from '@images/circle-ok-min.svg';
+import styles from './payment-good.module.scss';
+
+const PaymentGood = () => {
+ const { isLoggedIn } = useAuth();
+ const text = 'Пока вы ждёте заказ, можете ознакомиться с рецептами из нашего блога';
+
+ return (
+
+
+
+ Успешно!
+ {isLoggedIn ? (
+
+ Ваш платеж принят в обработку. Отслеживать его вы можете в
+ личном кабинете
+
+ ) : (
+
+ Ваш платеж принят в обработку. История заказов доступна для зарегистрированных
+ пользователей.
+
+ )}
+
+
+
+ );
+};
+
+export default PaymentGood;
diff --git a/src/pages/payment/payment-good/payment-good.module.scss b/src/pages/payment/payment-good/payment-good.module.scss
new file mode 100644
index 00000000..62ff02cf
--- /dev/null
+++ b/src/pages/payment/payment-good/payment-good.module.scss
@@ -0,0 +1,52 @@
+@use '@scss/variables' as *;
+
+.image {
+ width: 66px;
+ height: 66px;
+}
+
+.container {
+ padding: 108px 128px 100px;
+ max-width: 1024px;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ font-family: $ubuntu-font;
+
+ @media screen and (width <= 768px) {
+ padding: 40px 20px;
+ }
+}
+
+.title {
+ color: $active-text-color;
+ font-size: 36px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 140%;
+ text-align: center;
+ padding-top: 12px;
+
+ @media screen and (width <= 768px) {
+ font-size: 24px;
+ }
+}
+
+.info {
+ color: $active-text-color;
+ text-align: center;
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 140%;
+ margin: 0;
+ padding: 20px 0 40px;
+ max-width: 450px;
+
+ @media screen and (width <= 768px) {
+ font-size: 14px;
+ max-width: 225px;
+ }
+}
From d5231c1734e39420fb3447be114ed744437db06e Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 10 Jan 2024 21:36:17 +0300
Subject: [PATCH 135/141] feat: add route for PaymentGood page
---
src/App.tsx | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/App.tsx b/src/App.tsx
index f49a4337..6071a0e0 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -25,6 +25,7 @@ import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
import PaymentBad from '@pages/payment/payment-bad/index.tsx';
+import PaymentGood from '@pages/payment/payment-good/index.tsx';
// импорт временных массивов для отображения каталогов и продуктов
// временное решение для верстки, потом удалить
@@ -65,6 +66,7 @@ function App() {
} />
} />
} />
+ } />
} />
From 6136e319fad2d5cdd03316418c5fdee74e984282 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 11 Jan 2024 14:20:49 +0300
Subject: [PATCH 136/141] feat: add payment api request
---
src/App.tsx | 22 ++++----
src/components/payment/payment.module.scss | 33 ++++++++++++
.../payment-results.module.scss | 54 +++++++++++++++++++
src/services/api.ts | 12 +++++
src/services/generated-api/data-contracts.ts | 4 ++
5 files changed, 112 insertions(+), 13 deletions(-)
create mode 100644 src/components/payment/payment.module.scss
create mode 100644 src/pages/payment-results/payment-results.module.scss
diff --git a/src/App.tsx b/src/App.tsx
index 6071a0e0..83428602 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,17 +24,7 @@ import RecipeList from '@pages/recipe-list/index.tsx';
import Agreement from '@pages/agreement/index.tsx';
import DeliveryConditions from '@pages/delivery-conditions/index.tsx';
import CheckoutSuccess from '@pages/checkout/checkout-success/index.tsx';
-import PaymentBad from '@pages/payment/payment-bad/index.tsx';
-import PaymentGood from '@pages/payment/payment-good/index.tsx';
-
-// импорт временных массивов для отображения каталогов и продуктов
-// временное решение для верстки, потом удалить
-
-// import { mainPageBlockLinks, products } from './data/dataExamples.ts';
-
-// примеры рендера каталогов
-//
-//
+import PaymentResults from '@pages/payment-results/index.tsx';
function App() {
const { isLoggedIn } = useAuth();
@@ -65,8 +55,14 @@ function App() {
} />
} />
- } />
- } />
+ }
+ />
+ }
+ />
} />
diff --git a/src/components/payment/payment.module.scss b/src/components/payment/payment.module.scss
new file mode 100644
index 00000000..66982e7e
--- /dev/null
+++ b/src/components/payment/payment.module.scss
@@ -0,0 +1,33 @@
+@use '@scss/variables' as *;
+
+.payment {
+ display: flex;
+ flex-direction: column;
+ font-family: $ubuntu-font;
+}
+
+.payment__ourBlock {
+ padding: 100px 128px 0;
+ max-width: 1024px;
+
+ @media screen and (width <= 768px) {
+ padding: 40px 20px 0;
+ }
+
+ @media screen and (width <= 550px) {
+ padding-right: 0;
+ }
+}
+
+.payment__advice {
+ padding: 0 0 44px;
+ margin: 0;
+ font-size: 30px;
+ font-weight: 700;
+ line-height: 140%;
+
+ @media screen and (width <= 768px) {
+ padding: 0 0 16px;
+ font-size: 18px;
+ }
+}
diff --git a/src/pages/payment-results/payment-results.module.scss b/src/pages/payment-results/payment-results.module.scss
new file mode 100644
index 00000000..1372d32d
--- /dev/null
+++ b/src/pages/payment-results/payment-results.module.scss
@@ -0,0 +1,54 @@
+@use '@scss/variables' as *;
+
+.image {
+ width: 66px;
+ height: 66px;
+}
+
+.container {
+ padding: 108px 128px 100px;
+ max-width: 1024px;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ font-family: $ubuntu-font;
+
+ @media screen and (width <= 768px) {
+ padding: 40px 20px;
+ }
+}
+
+.title {
+ color: $active-text-color;
+ font-size: 36px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 140%;
+ text-align: center;
+ padding-top: 12px;
+ max-width: 450px;
+
+ @media screen and (width <= 768px) {
+ font-size: 24px;
+ max-width: 225px;
+ }
+}
+
+.info {
+ color: $active-text-color;
+ text-align: center;
+ font-size: 20px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 140%;
+ margin: 0;
+ padding: 20px 0 40px;
+ max-width: 450px;
+
+ @media screen and (width <= 768px) {
+ font-size: 14px;
+ max-width: 225px;
+ }
+}
diff --git a/src/services/api.ts b/src/services/api.ts
index 96c6aeff..25364268 100644
--- a/src/services/api.ts
+++ b/src/services/api.ts
@@ -20,6 +20,7 @@ import type {
OrderPostAdd,
ReviewCreate,
ReviewUpdate,
+ Payment,
} from './generated-api/data-contracts';
import { BACKEND_URL } from '@data/constants.ts';
import Cookies from 'js-cookie';
@@ -671,6 +672,17 @@ class Api {
method: 'GET',
});
}
+
+ /* ----------------------------- Payment ------------------------------- */
+ paymentCheck(data: Payment) {
+ return this._request('order/successful_pay/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(data),
+ });
+ }
}
const api = new Api(BACKEND_URL);
diff --git a/src/services/generated-api/data-contracts.ts b/src/services/generated-api/data-contracts.ts
index 85fbae13..6516c134 100644
--- a/src/services/generated-api/data-contracts.ts
+++ b/src/services/generated-api/data-contracts.ts
@@ -1129,3 +1129,7 @@ export interface OrderCheck {
user: number;
ordered: boolean;
}
+
+export type Payment = {
+ stripe_session_id: string;
+};
From 7fd4600b7ef439d51214ce5e0c00a03134a0535e Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 11 Jan 2024 14:37:51 +0300
Subject: [PATCH 137/141] feat: add PaymentResults page
---
src/{pages => components}/payment/index.tsx | 0
src/data/constants.ts | 5 ++
src/pages/payment-results/index.tsx | 78 +++++++++++++++++++
src/pages/payment/payment-bad/index.tsx | 29 -------
.../payment-bad/payment-bad.module.scss | 52 -------------
src/pages/payment/payment-good/index.tsx | 34 --------
.../payment-good/payment-good.module.scss | 52 -------------
src/pages/payment/payment.module.scss | 33 --------
8 files changed, 83 insertions(+), 200 deletions(-)
rename src/{pages => components}/payment/index.tsx (100%)
create mode 100644 src/pages/payment-results/index.tsx
delete mode 100644 src/pages/payment/payment-bad/index.tsx
delete mode 100644 src/pages/payment/payment-bad/payment-bad.module.scss
delete mode 100644 src/pages/payment/payment-good/index.tsx
delete mode 100644 src/pages/payment/payment-good/payment-good.module.scss
delete mode 100644 src/pages/payment/payment.module.scss
diff --git a/src/pages/payment/index.tsx b/src/components/payment/index.tsx
similarity index 100%
rename from src/pages/payment/index.tsx
rename to src/components/payment/index.tsx
diff --git a/src/data/constants.ts b/src/data/constants.ts
index c767dc23..986682c6 100644
--- a/src/data/constants.ts
+++ b/src/data/constants.ts
@@ -39,3 +39,8 @@ export const popupInfoText = {
selectAgreement:
'Для оформления заказа необходимо согласие с условиями обработки персональных данных и условиями продажи.',
};
+
+export const textIfOrderPaid =
+ 'Пока вы ждёте заказ, можете ознакомиться с рецептами из нашего блога';
+export const textIfOrderNotPaid =
+ 'Пока решается проблема с оплатой, вы можете ознакомиться с рецептами из нашего блога';
diff --git a/src/pages/payment-results/index.tsx b/src/pages/payment-results/index.tsx
new file mode 100644
index 00000000..fc69e5c0
--- /dev/null
+++ b/src/pages/payment-results/index.tsx
@@ -0,0 +1,78 @@
+import { useEffect, useState } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import api from '@services/api';
+import successIcon from '@images/circle-ok-min.svg';
+import { textIfOrderPaid, textIfOrderNotPaid } from '@data/constants';
+import Payment from '../../components/payment';
+import OrderStatusTracker from '@components/order-status-tracker';
+import PaymentButton from '@components/payment-button';
+import Preloader from '@components/preloader';
+import failIcon from '@images/circle-not-ok.svg';
+import { useAuth } from '@hooks/use-auth';
+import styles from './payment-results.module.scss';
+
+type PaymentResultsProps = {
+ isPaid: boolean;
+};
+
+const PaymentResults: React.FC = ({ isPaid }) => {
+ const { isLoggedIn } = useAuth();
+ const [paimentInfo, setPaymentInfo] = useState({
+ order_id: '',
+ order_number: '',
+ stripe_session_id: '',
+ });
+ const textToShow = isPaid ? textIfOrderPaid : textIfOrderNotPaid;
+ const location = useLocation();
+ const paymentSessionIid = location.search.split('=')[1];
+
+ useEffect(() => {
+ const data = {
+ stripe_session_id: paymentSessionIid,
+ };
+
+ api.paymentCheck(data).then(setPaymentInfo);
+ }, [paymentSessionIid]);
+
+ if (!paimentInfo.order_number) return ;
+
+ return (
+
+ {isPaid ? (
+
+
+ Успешно!
+ {isLoggedIn ? (
+
+ Ваш платеж по заказу {paimentInfo.order_number} принят в обработку.
+ Отслеживать его вы можете в личном кабинете
+
+ ) : (
+
+ Ваш платеж по заказу {paimentInfo.order_number} принят в обработку. История
+ заказов доступна для зарегистрированных пользователей.
+
+ )}
+
+
+ ) : (
+
+
+
+ Ваш платеж по заказу {paimentInfo.order_number} отменён
+
+
+ Возможно это был временный сбой — просто попробуйте снова
+
+
+
+ )}
+
+ );
+};
+
+export default PaymentResults;
diff --git a/src/pages/payment/payment-bad/index.tsx b/src/pages/payment/payment-bad/index.tsx
deleted file mode 100644
index 573763eb..00000000
--- a/src/pages/payment/payment-bad/index.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import Payment from '..';
-import PaymentButton from '@components/payment-button';
-import failIcon from '@images/circle-not-ok.svg';
-import styles from './payment-bad.module.scss';
-
-const PaymentBad = () => {
- const text =
- 'Пока решается проблема с оплатой, вы можете ознакомиться с рецептами из нашего блога';
-
- return (
-
-
-
- Платеж не прошел
-
- Возможно это был временный сбой — просто попробуйте снова
-
-
-
-
- );
-};
-
-export default PaymentBad;
diff --git a/src/pages/payment/payment-bad/payment-bad.module.scss b/src/pages/payment/payment-bad/payment-bad.module.scss
deleted file mode 100644
index a006a8b2..00000000
--- a/src/pages/payment/payment-bad/payment-bad.module.scss
+++ /dev/null
@@ -1,52 +0,0 @@
-@use '@scss/variables' as *;
-
-.image {
- width: 66px;
- height: 66px;
-}
-
-.container {
- padding: 108px 128px 100px;
- max-width: 1024px;
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- font-family: $ubuntu-font;
-
- @media screen and (width <= 768px) {
- padding: 40px 20px;
- }
-}
-
-.title {
- color: $active-text-color;
- font-size: 36px;
- font-style: normal;
- font-weight: 700;
- line-height: 140%;
- text-align: center;
- padding-top: 12px;
-
- @media screen and (width <= 768px) {
- font-size: 24px;
- }
-}
-
-.info {
- color: $active-text-color;
- text-align: center;
- font-size: 20px;
- font-style: normal;
- font-weight: 400;
- line-height: 140%;
- margin: 0;
- padding: 20px 0 40px;
- max-width: 340px;
-
- @media screen and (width <= 768px) {
- font-size: 14px;
- max-width: 225px;
- }
-}
diff --git a/src/pages/payment/payment-good/index.tsx b/src/pages/payment/payment-good/index.tsx
deleted file mode 100644
index a03a9386..00000000
--- a/src/pages/payment/payment-good/index.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { Link } from 'react-router-dom';
-import Payment from '..';
-import OrderStatusTracker from '@components/order-status-tracker';
-import { useAuth } from '@hooks/use-auth';
-import successIcon from '@images/circle-ok-min.svg';
-import styles from './payment-good.module.scss';
-
-const PaymentGood = () => {
- const { isLoggedIn } = useAuth();
- const text = 'Пока вы ждёте заказ, можете ознакомиться с рецептами из нашего блога';
-
- return (
-
-
-
- Успешно!
- {isLoggedIn ? (
-
- Ваш платеж принят в обработку. Отслеживать его вы можете в
- личном кабинете
-
- ) : (
-
- Ваш платеж принят в обработку. История заказов доступна для зарегистрированных
- пользователей.
-
- )}
-
-
-
- );
-};
-
-export default PaymentGood;
diff --git a/src/pages/payment/payment-good/payment-good.module.scss b/src/pages/payment/payment-good/payment-good.module.scss
deleted file mode 100644
index 62ff02cf..00000000
--- a/src/pages/payment/payment-good/payment-good.module.scss
+++ /dev/null
@@ -1,52 +0,0 @@
-@use '@scss/variables' as *;
-
-.image {
- width: 66px;
- height: 66px;
-}
-
-.container {
- padding: 108px 128px 100px;
- max-width: 1024px;
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- font-family: $ubuntu-font;
-
- @media screen and (width <= 768px) {
- padding: 40px 20px;
- }
-}
-
-.title {
- color: $active-text-color;
- font-size: 36px;
- font-style: normal;
- font-weight: 700;
- line-height: 140%;
- text-align: center;
- padding-top: 12px;
-
- @media screen and (width <= 768px) {
- font-size: 24px;
- }
-}
-
-.info {
- color: $active-text-color;
- text-align: center;
- font-size: 20px;
- font-style: normal;
- font-weight: 400;
- line-height: 140%;
- margin: 0;
- padding: 20px 0 40px;
- max-width: 450px;
-
- @media screen and (width <= 768px) {
- font-size: 14px;
- max-width: 225px;
- }
-}
diff --git a/src/pages/payment/payment.module.scss b/src/pages/payment/payment.module.scss
deleted file mode 100644
index 66982e7e..00000000
--- a/src/pages/payment/payment.module.scss
+++ /dev/null
@@ -1,33 +0,0 @@
-@use '@scss/variables' as *;
-
-.payment {
- display: flex;
- flex-direction: column;
- font-family: $ubuntu-font;
-}
-
-.payment__ourBlock {
- padding: 100px 128px 0;
- max-width: 1024px;
-
- @media screen and (width <= 768px) {
- padding: 40px 20px 0;
- }
-
- @media screen and (width <= 550px) {
- padding-right: 0;
- }
-}
-
-.payment__advice {
- padding: 0 0 44px;
- margin: 0;
- font-size: 30px;
- font-weight: 700;
- line-height: 140%;
-
- @media screen and (width <= 768px) {
- padding: 0 0 16px;
- font-size: 18px;
- }
-}
From a63d1e0ecb6bb9fd408410030c0dc1f90a4c0a9d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Tue, 9 Jan 2024 20:25:27 +0300
Subject: [PATCH 138/141] fix: wrapping text on button
---
src/components/Button/button.module.scss | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/components/Button/button.module.scss b/src/components/Button/button.module.scss
index 64a78094..75f6be7d 100644
--- a/src/components/Button/button.module.scss
+++ b/src/components/Button/button.module.scss
@@ -135,6 +135,7 @@
width: 100%;
height: 50px;
cursor: pointer;
+ white-space: nowrap;
&:not(.green-border-button__active):hover {
color: white;
@@ -167,6 +168,7 @@
width: 100%;
height: 50px;
transition: 0.5s;
+ white-space: nowrap;
@media screen and (width <= 768px) {
font-size: 13px;
From 144ef39f80255788123de05e2029b58811ea873d Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Wed, 10 Jan 2024 14:56:26 +0300
Subject: [PATCH 139/141] feat: (rebase) add 404 route
---
src/App.tsx | 1 +
src/pages/category/index.tsx | 2 +-
src/pages/product/index.tsx | 4 ++--
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/App.tsx b/src/App.tsx
index 83428602..36706682 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -63,6 +63,7 @@ function App() {
path={'payment-cancelled'}
element={ }
/>
+ } />
} />
diff --git a/src/pages/category/index.tsx b/src/pages/category/index.tsx
index 277c9708..2877acde 100644
--- a/src/pages/category/index.tsx
+++ b/src/pages/category/index.tsx
@@ -44,7 +44,7 @@ const Category: React.FC = () => {
setCategoryObj(data.results[0]?.category);
})
.catch(() => {
- navigate('/упс');
+ navigate('/404');
})
.finally(() => {
setIsLoading(false);
diff --git a/src/pages/product/index.tsx b/src/pages/product/index.tsx
index 752b2eeb..03e149ae 100644
--- a/src/pages/product/index.tsx
+++ b/src/pages/product/index.tsx
@@ -47,7 +47,7 @@ const Product: React.FC = () => {
.then((data) => setProductItem(data))
.catch((error) => {
console.log(error);
- navigate('/упс');
+ navigate('/404');
});
api
.reviewsList(Number(id))
@@ -58,7 +58,7 @@ const Product: React.FC = () => {
.catch((err) => console.log(err));
} else {
console.log('ID is undefined');
- navigate('/упс');
+ navigate('/404');
}
}, [id, navigate]);
From a198d6c5eef6ed4c5358ba3e26bb5697bd9ba082 Mon Sep 17 00:00:00 2001
From: jsapro <77.3.77@mail.ru>
Date: Thu, 11 Jan 2024 15:45:36 +0300
Subject: [PATCH 140/141] fix: put description under image
---
.../recipes-components/recipe-info/index.tsx | 4 +++-
.../recipe-info/recipe-info.module.scss | 15 ++++++++-------
src/pages/recipe/index.tsx | 8 ++++++--
src/pages/recipe/recipe.module.scss | 4 ----
4 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/src/components/recipes-components/recipe-info/index.tsx b/src/components/recipes-components/recipe-info/index.tsx
index d50f9bb5..9a9617f9 100644
--- a/src/components/recipes-components/recipe-info/index.tsx
+++ b/src/components/recipes-components/recipe-info/index.tsx
@@ -4,6 +4,7 @@ import { useParams } from 'react-router';
type RecipeInfoProps = {
img: string;
+ description: string;
recipeNutrients: {
proteins: number;
fats: number;
@@ -12,12 +13,13 @@ type RecipeInfoProps = {
};
};
-const RecipeInfo: React.FC = ({ img, recipeNutrients }) => {
+const RecipeInfo: React.FC = ({ img, description, recipeNutrients }) => {
const { id } = useParams();
return (
+
{description}
Энергетическая ценность на порцию
diff --git a/src/components/recipes-components/recipe-info/recipe-info.module.scss b/src/components/recipes-components/recipe-info/recipe-info.module.scss
index cf3f408b..90772092 100644
--- a/src/components/recipes-components/recipe-info/recipe-info.module.scss
+++ b/src/components/recipes-components/recipe-info/recipe-info.module.scss
@@ -18,13 +18,6 @@
}
}
- &__description {
- margin: 0;
- font-size: 20px;
- line-height: 140%;
- margin-bottom: 20px;
- }
-
&__info {
margin: 0;
font-size: 22px;
@@ -32,4 +25,12 @@
line-height: 140%;
margin-bottom: 8px;
}
+
+ &__description {
+ margin: 0;
+ padding-top: 16px;
+ font-size: 20px;
+ font-weight: 400;
+ line-height: 140%;
+ }
}
diff --git a/src/pages/recipe/index.tsx b/src/pages/recipe/index.tsx
index 0f937f49..ec64ae67 100644
--- a/src/pages/recipe/index.tsx
+++ b/src/pages/recipe/index.tsx
@@ -182,13 +182,17 @@ const Recipe: React.FC = () => {
-
+
Инструкция приготовления
- {recipeByLines.map((line, index) => (
+ {recipeByLines.slice(2).map((line, index) => (
{line}
diff --git a/src/pages/recipe/recipe.module.scss b/src/pages/recipe/recipe.module.scss
index 2a66d6d5..b4feb160 100644
--- a/src/pages/recipe/recipe.module.scss
+++ b/src/pages/recipe/recipe.module.scss
@@ -90,10 +90,6 @@
margin-bottom: 25px;
}
- &__item:first-child {
- font-weight: 500;
- }
-
&__item:last-child {
margin: 0;
}
From a4ae0c885d0872f686fc1def5db90bb5b4e5071a Mon Sep 17 00:00:00 2001
From: kavabunga
Date: Mon, 8 Jan 2024 20:12:47 +0200
Subject: [PATCH 141/141] fix: fix error handling in shopping-cart
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Добавил проверки на наличие ожидаемой структуры ответа сервера для кейсов ошибок. В каких-то редких
случаях приложение не загружалось при 500-ой ошибке api коризны пользователя.
closes #232
---
src/contexts/cart-context.tsx | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/contexts/cart-context.tsx b/src/contexts/cart-context.tsx
index 78b06bfa..a765c477 100644
--- a/src/contexts/cart-context.tsx
+++ b/src/contexts/cart-context.tsx
@@ -114,7 +114,10 @@ export const CartProvider: React.FC = ({ children }) => {
})
.catch((error) => {
setError((prev) => {
- return { ...prev, loadCartData: error.errors[0].detail };
+ return {
+ ...prev,
+ loadCartData: error.errors?.[0]?.detail || 'Ошибка загрузки корзины покупок',
+ };
});
})
.finally(() => {
@@ -140,7 +143,10 @@ export const CartProvider: React.FC = ({ children }) => {
})
.catch((error) => {
setError((prev) => {
- return { ...prev, updateCart: error.errors[0].detail };
+ return {
+ ...prev,
+ updateCart: error.errors?.[0]?.detail || 'Ошибка обновления корзины покупок',
+ };
});
})
.finally(() => {
@@ -154,7 +160,11 @@ export const CartProvider: React.FC = ({ children }) => {
.catch((error) => {
if (error?.errors) {
setError((prev) => {
- return { ...prev, deleteCart: error.errors[0]?.detail };
+ return {
+ ...prev,
+ deleteCart:
+ error.errors[0]?.detail || 'Ошибка удаления товара из корзины покупок',
+ };
});
}
})
@@ -172,9 +182,12 @@ export const CartProvider: React.FC = ({ children }) => {
return { ...prev, clearCart: message };
});
})
- .catch(({ errors }) => {
+ .catch((error) => {
setError((prev) => {
- return { ...prev, clearCart: errors };
+ return {
+ ...prev,
+ clearCart: error.errors || 'Ошибка очистки корзины покупок',
+ };
});
});
};