Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion services/community/api/controllers/coupon_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package controllers

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
Expand Down Expand Up @@ -46,12 +47,20 @@ func (s *Server) AddNewCoupon(w http.ResponseWriter, r *http.Request) {
return
}
coupon.Prepare()

existingCoupon, err := models.ValidateCode(s.Client, s.DB, bson.M{"coupon_code": coupon.CouponCode})
if err == nil && existingCoupon.CouponCode != "" {
responses.ERROR(w, http.StatusConflict, fmt.Errorf("coupon code already exists"))
return
}

savedCoupon, er := models.SaveCoupon(s.Client, coupon)
if er != nil {
responses.ERROR(w, http.StatusInternalServerError, er)
return
}
if savedCoupon.CouponCode != "" {
responses.JSON(w, http.StatusOK, "Coupon Added in database")
responses.JSON(w, http.StatusOK, "Coupon added in database!")
}

}
Expand Down
15 changes: 15 additions & 0 deletions services/web/src/actions/shopActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,18 @@ export const newProductAction = ({
},
};
};

export const newCouponAction = ({
accessToken,
callback,
...data
}: ActionPayload) => {
return {
type: actionTypes.NEW_COUPON,
payload: {
accessToken,
...data,
callback,
},
};
};
67 changes: 66 additions & 1 deletion services/web/src/components/shop/shop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ import {
PlusOutlined,
OrderedListOutlined,
ShoppingCartOutlined,
GiftOutlined,
} from "@ant-design/icons";
import {
COUPON_CODE_REQUIRED,
PRODUCT_DETAILS_REQUIRED,
COUPON_AMOUNT_REQUIRED,
} from "../../constants/messages";
import { useNavigate } from "react-router-dom";
import roleTypes from "../../constants/roleTypes";
Expand Down Expand Up @@ -68,6 +70,11 @@ interface ShopProps extends PropsFromRedux {
newProductHasErrored: boolean;
newProductErrorMessage: string;
onNewProductFinish: (values: any) => void;
isNewCouponFormOpen: boolean;
setIsNewCouponFormOpen: (isOpen: boolean) => void;
newCouponHasErrored: boolean;
newCouponErrorMessage: string;
onNewCouponFinish: (values: any) => void;
role: string;
}

Expand Down Expand Up @@ -120,6 +127,11 @@ const Shop: React.FC<ShopProps> = (props) => {
newProductHasErrored,
newProductErrorMessage,
onNewProductFinish,
isNewCouponFormOpen,
setIsNewCouponFormOpen,
newCouponHasErrored,
newCouponErrorMessage,
onNewCouponFinish,
role,
} = props;

Expand All @@ -130,6 +142,18 @@ const Shop: React.FC<ShopProps> = (props) => {
title="Shop"
onBack={() => navigate("/dashboard")}
extra={[
role === roleTypes.ROLE_ADMIN && (
<Button
type="primary"
shape="round"
icon={<GiftOutlined />}
size="large"
key="new-coupon"
onClick={() => setIsNewCouponFormOpen(true)}
>
Create Coupon
</Button>
),
<Button
type="primary"
shape="round"
Expand All @@ -150,7 +174,7 @@ const Shop: React.FC<ShopProps> = (props) => {
>
Past Orders
</Button>,
]}
].filter(Boolean)}
/>
<Descriptions column={1} className="balance-desc">
<Descriptions.Item label="Available Balance">
Expand Down Expand Up @@ -291,6 +315,47 @@ const Shop: React.FC<ShopProps> = (props) => {
</Form.Item>
</Form>
</Modal>
<Modal
title="Create New Coupon"
open={isNewCouponFormOpen}
footer={null}
onCancel={() => setIsNewCouponFormOpen(false)}
>
<Form
name="basic"
initialValues={{
remember: true,
}}
onFinish={onNewCouponFinish}
>
<Form.Item
name="couponCode"
rules={[{ required: true, message: COUPON_CODE_REQUIRED }]}
>
<Input placeholder="Coupon Code" />
</Form.Item>
<Form.Item
name="amount"
rules={[
{ required: true, message: COUPON_AMOUNT_REQUIRED },
{
pattern: /^\d+$/,
message: "Please enter a valid amount!",
},
]}
>
<Input placeholder="Amount" type="number" step="1" />
</Form.Item>
<Form.Item>
{newCouponHasErrored && (
<div className="error-message">{newCouponErrorMessage}</div>
)}
<Button type="primary" htmlType="submit" className="form-button">
Create
</Button>
</Form.Item>
</Form>
</Modal>
</Layout>
);
};
Expand Down
1 change: 1 addition & 0 deletions services/web/src/constants/APIConstant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,6 @@ export const requestURLS: RequestURLSType = {
GET_POST_BY_ID: "api/v2/community/posts/<postId>",
ADD_COMMENT: "api/v2/community/posts/<postId>/comment",
VALIDATE_COUPON: "api/v2/coupon/validate-coupon",
NEW_COUPON: "api/v2/coupon/new-coupon",
VALIDATE_TOKEN: "api/auth/verify",
};
1 change: 1 addition & 0 deletions services/web/src/constants/actionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const actionTypes = {
ORDER_RETURNED: "ORDER_RETURNED",
APPLY_COUPON: "APPLY_COUPON",
NEW_PRODUCT: "NEW_PRODUCT",
NEW_COUPON: "NEW_COUPON",

GET_POSTS: "GET_POSTS",
FETCHED_POSTS: "FETCHED_POSTS",
Expand Down
3 changes: 3 additions & 0 deletions services/web/src/constants/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export const COMMENT_REQUIRED: string = "Please enter a comment!";
export const COUPON_CODE_REQUIRED: string = "Please enter a coupon code!";
export const PRODUCT_DETAILS_REQUIRED: string =
"Please enter all product details!";
export const COUPON_AMOUNT_REQUIRED: string = "Please enter a coupon amount!";

export const NO_VEHICLE_DESC_1: string =
"Your newly purchased Vehicle Details have been sent to you email address. Please check your email for the VIN and PIN code of your vehicle using the MailHog web portal.";
export const NO_VEHICLE_DESC_2: string = " Click here ";
Expand Down Expand Up @@ -84,6 +86,7 @@ export const ORDER_NOT_RETURNED: string = "Could not return order";
export const INVALID_COUPON_CODE: string = "Invalid Coupon Code";
export const COUPON_APPLIED: string = "Coupon applied";
export const COUPON_NOT_APPLIED: string = "Could not validate coupon";
export const COUPON_NOT_CREATED: string = "Could not create coupon";
export const PRODUCT_NOT_ADDED: string = "Could not add product";
export const NEW_PRODUCT_ADDED: string = "Product added!";
export const INVALID_CREDS: string = "Invalid Username or Password";
Expand Down
32 changes: 32 additions & 0 deletions services/web/src/containers/shop/shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
buyProductAction,
applyCouponAction,
newProductAction,
newCouponAction,
} from "../../actions/shopActions";
import Shop from "../../components/shop/shop";
import { useNavigate } from "react-router-dom";
Expand All @@ -41,6 +42,10 @@ const ShopContainer = (props) => {
const [newProductHasErrored, setNewProductHasErrored] = useState(false);
const [newProductErrorMessage, setNewProductErrorMessage] = useState("");

const [newCouponHasErrored, setNewCouponHasErrored] = React.useState(false);
const [newCouponErrorMessage, setNewCouponErrorMessage] = React.useState("");
const [isNewCouponFormOpen, setIsNewCouponFormOpen] = useState(false);

useEffect(() => {
const callback = (res, data) => {
if (res !== responseTypes.SUCCESS) {
Expand Down Expand Up @@ -124,6 +129,26 @@ const ShopContainer = (props) => {
});
};

const handleNewCouponFormFinish = (values) => {
const callback = (res, data) => {
if (res === responseTypes.SUCCESS) {
setIsNewCouponFormOpen(false);
Modal.success({
title: SUCCESS_MESSAGE,
content: data,
});
} else {
setNewCouponHasErrored(true);
setNewCouponErrorMessage(data);
}
};
props.newCoupon({
callback,
accessToken,
...values,
});
};

return (
<Shop
onBuyProduct={handleBuyProduct}
Expand All @@ -138,6 +163,11 @@ const ShopContainer = (props) => {
newProductHasErrored={newProductHasErrored}
newProductErrorMessage={newProductErrorMessage}
onNewProductFinish={handleNewProductFormFinish}
isNewCouponFormOpen={isNewCouponFormOpen}
setIsNewCouponFormOpen={setIsNewCouponFormOpen}
newCouponHasErrored={newCouponHasErrored}
newCouponErrorMessage={newCouponErrorMessage}
onNewCouponFinish={handleNewCouponFormFinish}
{...props}
/>
);
Expand All @@ -154,6 +184,7 @@ const mapDispatchToProps = {
buyProduct: buyProductAction,
applyCoupon: applyCouponAction,
newProduct: newProductAction,
newCoupon: newCouponAction,
};

ShopContainer.propTypes = {
Expand All @@ -162,6 +193,7 @@ ShopContainer.propTypes = {
buyProduct: PropTypes.func,
applyCoupon: PropTypes.func,
newProduct: PropTypes.func,
newCoupon: PropTypes.func,
nextOffset: PropTypes.number,
prevOffset: PropTypes.number,
onOffsetChange: PropTypes.func,
Expand Down
41 changes: 41 additions & 0 deletions services/web/src/sagas/shopSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
INVALID_COUPON_CODE,
COUPON_APPLIED,
COUPON_NOT_APPLIED,
COUPON_NOT_CREATED,
NEW_PRODUCT_ADDED,
PRODUCT_NOT_ADDED,
} from "../constants/messages";
Expand Down Expand Up @@ -384,6 +385,45 @@ export function* newProduct(action: MyAction): Generator<any, void, any> {
}
}

/**
* create a new coupon (admin only)
* @payload { accessToken, couponCode, amount, callback} payload
* accessToken: access token of the user
* couponCode: coupon code to create
* amount: amount for the coupon
* callback : callback method
*/
export function* newCoupon(action: MyAction): Generator<any, void, any> {
const { accessToken, couponCode, amount, callback } = action.payload;
let recievedResponse: ReceivedResponse = {} as ReceivedResponse;
try {
yield put({ type: actionTypes.FETCHING_DATA });
let postUrl = APIService.COMMUNITY_SERVICE + requestURLS.NEW_COUPON;
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
};
const responseJson = yield fetch(postUrl, {
headers,
method: "POST",
body: JSON.stringify({ coupon_code: couponCode, amount: amount }),
}).then((response: Response) => {
recievedResponse = response as ReceivedResponse;
return response.json();
});

yield put({ type: actionTypes.FETCHED_DATA, payload: recievedResponse });
if (recievedResponse.ok) {
callback(responseTypes.SUCCESS, responseJson);
} else {
callback(responseTypes.FAILURE, COUPON_NOT_CREATED);
}
} catch (e) {
yield put({ type: actionTypes.FETCHED_DATA, payload: recievedResponse });
callback(responseTypes.FAILURE, COUPON_NOT_CREATED);
}
}

export function* shopActionWatcher(): Generator<any, void, any> {
yield takeLatest(actionTypes.GET_PRODUCTS, getProducts);
yield takeLatest(actionTypes.BUY_PRODUCT, buyProduct);
Expand All @@ -392,4 +432,5 @@ export function* shopActionWatcher(): Generator<any, void, any> {
yield takeLatest(actionTypes.RETURN_ORDER, returnOrder);
yield takeLatest(actionTypes.APPLY_COUPON, applyCoupon);
yield takeLatest(actionTypes.NEW_PRODUCT, newProduct);
yield takeLatest(actionTypes.NEW_COUPON, newCoupon);
}
Loading