Skip to content

Commit

Permalink
Merge pull request #224 from lotteon2/develop
Browse files Browse the repository at this point in the history
release
  • Loading branch information
CokeLee777 authored Jan 18, 2024
2 parents 36e9ff8 + 5f9b64d commit dd66bc0
Show file tree
Hide file tree
Showing 12 changed files with 389 additions and 102 deletions.
10 changes: 10 additions & 0 deletions src/apis/coupon/CouponItemDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,13 @@ export interface Coupons {
memberCouponInfoReadItemResponse: Coupon[]
totalCounts: number
}

export interface MultipleProductsCouponRequest {
products: ProductCategoryPair[]
}

export interface MultipleProductCouponsResponse {
coupons: {
[productId: number]: CouponInfoItemResponse[]
}
}
32 changes: 30 additions & 2 deletions src/apis/coupon/coupon.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import axios, { AxiosError } from 'axios'
import { authAxiosInstance } from '@/apis/utils/index'
import { authAxiosInstance, defaultAxiosInstance } from '@/apis/utils/index'
import type {
CouponInfoItemWithAvailabilityResponse,
ProductCategoryPair,
CheckoutCouponApplicationResponse,
CouponInfoItemResponse,
MultipleCouponDownloadResponse
MultipleCouponDownloadResponse,
MultipleProductsCouponRequest,
MultipleProductCouponsResponse
} from '@/apis/coupon/CouponItemDto'
import { openInternalServerErrorNotification } from '@/utils/Toast'
import { warningModal } from '@/utils/Modal'
Expand Down Expand Up @@ -147,3 +149,29 @@ export const getMyCoupons = async (page: Number) => {
throw error
}
}

export const getMultipleProductsCoupons = async (
requestPayload: MultipleProductsCouponRequest
): Promise<MultipleProductCouponsResponse> => {
try {
const response = await defaultAxiosInstance.post(
`${PROMOTION_PREFIX_PATH}${COUPON_DOMAIN_PREFIX_PATH}/multiple-products`,
requestPayload
)
return response.data
} catch (error) {
if (error instanceof AxiosError) {
if (error.response) {
if (error.response.status >= 400 && error.response.status < 500) {
await warningModal('알림', error.response.data.message)
console.error(`Client Error=${error.response.data.message}`)
}
if (error.response.status >= 500) {
openInternalServerErrorNotification()
console.error('Internal Server Error')
}
}
}
throw error
}
}
13 changes: 12 additions & 1 deletion src/apis/product/ProductDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ export interface ReadProductSliceResponse {
productResponses: ReadProductResponse[]
}

export interface ExtendedReadProductResponse extends ReadProductResponse {
categoryId: number
}

export interface ReadProductResponse {
id: number
brandName: string
Expand All @@ -13,7 +17,7 @@ export interface ReadProductResponse {
imgUrl: string
avgRating: number
reviewCount: number
coupons: CouponInfoItemResponse[]
coupons?: CouponInfoItemResponse[]
}

export interface CouponInfoItemResponse {
Expand Down Expand Up @@ -62,9 +66,16 @@ export interface ReadCacheProductListResponse {
responses: ReadCacheProductResponse[]
}

export interface ExtendedReadCacheProductResponse extends ReadCacheProductResponse {
avgRating: number
reviewCount: number
coupons: CouponInfoItemResponse[]
}

export interface ReadCacheProductResponse {
id: number
brandName: string
categoryId: number
categoryName: string
price: number
name: string
Expand Down
5 changes: 5 additions & 0 deletions src/apis/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import axios, { type AxiosInstance } from 'axios'
import { infoModal } from '@/utils/Modal'
import { useNotificationStore } from '@/stores/notification/NotificationStore'
import router from '@/router'

const BASE_URL = import.meta.env.VITE_API_BASE_URL

Expand Down Expand Up @@ -102,6 +103,10 @@ const axiosAuthApi = (baseURL: string) => {
}
}

if(error.response && error.response.status === 404) {
router.go(-1)
}

return Promise.reject(error)
}
)
Expand Down
58 changes: 36 additions & 22 deletions src/components/product/ProductListPriceDisplay.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
<template>
<div class="product-price">
<template v-if="hasDiscount">
<span class="product-price__original">
<div
class="product-price"
:class="hasDiscount ? 'product-price--has-discount' : 'product-price--no-discount'"
>
<div v-if="hasDiscount" class="product-price__original-and-discount">
<div class="product-price__original">
<del>{{ originalPrice?.toLocaleString() }}원</del>
</span>
<span class="product-price__discount">{{ discountPercentage }}%</span>
</template>
<strong class="product-price__final">{{ finalPrice?.toLocaleString() }}원</strong>
</div>
<div class="product-price__discount-and-final">
<span class="product-price__discount">{{ discountPercentage }}%</span>
<strong class="product-price__final">{{ finalPrice?.toLocaleString() }}원</strong>
</div>
</div>
<strong v-else class="product-price__final">{{ finalPrice?.toLocaleString() }}원</strong>
</div>
</template>

Expand All @@ -27,43 +33,51 @@ const hasDiscount = computed(() => props.discountPercentage > 0)

<style scoped>
.product-price {
width: auto;
height: auto;
/* display: flex;
display: flex;
flex-direction: column;
align-items: center;
text-align: end; */
font-family: 'TheJamsil';
}
.product-price {
.product-price__original-and-discount {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 100%;
}
.product-price__discount-and-final {
display: flex;
justify-content: flex-end;
align-items: center;
font-size: 0; /* inline-block elements간의 의도하지않은 spacing방지용 */
/* margin-left: auto; */
/* margin-left 안넣는게 좋을까요? 😀 */
}
.product-price__original,
.product-price__discount,
.product-price__final {
font-size: 18px; /* 이 컴포넌트의 기본 폰트크기 */
line-height: 24px; /* 가격은 좀 더 크게 뒀음. */
display: inline-block;
font-size: 18px;
line-height: 24px;
}
.product-price__original {
color: #888;
text-decoration: line-through; /* 긋는 효과 */
margin-right: 6px;
font-size: 16px;
color: #888;
text-decoration: line-through;
}
.product-price__discount {
color: #c22727;
margin-right: 7px;
font-size: 18px;
font-weight: bold;
margin-right: 10px; /* Adjust spacing between discount and final price */
}
.product-price__final {
font-weight: 700;
color: #000;
}
.product-price--no-discount .product-price__final {
margin-left: auto;
}
</style>
66 changes: 34 additions & 32 deletions src/stores/post/PostStore.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { PostUpdateRequest, TemporaryUpdateTagProduct } from '@/apis/ootd/PostDto'
import type {
PostImageProductDetailUpdateRequest,
PostUpdateHashTagRequest
PostUpdateHashTagRequest,
PostUpdateRequest,
TemporaryUpdateTagProduct
} from '@/apis/ootd/PostDto'

export const usePostStore = defineStore('post', () => {
Expand All @@ -22,7 +23,10 @@ export const usePostStore = defineStore('post', () => {
new Array<TemporaryUpdateTagProduct>()
)

const postViews = ref<Array<number>>([])
const postViews = ref({
value: [] as number[],
expiry: new Date(Date.now() + 24 * 60 * 60 * 1000)
})

const setPostUpdateRequest = async (post: PostUpdateRequest) => {
postUpdateRequest.value = post
Expand All @@ -35,7 +39,23 @@ export const usePostStore = defineStore('post', () => {
}

const addPostView = async (postId: number) => {
postViews.value.push(postId)
postViews.value.value.push(postId)
}

const hasPostView = async (postId: number) => {
return postViews.value.value.includes(postId)
}

const renewPostView = async () => {
const expiryDate: Date = postViews.value.expiry
if (expiryDate < new Date()) {
const newPostViews = {
value: [] as number[],
expiry: new Date(Date.now() + 24 * 60 * 60 * 1000)
}
postViews.value.value = []
postViews.value.expiry = new Date(Date.now() + 24 * 60 * 60 * 1000)
}
}

const getPostUpdateRequest = async () => {
Expand All @@ -56,32 +76,6 @@ export const usePostStore = defineStore('post', () => {
return temporaryUpdateTagProducts
}

const getPostViews = async () => {
if(localStorage.getItem("postViews") === null) {
const newPostViews = {
value: [] as number[],
expiry: new Date(Date.now() + 24 * 60 * 60 * 1000)
}
localStorage.setItem("postViews", JSON.stringify(postViews))
postViews.value = newPostViews.value
return postViews
}

const postViewsStorage
= JSON.parse(localStorage.getItem("postViews")!) as {value: number[], expiry: Date}
const expiryDate: Date = postViewsStorage.expiry
if(expiryDate < new Date()) {
const newPostViews = {
value: [] as number[],
expiry: new Date(Date.now() + 24 * 60 * 60 * 1000)
}
localStorage.setItem('postViews', JSON.stringify(postViews))
postViews.value = newPostViews.value
}

return postViews
}

const clearPostUpdateRequest = async () => {
sessionStorage.removeItem('postUpdateRequest')
}
Expand All @@ -91,13 +85,21 @@ export const usePostStore = defineStore('post', () => {
}

return {
postViews,
setPostUpdateRequest,
getPostUpdateRequest,
setTemporaryTagProducts,
hasPostView,
addPostView,
renewPostView,
getTemporaryTagProducts,
clearPostUpdateRequest,
clearTemporaryTagProducts,
getPostViews
clearTemporaryTagProducts
}
}, {
persist: {
key: 'postViews',
storage: localStorage,
paths: ['postViews']
}
})
58 changes: 58 additions & 0 deletions src/utils/UtilFunc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { ReadProductResponse, ReadProductSliceResponse } from '@/apis/product/ProductDto'

/**
혜택가 관련 계산입니다.
*/
export const getFloorDiscountPercentage = (product: ReadProductResponse) => {
const { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring } =
getProductMaxDiscountPercentage(product)
return Math.floor(maxDiscountPercentageAfterFlooring)
}

export const getFinalPrice = (product: ReadProductResponse) => {
const { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring } =
getProductMaxDiscountPercentage(product)
return Math.floor(product.price - (product.price * maxDiscountPercentageBeforeFlooring) / 100)
}

const getProductMaxDiscountPercentage = (product: ReadProductResponse) => {
let maxDiscountPercentageBeforeFlooring = 0 // 가격을 따로 정확히 보여주기 위한 보존값
let maxDiscountPercentageAfterFlooring = 0

// product.coupons 없을시, for Each 발동안함. 0, 0 return
if (!product.coupons) {
return { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring }
}
product.coupons.forEach((coupon) => {
if (product.price >= coupon.minPurchaseAmount) {
// Listview에서는 일단 1개 구매를 기준으로 display
// N개 이상 구매했을때를 가정한 최적 할인율과 해당 쿠폰 적용환경의 최소개수도 구할까 고민중임.
let discountPercentage = 0

if (coupon.discountType === 'PERCENTAGE') {
discountPercentage = coupon.discountValue
} else if (coupon.discountType === 'FIXED_AMOUNT') {
discountPercentage = (coupon.discountValue / product.price) * 100
}

// 쿠폰별 maxDiscount cap 적용해서 갱신
if (coupon.maxDiscountAmount !== null) {
const maxDiscountValue = (coupon.maxDiscountAmount / product.price) * 100
discountPercentage = Math.min(discountPercentage, maxDiscountValue)
}

// 직전 iteration과 비교 갱신
maxDiscountPercentageBeforeFlooring = Math.max(
// 가격을 따로 정확히 보여주기 위한 보존값
maxDiscountPercentageBeforeFlooring,
discountPercentage
)
maxDiscountPercentageAfterFlooring = Math.max(
maxDiscountPercentageAfterFlooring,
Math.floor(discountPercentage)
)
}
})

return { maxDiscountPercentageBeforeFlooring, maxDiscountPercentageAfterFlooring }
}
Loading

0 comments on commit dd66bc0

Please sign in to comment.