Skip to content

Commit 518e2bf

Browse files
committed
Resolved conflicts and merged develop
2 parents 6f13f40 + f3f4d53 commit 518e2bf

File tree

8 files changed

+247
-3
lines changed

8 files changed

+247
-3
lines changed

services/web/src/actions/shopActions.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,21 @@ export const applyCouponAction = ({
115115
};
116116
};
117117

118+
export const newProductAction = ({
119+
accessToken,
120+
callback,
121+
...data
122+
}: ActionPayload) => {
123+
return {
124+
type: actionTypes.NEW_PRODUCT,
125+
payload: {
126+
accessToken,
127+
...data,
128+
callback,
129+
},
130+
};
131+
};
132+
118133
export const newCouponAction = ({
119134
accessToken,
120135
callback,

services/web/src/components/layout/layout.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ const AfterLogin: React.FC<AfterLoginProps> = ({
108108
return <Navigate to="/login" />;
109109
}
110110

111-
if (!componentRole || (componentRole && componentRole === userRole)) {
111+
if (
112+
!componentRole ||
113+
(componentRole && componentRole === userRole) ||
114+
userRole === roleTypes.ROLE_ADMIN
115+
) {
112116
return <Component />;
113117
}
114118

services/web/src/components/shop/shop.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
} from "@ant-design/icons";
3838
import {
3939
COUPON_CODE_REQUIRED,
40+
PRODUCT_DETAILS_REQUIRED,
4041
COUPON_AMOUNT_REQUIRED,
4142
} from "../../constants/messages";
4243
import { useNavigate } from "react-router-dom";
@@ -64,6 +65,11 @@ interface ShopProps extends PropsFromRedux {
6465
nextOffset: number | null;
6566
onOffsetChange: (offset: number | null) => void;
6667
onBuyProduct: (product: Product) => void;
68+
isNewProductFormOpen: boolean;
69+
setIsNewProductFormOpen: (isOpen: boolean) => void;
70+
newProductHasErrored: boolean;
71+
newProductErrorMessage: string;
72+
onNewProductFinish: (values: any) => void;
6773
isNewCouponFormOpen: boolean;
6874
setIsNewCouponFormOpen: (isOpen: boolean) => void;
6975
newCouponHasErrored: boolean;
@@ -116,6 +122,11 @@ const Shop: React.FC<ShopProps> = (props) => {
116122
nextOffset,
117123
onOffsetChange,
118124
onBuyProduct,
125+
isNewProductFormOpen,
126+
setIsNewProductFormOpen,
127+
newProductHasErrored,
128+
newProductErrorMessage,
129+
onNewProductFinish,
119130
isNewCouponFormOpen,
120131
setIsNewCouponFormOpen,
121132
newCouponHasErrored,
@@ -189,6 +200,23 @@ const Shop: React.FC<ShopProps> = (props) => {
189200
</Card>
190201
</Col>
191202
))}
203+
{role === roleTypes.ROLE_ADMIN && (
204+
<Col span={8} key="new-product-card">
205+
<Card
206+
className="new-product-card"
207+
onClick={() => setIsNewProductFormOpen(true)}
208+
cover={<PlusOutlined className="add-icon" />}
209+
>
210+
<Meta
211+
description={
212+
<div className="product-info product-price">
213+
Add Product
214+
</div>
215+
}
216+
/>
217+
</Card>
218+
</Col>
219+
)}
192220
</Row>
193221
<Row justify="center" className="pagination">
194222
<Button
@@ -240,6 +268,53 @@ const Shop: React.FC<ShopProps> = (props) => {
240268
</Form.Item>
241269
</Form>
242270
</Modal>
271+
<Modal
272+
title="Add New Product"
273+
open={isNewProductFormOpen}
274+
footer={null}
275+
onCancel={() => setIsNewProductFormOpen(false)}
276+
>
277+
<Form
278+
name="basic"
279+
initialValues={{
280+
remember: true,
281+
}}
282+
onFinish={onNewProductFinish}
283+
>
284+
<Form.Item
285+
name="name"
286+
rules={[{ required: true, message: PRODUCT_DETAILS_REQUIRED }]}
287+
>
288+
<Input placeholder="Product Name" />
289+
</Form.Item>
290+
<Form.Item
291+
name="price"
292+
rules={[
293+
{ required: true, message: PRODUCT_DETAILS_REQUIRED },
294+
{
295+
pattern: /^\d+$/,
296+
message: "Please enter a valid price!",
297+
},
298+
]}
299+
>
300+
<Input placeholder="Price" type="number" step="1" />
301+
</Form.Item>
302+
<Form.Item
303+
name="image_url"
304+
rules={[{ required: true, message: PRODUCT_DETAILS_REQUIRED }]}
305+
>
306+
<Input placeholder="Image URL (e.g., images/product.svg)" />
307+
</Form.Item>
308+
<Form.Item>
309+
{newProductHasErrored && (
310+
<div className="error-message">{newProductErrorMessage}</div>
311+
)}
312+
<Button type="primary" htmlType="submit" className="form-button">
313+
Add
314+
</Button>
315+
</Form.Item>
316+
</Form>
317+
</Modal>
243318
<Modal
244319
title="Create New Coupon"
245320
open={isNewCouponFormOpen}

services/web/src/components/shop/styles.css

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@
315315

316316
/* Responsive Design */
317317
@media (max-width: 1200px) {
318-
.product-card .ant-card-cover {
318+
.product-card .ant-card-cover,
319+
.new-product-card .ant-card-cover {
319320
min-height: 240px;
320321
}
321322
}
@@ -329,7 +330,8 @@
329330
margin-bottom: var(--spacing-md);
330331
}
331332

332-
.product-card .ant-card-cover {
333+
.product-card .ant-card-cover,
334+
.new-product-card .ant-card-cover {
333335
min-height: 200px;
334336
padding: var(--spacing-md);
335337
}
@@ -366,6 +368,19 @@
366368
.pagination .ant-btn {
367369
width: 200px;
368370
}
371+
372+
.new-product-card .ant-card-body {
373+
min-height: 180px;
374+
padding: var(--spacing-md);
375+
}
376+
377+
.add-icon {
378+
font-size: 100px;
379+
}
380+
381+
.new-product-card .product-price {
382+
font-size: 22px;
383+
}
369384
}
370385

371386
@media (max-width: 576px) {
@@ -377,5 +392,59 @@
377392
.page-header .ant-btn {
378393
width: 100%;
379394
}
395+
396+
.new-product-card .ant-card-cover {
397+
min-height: 180px;
398+
}
399+
400+
.new-product-card .ant-card-body {
401+
min-height: 160px;
402+
}
403+
404+
.add-icon {
405+
font-size: 80px;
406+
}
407+
408+
.new-product-card .product-price {
409+
font-size: 18px;
410+
}
411+
}
412+
413+
.new-product-card {
414+
border: 5px dashed #d9d9d9 !important;
415+
background: transparent !important;
416+
}
417+
418+
.new-product-card:hover {
419+
border: none !important;
420+
background: rgba(255, 255, 255, 0.8) !important;
421+
border-color: rgba(139, 92, 246, 0.3);
422+
}
423+
424+
.new-product-card .ant-card-cover {
425+
min-height: 280px;
426+
display: flex;
427+
align-items: center;
428+
justify-content: center;
429+
padding: var(--spacing-lg);
380430
}
381431

432+
.add-icon {
433+
font-size: 150px;
434+
color: #8b5cf6;
435+
opacity: 0.5;
436+
transition: all 0.3s ease;
437+
}
438+
439+
.new-product-card .ant-card-body {
440+
min-height: 217px;
441+
padding: var(--spacing-lg);
442+
display: flex;
443+
align-items: center;
444+
justify-content: center;
445+
}
446+
447+
.new-product-card .product-price {
448+
font-size: 30px;
449+
font-weight: 700;
450+
}

services/web/src/constants/actionTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const actionTypes = {
7070
RETURN_ORDER: "RETURN_ORDER",
7171
ORDER_RETURNED: "ORDER_RETURNED",
7272
APPLY_COUPON: "APPLY_COUPON",
73+
NEW_PRODUCT: "NEW_PRODUCT",
7374
NEW_COUPON: "NEW_COUPON",
7475

7576
GET_POSTS: "GET_POSTS",

services/web/src/constants/messages.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export const POST_TITLE_REQUIRED: string = "Please enter title for post!";
5353
export const POST_DESC_REQUIRED: string = "Please enter description for Post!";
5454
export const COMMENT_REQUIRED: string = "Please enter a comment!";
5555
export const COUPON_CODE_REQUIRED: string = "Please enter a coupon code!";
56+
export const PRODUCT_DETAILS_REQUIRED: string =
57+
"Please enter all product details!";
5658
export const COUPON_AMOUNT_REQUIRED: string = "Please enter a coupon amount!";
5759

5860
export const NO_VEHICLE_DESC_1: string =
@@ -85,6 +87,8 @@ export const INVALID_COUPON_CODE: string = "Invalid Coupon Code";
8587
export const COUPON_APPLIED: string = "Coupon applied";
8688
export const COUPON_NOT_APPLIED: string = "Could not validate coupon";
8789
export const COUPON_NOT_CREATED: string = "Could not create coupon";
90+
export const PRODUCT_NOT_ADDED: string = "Could not add product";
91+
export const NEW_PRODUCT_ADDED: string = "Product added!";
8892
export const INVALID_CREDS: string = "Invalid Username or Password";
8993
export const INVALID_CODE_CREDS: string = "Invalid Email or Code";
9094
export const SIGN_UP_SUCCESS: string = "User Registered Successfully!";

services/web/src/containers/shop/shop.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
getProductsAction,
2323
buyProductAction,
2424
applyCouponAction,
25+
newProductAction,
2526
newCouponAction,
2627
} from "../../actions/shopActions";
2728
import Shop from "../../components/shop/shop";
@@ -37,6 +38,10 @@ const ShopContainer = (props) => {
3738
const [errorMessage, setErrorMessage] = React.useState("");
3839
const [isCouponFormOpen, setIsCouponFormOpen] = useState(false);
3940

41+
const [isNewProductFormOpen, setIsNewProductFormOpen] = useState(false);
42+
const [newProductHasErrored, setNewProductHasErrored] = useState(false);
43+
const [newProductErrorMessage, setNewProductErrorMessage] = useState("");
44+
4045
const [newCouponHasErrored, setNewCouponHasErrored] = React.useState(false);
4146
const [newCouponErrorMessage, setNewCouponErrorMessage] = React.useState("");
4247
const [isNewCouponFormOpen, setIsNewCouponFormOpen] = useState(false);
@@ -103,6 +108,27 @@ const ShopContainer = (props) => {
103108
});
104109
};
105110

111+
const handleNewProductFormFinish = (values) => {
112+
const callback = (res, data) => {
113+
if (res === responseTypes.SUCCESS) {
114+
setIsNewProductFormOpen(false);
115+
Modal.success({
116+
title: SUCCESS_MESSAGE,
117+
content: data,
118+
onOk: () => handleOffsetChange(0),
119+
});
120+
} else {
121+
setNewProductHasErrored(true);
122+
setNewProductErrorMessage(data);
123+
}
124+
};
125+
props.newProduct({
126+
callback,
127+
accessToken,
128+
...values,
129+
});
130+
};
131+
106132
const handleNewCouponFormFinish = (values) => {
107133
const callback = (res, data) => {
108134
if (res === responseTypes.SUCCESS) {
@@ -132,6 +158,11 @@ const ShopContainer = (props) => {
132158
errorMessage={errorMessage}
133159
onFinish={handleFormFinish}
134160
onOffsetChange={handleOffsetChange}
161+
isNewProductFormOpen={isNewProductFormOpen}
162+
setIsNewProductFormOpen={setIsNewProductFormOpen}
163+
newProductHasErrored={newProductHasErrored}
164+
newProductErrorMessage={newProductErrorMessage}
165+
onNewProductFinish={handleNewProductFormFinish}
135166
isNewCouponFormOpen={isNewCouponFormOpen}
136167
setIsNewCouponFormOpen={setIsNewCouponFormOpen}
137168
newCouponHasErrored={newCouponHasErrored}
@@ -152,6 +183,7 @@ const mapDispatchToProps = {
152183
getProducts: getProductsAction,
153184
buyProduct: buyProductAction,
154185
applyCoupon: applyCouponAction,
186+
newProduct: newProductAction,
155187
newCoupon: newCouponAction,
156188
};
157189

@@ -160,6 +192,7 @@ ShopContainer.propTypes = {
160192
getProducts: PropTypes.func,
161193
buyProduct: PropTypes.func,
162194
applyCoupon: PropTypes.func,
195+
newProduct: PropTypes.func,
163196
newCoupon: PropTypes.func,
164197
nextOffset: PropTypes.number,
165198
prevOffset: PropTypes.number,

0 commit comments

Comments
 (0)