diff --git a/services/community/api/controllers/coupon_controller.go b/services/community/api/controllers/coupon_controller.go index a1f1f2e6..66e8c9fb 100644 --- a/services/community/api/controllers/coupon_controller.go +++ b/services/community/api/controllers/coupon_controller.go @@ -16,6 +16,7 @@ package controllers import ( "encoding/json" + "fmt" "io" "log" "net/http" @@ -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!") } } diff --git a/services/web/src/actions/shopActions.ts b/services/web/src/actions/shopActions.ts index ba7286f8..5e361743 100644 --- a/services/web/src/actions/shopActions.ts +++ b/services/web/src/actions/shopActions.ts @@ -129,3 +129,18 @@ export const newProductAction = ({ }, }; }; + +export const newCouponAction = ({ + accessToken, + callback, + ...data +}: ActionPayload) => { + return { + type: actionTypes.NEW_COUPON, + payload: { + accessToken, + ...data, + callback, + }, + }; +}; diff --git a/services/web/src/components/shop/shop.tsx b/services/web/src/components/shop/shop.tsx index b344b1c3..a7b4f83d 100644 --- a/services/web/src/components/shop/shop.tsx +++ b/services/web/src/components/shop/shop.tsx @@ -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"; @@ -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; } @@ -120,6 +127,11 @@ const Shop: React.FC = (props) => { newProductHasErrored, newProductErrorMessage, onNewProductFinish, + isNewCouponFormOpen, + setIsNewCouponFormOpen, + newCouponHasErrored, + newCouponErrorMessage, + onNewCouponFinish, role, } = props; @@ -130,6 +142,18 @@ const Shop: React.FC = (props) => { title="Shop" onBack={() => navigate("/dashboard")} extra={[ + role === roleTypes.ROLE_ADMIN && ( + + ), , - ]} + ].filter(Boolean)} /> @@ -291,6 +315,47 @@ const Shop: React.FC = (props) => { + setIsNewCouponFormOpen(false)} + > +
+ + + + + + + + {newCouponHasErrored && ( +
{newCouponErrorMessage}
+ )} + +
+
+
); }; diff --git a/services/web/src/constants/APIConstant.ts b/services/web/src/constants/APIConstant.ts index 63d3fc2b..f4036cb8 100644 --- a/services/web/src/constants/APIConstant.ts +++ b/services/web/src/constants/APIConstant.ts @@ -76,5 +76,6 @@ export const requestURLS: RequestURLSType = { GET_POST_BY_ID: "api/v2/community/posts/", ADD_COMMENT: "api/v2/community/posts//comment", VALIDATE_COUPON: "api/v2/coupon/validate-coupon", + NEW_COUPON: "api/v2/coupon/new-coupon", VALIDATE_TOKEN: "api/auth/verify", }; diff --git a/services/web/src/constants/actionTypes.ts b/services/web/src/constants/actionTypes.ts index 28247026..04381374 100644 --- a/services/web/src/constants/actionTypes.ts +++ b/services/web/src/constants/actionTypes.ts @@ -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", diff --git a/services/web/src/constants/messages.ts b/services/web/src/constants/messages.ts index c003f2d8..c723b96b 100644 --- a/services/web/src/constants/messages.ts +++ b/services/web/src/constants/messages.ts @@ -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 "; @@ -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"; diff --git a/services/web/src/containers/shop/shop.js b/services/web/src/containers/shop/shop.js index dc60a8fb..97c91b15 100644 --- a/services/web/src/containers/shop/shop.js +++ b/services/web/src/containers/shop/shop.js @@ -23,6 +23,7 @@ import { buyProductAction, applyCouponAction, newProductAction, + newCouponAction, } from "../../actions/shopActions"; import Shop from "../../components/shop/shop"; import { useNavigate } from "react-router-dom"; @@ -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) { @@ -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 ( { newProductHasErrored={newProductHasErrored} newProductErrorMessage={newProductErrorMessage} onNewProductFinish={handleNewProductFormFinish} + isNewCouponFormOpen={isNewCouponFormOpen} + setIsNewCouponFormOpen={setIsNewCouponFormOpen} + newCouponHasErrored={newCouponHasErrored} + newCouponErrorMessage={newCouponErrorMessage} + onNewCouponFinish={handleNewCouponFormFinish} {...props} /> ); @@ -154,6 +184,7 @@ const mapDispatchToProps = { buyProduct: buyProductAction, applyCoupon: applyCouponAction, newProduct: newProductAction, + newCoupon: newCouponAction, }; ShopContainer.propTypes = { @@ -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, diff --git a/services/web/src/sagas/shopSaga.ts b/services/web/src/sagas/shopSaga.ts index 5451314d..adda2209 100644 --- a/services/web/src/sagas/shopSaga.ts +++ b/services/web/src/sagas/shopSaga.ts @@ -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"; @@ -384,6 +385,45 @@ export function* newProduct(action: MyAction): Generator { } } +/** + * 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 { + 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 { yield takeLatest(actionTypes.GET_PRODUCTS, getProducts); yield takeLatest(actionTypes.BUY_PRODUCT, buyProduct); @@ -392,4 +432,5 @@ export function* shopActionWatcher(): Generator { yield takeLatest(actionTypes.RETURN_ORDER, returnOrder); yield takeLatest(actionTypes.APPLY_COUPON, applyCoupon); yield takeLatest(actionTypes.NEW_PRODUCT, newProduct); + yield takeLatest(actionTypes.NEW_COUPON, newCoupon); }