871 lines
28 KiB
Vue
871 lines
28 KiB
Vue
<script setup lang="ts">
|
|
import {
|
|
globalDateFormat,
|
|
getTime,
|
|
fromUnixTime,
|
|
addMonths,
|
|
differenceInDays,
|
|
} from '@seed-next/date'
|
|
import { getComponentGroup } from '#layers/utils/dataUtil'
|
|
import {
|
|
COUPON_NO_LENGTH_LIMIT,
|
|
COUPON_RESULT,
|
|
} from '#layers/types/api/couponData'
|
|
import type { PageDataTemplateComponents } from '#layers/types/api/pageData'
|
|
import type { ReqCouponList } from '#layers/types/api/couponData'
|
|
|
|
interface Props {
|
|
id?: string
|
|
components: PageDataTemplateComponents
|
|
pageVerTmplSeq: number
|
|
pageVerTmplNameEn: string
|
|
}
|
|
const props = defineProps<Props>()
|
|
|
|
const runtimeConfig = useRuntimeConfig()
|
|
|
|
const stoveApiUrl = runtimeConfig.public.stoveApiUrl as string
|
|
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
|
|
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_coupon.json'
|
|
|
|
// Multilingual
|
|
const resultGetMultilingual = await useGetMultilingual({
|
|
baseApiUrl: dataResourcesUrl,
|
|
fileName: multilingualFileName,
|
|
})
|
|
const { t, tm, locale }: any = useI18n({
|
|
useScope: 'local',
|
|
messages: Object(resultGetMultilingual?.value?.multilingual),
|
|
})
|
|
|
|
// Composables
|
|
const { sendLog } = useAnalytics()
|
|
// const { isGameMaintenance, checkGameMaintenance } = useGetGameMaintenance()
|
|
const { isWebInspection, getInspectionDataExternal } =
|
|
useGetInspectionDataExternal()
|
|
const breakpoints = useResponsiveBreakpoints()
|
|
const { handleTokenValidation } = useTokenValidation()
|
|
const { hasGuid, characterList, getGuid, getCharacterList } =
|
|
useGameLinkedData()
|
|
const {
|
|
couponList,
|
|
hasNoCouponList,
|
|
totalCount,
|
|
postCouponUse,
|
|
getCouponList,
|
|
} = useCoupon()
|
|
const {
|
|
startDate,
|
|
endDate,
|
|
currentSearchPeriod,
|
|
searchStatus,
|
|
setCouponDate,
|
|
toUnixTimestamp,
|
|
} = useCouponDate()
|
|
const { pageNo, pageSize, updatePagination, getPageBlock } = useCouponPaging()
|
|
|
|
// Store
|
|
const gameDataStore = useGameDataStore()
|
|
const modalStore = useModalStore()
|
|
const couponStore = useCouponStore()
|
|
const { gameId, gameCode } = storeToRefs(gameDataStore)
|
|
const { handleOpenAlert, handleOpenConfirm } = modalStore
|
|
const { couponNo, isSelectCharacter, selectCharacter } =
|
|
storeToRefs(couponStore)
|
|
const { updateCouponNo, updateSelectCharacter, isEmptyCouponNo } = couponStore
|
|
|
|
// Refs
|
|
const clientIp = ref('')
|
|
const monthSelectList = ref<Array<number>>([1, 3, 6, 12])
|
|
const isSelectCharacterModalOpen = ref(false)
|
|
|
|
// Computed
|
|
const backgroundData = computed(() =>
|
|
getComponentGroup(props.components, 'background')
|
|
)
|
|
const sortedCharacterList = computed(() => {
|
|
return characterList.value
|
|
.map(characterInfo => {
|
|
const worldId =
|
|
characterInfo.world_id.split('_')[
|
|
characterInfo.world_id?.split('_').length - 1
|
|
]
|
|
const arrWorldIdPriority = ['global', 'asia']
|
|
const sortedIndex = arrWorldIdPriority.indexOf(worldId)
|
|
return { ...characterInfo, sortedIndex }
|
|
})
|
|
.sort((a, b) => {
|
|
return a.sortedIndex !== b.sortedIndex ? a.sortedIndex - b.sortedIndex : 0
|
|
})
|
|
})
|
|
|
|
// Functions
|
|
const handleSendLog = (item: string) => {
|
|
const analytics = {
|
|
action_type: 'click',
|
|
click_item: item,
|
|
click_sarea: props.pageVerTmplNameEn,
|
|
}
|
|
|
|
sendLog(locale.value, analytics)
|
|
}
|
|
/**
|
|
* @description 기본 Alert 모달 팝업 함수입니다.
|
|
* @param text - 모달 내용
|
|
*/
|
|
const openAlert = (text: string) => {
|
|
handleOpenAlert({
|
|
contentText: text,
|
|
isOutsideClose: true,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @description 유저 IP 조회 함수입니다.
|
|
*/
|
|
const getClientIp = async () => {
|
|
if (!clientIp.value) {
|
|
try {
|
|
const ipData = await $fetch('/api/clientIp')
|
|
clientIp.value = ipData || ''
|
|
} catch (e) {
|
|
console.error('[Exception] FxCoupon01.getClientIp:', e)
|
|
clientIp.value = ''
|
|
}
|
|
} // 이미 조회했으면 재사용
|
|
return clientIp.value
|
|
}
|
|
|
|
/**
|
|
* @description 쿠폰 사용 전 유효성 체크 함수입니다.
|
|
* @returns {number} 쿠폰 사용 전 유효성 체크 결과
|
|
*/
|
|
const validationCheckBefore = async () => {
|
|
// FIXME: 다른 체크 로직 추가 시 주석 해제 필요 ---------------------------------------------------
|
|
|
|
/**
|
|
* @description 게임 점검 체크하는 로직입니다.
|
|
* await checkGameMaintenance({
|
|
* baseApiUrl: stoveMaintenanceApiUrl,
|
|
* category: 'GAME',
|
|
* service_id1: gameId.value,
|
|
* gameId: gameId.value,
|
|
* lang: locale.value,
|
|
* })
|
|
* if (isGameMaintenance.value) {
|
|
* return COUPON_RESULT.GAME_MAINTENANCE
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* @description 언어 코드 존재 여부 체크하는 로직입니다.
|
|
* if (locale.value == null || locale.value === '') {
|
|
* return COUPON_RESULT.SYSTEM_ERROR
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* @description 서버(캐릭터) 선택 여부 체크하는 로직입니다.
|
|
* if (!isSelectCharacter.value) {
|
|
* return COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
|
* }
|
|
*/
|
|
|
|
/**
|
|
* @description 선택한 캐릭터 정보 유효 여부 (접속 계정과 동일한 캐릭터 여부) 체크하는 로직입니다.
|
|
* await getCharacterList({
|
|
* baseApiUrl: stoveApiUrl,
|
|
* accessToken: csrGetAccessToken(),
|
|
* game_id: gameId.value,
|
|
* gameId: gameId.value,
|
|
* })
|
|
* if (
|
|
* !characterList.value.some(
|
|
* character => character.character_id === selectCharacter.value.character_id
|
|
* )
|
|
* ) {
|
|
* return COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
|
* }
|
|
*/
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @description 웹 점검 여부 체크
|
|
*/
|
|
await getInspectionDataExternal({
|
|
baseApiUrl: stoveApiUrl,
|
|
gameId: gameId.value,
|
|
})
|
|
if (isWebInspection.value) {
|
|
return COUPON_RESULT.WEB_INSPECTION
|
|
}
|
|
|
|
/**
|
|
* @description 로그인 여부 체크
|
|
*/
|
|
const accessToken = csrGetAccessToken()
|
|
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
|
if (validateTokenResult === false) {
|
|
return COUPON_RESULT.LOGIN_REQUIRED
|
|
}
|
|
|
|
/**
|
|
* @description GUID(게임 이용 동의) 체크
|
|
*/
|
|
await getGuid({
|
|
baseApiUrl: stoveApiUrl,
|
|
accessToken: csrGetAccessToken(),
|
|
game_id: gameId.value,
|
|
gameId: gameId.value,
|
|
})
|
|
if (!hasGuid.value) {
|
|
return COUPON_RESULT.EMPTY_GUID
|
|
}
|
|
|
|
/**
|
|
* @description 쿠폰번호 입력 여부 체크
|
|
*/
|
|
if (isEmptyCouponNo(couponNo.value)) {
|
|
return COUPON_RESULT.NULL_COUPON_NO
|
|
}
|
|
|
|
/**
|
|
* @description 쿠폰번호 유효성 체크 (최소 2자, 최대 20자)
|
|
*/
|
|
if (
|
|
!isInRange(
|
|
couponNo.value.length,
|
|
COUPON_NO_LENGTH_LIMIT.MIN,
|
|
COUPON_NO_LENGTH_LIMIT.MAX
|
|
)
|
|
) {
|
|
return COUPON_RESULT.FAIL_COUPON_FORMAT
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
/**
|
|
* @description 쿠폰 등록 버튼 함수입니다.
|
|
* @description FE 유효성 체크 후 캐릭터 선택 모달 팝업 노출합니다.
|
|
*/
|
|
const handleCouponUse = async () => {
|
|
handleSendLog(t('Coupon_Registration', {}, { locale: 'ko' }))
|
|
|
|
const validationCheckBeforeResult = await validationCheckBefore()
|
|
|
|
if (validationCheckBeforeResult !== 0) {
|
|
if (validationCheckBeforeResult === COUPON_RESULT.LOGIN_REQUIRED) {
|
|
// 미로그인일 경우 : 이미 validationCheckBefore 함수에서 로그인 모달 팝업 노출했으므로 추가 처리 필요 없음
|
|
return
|
|
} else if (
|
|
validationCheckBeforeResult === COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
|
) {
|
|
// 캐릭터 미선택일 경우 : 캐릭터 선택 모달 팝업 노출
|
|
openSelectCharacterModal()
|
|
return
|
|
} else if (validationCheckBeforeResult === COUPON_RESULT.SYSTEM_ERROR) {
|
|
// 시스템 에러일 경우 : 에러 팝업 노출 (에러코드)
|
|
openAlert(t('Coupon_Error', { code: validationCheckBeforeResult }))
|
|
return
|
|
} else {
|
|
// 그 외 오류 팝업 노출
|
|
openAlert(tm(`Coupon_Alert_(${validationCheckBeforeResult})`))
|
|
return
|
|
}
|
|
} else {
|
|
openSelectCharacterModal()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description 캐릭터 선택 후, 확인 버튼 클릭 시 쿠폰 등록 API 호출 함수입니다.
|
|
*/
|
|
const handleCouponRegister = async () => {
|
|
handleSendLog('캐릭터 선택 완료')
|
|
closeSelectCharacterModal()
|
|
|
|
const validationCheckBeforeResult = await validationCheckBefore()
|
|
if (validationCheckBeforeResult !== 0) {
|
|
if (validationCheckBeforeResult === COUPON_RESULT.LOGIN_REQUIRED) {
|
|
// 미로그인일 경우 : 이미 validationCheckBefore 함수에서 로그인 모달 팝업 노출했으므로 추가 처리 필요 없음
|
|
return
|
|
} else if (
|
|
validationCheckBeforeResult === COUPON_RESULT.SELECT_CHARACTER_REQUIRED
|
|
) {
|
|
// 캐릭터 미선택일 경우 : 캐릭터 선택 모달 팝업 노출
|
|
openSelectCharacterModal()
|
|
return
|
|
} else if (validationCheckBeforeResult === COUPON_RESULT.SYSTEM_ERROR) {
|
|
// 시스템 에러일 경우 : 에러 팝업 노출 (에러코드)
|
|
openAlert(t('Coupon_Error', { code: validationCheckBeforeResult }))
|
|
return
|
|
} else {
|
|
// 그 외 오류 팝업 노출
|
|
openAlert(tm(`Coupon_Alert_(${validationCheckBeforeResult})`))
|
|
return
|
|
}
|
|
} else {
|
|
await getClientIp()
|
|
const res = await postCouponUse({
|
|
accessToken: csrGetAccessToken(),
|
|
user_token_type: 'web',
|
|
game_code: gameCode.value.toString(),
|
|
coupon_no: couponNo.value,
|
|
client_ipaddr: clientIp.value,
|
|
world_no: selectCharacter.value.world_id,
|
|
character_no: `${selectCharacter.value.character_id}`,
|
|
lang_code: locale.value,
|
|
})
|
|
if (res.code === 0) {
|
|
openAlert(tm('Coupon_Alert_Success'))
|
|
} else {
|
|
openAlert(tm(`Coupon_Alert_(${res.code})`))
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description 기간 선택 이벤트 함수입니다.
|
|
* @param {number} month - 선택한 기간 개월수
|
|
*/
|
|
const handlePeriodSelect = (month: number) => {
|
|
const today = new Date()
|
|
const startTimestamp = toUnixTimestamp(today, 'start')
|
|
const endTimestamp = toUnixTimestamp(today, 'end')
|
|
|
|
const newStartDate = addMonths(fromUnixTime(startTimestamp), -month)
|
|
const newEndDate = fromUnixTime(endTimestamp)
|
|
|
|
setCouponDate(newStartDate, 'start')
|
|
setCouponDate(newEndDate, 'end')
|
|
handleSendLog(t(`Coupon_Month${month}`, {}, { locale: 'ko' }))
|
|
}
|
|
|
|
/**
|
|
* @description 조회하기 이벤트 함수입니다.
|
|
*/
|
|
const handlePeriodSearch = async () => {
|
|
if (!startDate.value || !endDate.value) {
|
|
return
|
|
}
|
|
|
|
handleSendLog(t('Coupon_Search', {}, { locale: 'ko' }))
|
|
|
|
const accessToken = csrGetAccessToken()
|
|
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
|
if (validateTokenResult === false) {
|
|
return
|
|
}
|
|
|
|
const result = differenceInDays(endDate.value, startDate.value)
|
|
if (result < 0 || result > 365) {
|
|
openAlert(tm('Coupon_Alert_OverDays'))
|
|
return
|
|
}
|
|
|
|
const req: ReqCouponList = {
|
|
accessToken: accessToken,
|
|
user_token_type: 'web',
|
|
game_code: gameCode.value.toString(),
|
|
start_date: getTime(startDate.value),
|
|
end_date: getTime(endDate.value),
|
|
use_state_code: searchStatus.value,
|
|
page_size: pageSize.value,
|
|
page_no: pageNo.value,
|
|
lang_code: locale.value,
|
|
}
|
|
|
|
await getCouponList(req)
|
|
}
|
|
|
|
/**
|
|
* @desciprion 페이지 변경 이벤트 함수입니다.
|
|
* @param {number} newPageNo
|
|
*/
|
|
const handlePaging = async (newPageNo: number) => {
|
|
updatePagination(newPageNo)
|
|
await handlePeriodSearch()
|
|
}
|
|
|
|
/**
|
|
* @description 게임별 스토브 쿠폰함 URL 조회 함수입니다.
|
|
* @returns {string} 게임별 스토브 쿠폰함 URL
|
|
*/
|
|
const getCouponBoxUrl = () => {
|
|
let url = ''
|
|
|
|
if (breakpoints.value.isMobile) {
|
|
url = getStoveCouponUrl('mobile')
|
|
} else {
|
|
url = getStoveCouponUrl('desktop')
|
|
}
|
|
|
|
return `${url}?game_id=${gameId.value}`
|
|
}
|
|
|
|
/**
|
|
* @description @coupon{문구} 형식의 문구를 게임별 스토브 쿠폰함 URL의 a태그로 변환합니다.
|
|
* @param {string} text - 변환할 문구
|
|
* @returns {string} 변환된 문구 HTML
|
|
* @description string으로 return하므로 v-dompurify-html 사용 필요
|
|
*/
|
|
const getKeyToCouponUrl = (text: string) => {
|
|
const couponBoxUrl = getCouponBoxUrl()
|
|
|
|
let result = ''
|
|
|
|
if (text.includes('@coupon')) {
|
|
result = text.replace(/@coupon\{(.*?)\}/g, (_, key) => {
|
|
return `<a href="${couponBoxUrl}" rel="noopener noreferrer" target="_blank" class="text-[#3C75FF] underline decoration-solid decoration-auto underline-offset-2 after:content-[''] after:absolute after:top-1/2 after:left-0 after:translate-y-[-50%] after:w-full after:h-fuill after:bg-white after:opacity-0 transition-opacity duration-300 ease-in-out hover:after:opacity-20">${key}</a>`
|
|
})
|
|
|
|
return result
|
|
} else {
|
|
return text
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description STOVE 쿠폰함으로 이동합니다.
|
|
*/
|
|
const goToCouponBox = () => {
|
|
let url = ''
|
|
|
|
if (breakpoints.value.isMobile) {
|
|
url = getStoveCouponUrl('mobile')
|
|
} else {
|
|
url = getStoveCouponUrl('desktop')
|
|
}
|
|
|
|
csrGoExternalLink(`${url}?game_id=${gameId.value}`)
|
|
}
|
|
|
|
/**
|
|
* @description 쿠폰 등록 내역의 사용하기 버튼의 클릭 이벤트 함수입니다.
|
|
*/
|
|
const handleGoToCouponBox = async () => {
|
|
handleSendLog(t('Coupon_Item_Use', {}, { locale: 'ko' }))
|
|
|
|
const accessToken = csrGetAccessToken()
|
|
const validateTokenResult = await handleTokenValidation(accessToken || '')
|
|
|
|
// 미로그인 상태일 경우 Return
|
|
if (validateTokenResult === false) {
|
|
return
|
|
}
|
|
|
|
// 로그인 상태일 경우 모달 표시
|
|
handleOpenConfirm({
|
|
contentText: tm('Coupon_Alert_StoveCouponBox'),
|
|
confirmButtonText: tm('Coupon_StoveCouponBox'),
|
|
modalName: 'modal-coupon-use',
|
|
confirmButtonEvent: () => {
|
|
goToCouponBox()
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @description 쿠폰 등록 관련 데이터를 초기화합니다.
|
|
*/
|
|
const initCouponData = () => {
|
|
updateSelectCharacter(null)
|
|
updateCouponNo('')
|
|
updatePagination(1)
|
|
}
|
|
|
|
/**
|
|
* @description 캐릭터 선택 모달 팝업 열기
|
|
*/
|
|
const openSelectCharacterModal = () => {
|
|
isSelectCharacterModalOpen.value = true
|
|
}
|
|
/**
|
|
* @description 캐릭터 선택 모달 팝업 닫기
|
|
*/
|
|
const closeSelectCharacterModal = () => {
|
|
isSelectCharacterModalOpen.value = false
|
|
}
|
|
|
|
onMounted(async () => {
|
|
initCouponData()
|
|
|
|
const accessToken = csrGetAccessToken()
|
|
if (accessToken) {
|
|
handlePeriodSelect(1)
|
|
await handlePeriodSearch()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<WidgetsFixMainTitle
|
|
:id="props.id"
|
|
:title="tm('Coupon_Page_Title')"
|
|
:resources-data="backgroundData"
|
|
/>
|
|
|
|
<div class="section-container static">
|
|
<section class="section-static">
|
|
<WidgetsFixSubTitle :title="tm('Coupon_Section_Registration_Title')" />
|
|
|
|
<div
|
|
class="relative flex flex-col items-center jutify-start w-full bg-white p-[20px] mt-[16px] rounded-[12px] sm:rounded-[16px] md:p-[32px] md:mt-[24px]"
|
|
>
|
|
<div
|
|
class="relative flex flex-col items-center justify-start gap-[16px] w-full max-w-[880px] md:gap-[20px]"
|
|
>
|
|
<div
|
|
novalidate
|
|
class="relative flex flex-col items-center justify-start gap-[12px] w-full sm:flex-row"
|
|
>
|
|
<AtomsInput
|
|
:model-value="couponNo"
|
|
:name="tm('Coupon_Enter_Number')"
|
|
:placeholder="tm('Coupon_Enter_Number_Please')"
|
|
maxlength="20"
|
|
@update:model-value="updateCouponNo"
|
|
/>
|
|
<AtomsButton
|
|
type="action"
|
|
size="size-small"
|
|
background-color="#383838"
|
|
text-color="#FFFFFF"
|
|
class="relative flex items-center justify-center w-full gap-[4px] px-0 sm:w-[143px] sm:shrink-0 md:h-[56px] md:text-[16px] md:leading-[24px] md:tracking-[-0.48px]"
|
|
@click="handleCouponUse"
|
|
>
|
|
<span>{{ tm('Coupon_Registration') }}</span>
|
|
</AtomsButton>
|
|
</div>
|
|
|
|
<ul class="relative flex flex-col items-start justify-start w-full">
|
|
<template v-for="notice in tm('Coupon_Notice_List')" :key="notice">
|
|
<li
|
|
class="relative flex items-start justify-start w-full pl-[20px] before:content-[''] before:absolute before:top-[10px] before:left-[9px] before:w-[3px] before:h-[3px] before:rounded-full before:bg-[#666666] text-left text-[#666666] text-[13px] font-[400] leading-[22px] tracking-[-0.325px] md:text-[14px] md:leading-[24px] md:tracking-[-0.42px] md:before:left-[8px] md:before:w-[4px] md:before:h-[4px]"
|
|
>
|
|
<span
|
|
v-dompurify-html="
|
|
getKeyToCouponUrl(tm(notice as string) as string)
|
|
"
|
|
></span>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="section-static">
|
|
<WidgetsFixSubTitle
|
|
:title="tm('Coupon_Section_History_Title')"
|
|
class="flex-col"
|
|
>
|
|
<div
|
|
class="relative flex flex-col items-start justify-start gap-[12px] w-full mt-[4px] md:gap-[16px] md:mt-[0] lg:flex-row lg:items-end lg:justify-between lg:gap-[0]"
|
|
>
|
|
<div
|
|
class="relative flex items-center justify-start gap-[20px] w-full lg:w-auto lg:mr-auto"
|
|
>
|
|
<div
|
|
v-if="breakpoints.isMd || breakpoints.isDesktop"
|
|
class="relative flex items-center justify-start"
|
|
>
|
|
<template v-for="month in monthSelectList" :key="month">
|
|
<AtomsButton
|
|
type="action"
|
|
size="size-small"
|
|
background-color="#FAFAFA"
|
|
text-color="#CCCCCC"
|
|
:class="[
|
|
'btn-period',
|
|
{ 'btn-period-active': currentSearchPeriod === month },
|
|
]"
|
|
@click="handlePeriodSelect(month)"
|
|
>
|
|
<span>{{ tm(`Coupon_Month${month}`) }}</span>
|
|
</AtomsButton>
|
|
</template>
|
|
</div>
|
|
|
|
<div
|
|
class="relative flex items-center justify-center gap-[8px] w-full md:w-auto"
|
|
>
|
|
<div
|
|
class="relative flex items-center justify-center gap-[4px] md:gap-[8px] w-full md:w-auto"
|
|
>
|
|
<BlocksDatePicker
|
|
:key="getTime(startDate)"
|
|
:date="startDate"
|
|
@update:date="
|
|
(date: string) => setCouponDate(new Date(date), 'start')
|
|
"
|
|
/>
|
|
<span
|
|
class="relative inline-flex items-center justify-center shrink-0 text-[#868078] text-[16px] font-[500] leading-[24px] tracking-[-0.48px] md:text-[18px] md:leading-[26px] md:tracking-[-0.54px]"
|
|
>
|
|
~
|
|
</span>
|
|
<BlocksDatePicker
|
|
:key="getTime(endDate)"
|
|
:date="endDate"
|
|
@update:date="
|
|
(date: string) => setCouponDate(new Date(date), 'end')
|
|
"
|
|
/>
|
|
</div>
|
|
<AtomsButton
|
|
type="action"
|
|
size="size-small"
|
|
background-color="#383838"
|
|
text-color="#FFFFFF"
|
|
class="shrink-0 w-[40px] h-[40px] p-0 md:w-auto md:px-[22px]"
|
|
@click="handlePeriodSearch"
|
|
>
|
|
<span
|
|
class="relative flex items-center justify-center gap-[2px]"
|
|
>
|
|
<span v-if="breakpoints.isMd || breakpoints.isDesktop">
|
|
{{ tm('Coupon_Search') }}
|
|
</span>
|
|
<AtomsIconsSearchLine :size="16" color="#FFFFFF" />
|
|
</span>
|
|
</AtomsButton>
|
|
</div>
|
|
</div>
|
|
<p
|
|
class="text-[#666666] text-[13px] font-[400] leading-[22px] tracking-[-0.325px] md:text-[14px] md:leading-[24px] md:tracking-[-0.421px]"
|
|
>
|
|
{{ tm('Coupon_Section_History_Description') }}
|
|
</p>
|
|
</div>
|
|
</WidgetsFixSubTitle>
|
|
|
|
<div
|
|
class="relative w-full border border-solid border-[#D9D9D9] rounded-[12px] overflow-hidden sm:rounded-[16px]"
|
|
>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th class="w-auto !border-l-[0]">
|
|
{{ tm('Coupon_Item_Name') }}
|
|
</th>
|
|
<th
|
|
v-if="breakpoints.isMd || breakpoints.isDesktop"
|
|
class="w-[260px]"
|
|
>
|
|
{{ tm('Coupon_Item_RegistDate') }}
|
|
</th>
|
|
<th class="w-[1%] whitespace-nowrap">
|
|
{{ tm('Coupon_Item_Status') }}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template v-if="hasNoCouponList">
|
|
<tr>
|
|
<td
|
|
:colspan="
|
|
breakpoints.isMd || breakpoints.isDesktop ? '3' : '2'
|
|
"
|
|
class="!border-l-[0] !border-b-[0]"
|
|
>
|
|
<p>{{ tm('Coupon_Has_Not_Item') }}</p>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
<template v-else>
|
|
<template
|
|
v-for="(coupon, couponIdx) in couponList"
|
|
:key="coupon.coupon_box_id"
|
|
>
|
|
<tr>
|
|
<td
|
|
class="w-auto"
|
|
:class="
|
|
couponIdx === couponList.length - 1
|
|
? '!border-l-[0] !border-b-[0]'
|
|
: '!border-l-[0]'
|
|
"
|
|
>
|
|
<p>{{ coupon.coupon_name }}</p>
|
|
</td>
|
|
<td
|
|
v-if="breakpoints.isMd || breakpoints.isDesktop"
|
|
:class="
|
|
couponIdx === couponList.length - 1
|
|
? 'w-[260px] !border-b-[0]'
|
|
: 'w-[260px]'
|
|
"
|
|
>
|
|
<p>
|
|
{{
|
|
globalDateFormat(new Date(coupon.register_date), locale)
|
|
}}
|
|
</p>
|
|
</td>
|
|
<td
|
|
:class="
|
|
couponIdx === couponList.length - 1
|
|
? '!px-[16px] md:!px-[20px] w-[1%] whitespace-nowrap !border-b-[0]'
|
|
: '!px-[16px] md:!px-[20px] w-[1%] whitespace-nowrap'
|
|
"
|
|
>
|
|
<template
|
|
v-if="
|
|
coupon.use_state === 1 &&
|
|
coupon.reward_type_code.includes(2)
|
|
"
|
|
>
|
|
<AtomsButton
|
|
type="action"
|
|
size="size-small"
|
|
background-color="transparent"
|
|
text-color="transparent"
|
|
class="coupon-item coupon-item-use"
|
|
@click="handleGoToCouponBox"
|
|
>
|
|
<span>{{ tm('Coupon_Item_Use') }}</span>
|
|
</AtomsButton>
|
|
</template>
|
|
<template v-else-if="coupon.use_state === 1">
|
|
<span class="coupon-item coupon-item-not-used">
|
|
{{ tm('Coupon_Item_Not_Used') }}
|
|
</span>
|
|
</template>
|
|
<template v-else-if="coupon.use_state === 2">
|
|
<span class="coupon-item coupon-item-used">
|
|
{{ tm('Coupon_Item_Used') }}
|
|
</span>
|
|
</template>
|
|
<template v-else-if="coupon.use_state === 4">
|
|
<span class="coupon-item coupon-item-expires">
|
|
{{ tm('Coupon_Item_Expires') }}
|
|
</span>
|
|
</template>
|
|
<template v-else-if="coupon.use_state === 6">
|
|
<span class="coupon-item coupon-item-deadline">
|
|
{{ tm('Coupon_Item_Deadline') }}
|
|
</span>
|
|
</template>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<BlocksPagination
|
|
:key="pageNo"
|
|
:current-page="pageNo"
|
|
:page-size="pageSize"
|
|
:page-block="getPageBlock(breakpoints.isMobile ? 'mobile' : 'desktop')"
|
|
:total-count="totalCount"
|
|
:use-min-max="true"
|
|
@update:page="handlePaging"
|
|
/>
|
|
</section>
|
|
</div>
|
|
|
|
<ClientOnly>
|
|
<Teleport to="body">
|
|
<BlocksModalLayer
|
|
:is-open="isSelectCharacterModalOpen"
|
|
:is-show-dimmed="true"
|
|
:is-outside-close="true"
|
|
modal-name="modal-coupon-character-select"
|
|
area-class="max-w-[480px] p-6 bg-white rounded-[20px]"
|
|
close-class="hidden"
|
|
>
|
|
<div
|
|
class="relative flex flex-col items-center justify-center gap-[24px] w-full"
|
|
>
|
|
<p
|
|
v-dompurify-html="tm('Coupon_Alert_SelectCharacter')"
|
|
class="relative flex items-start justify-center w-full text-center text-[#333333] text-[15px] font-[400] leading-[24px] tracking-[-0.45px]"
|
|
></p>
|
|
|
|
<AtomsSelect
|
|
:options="sortedCharacterList"
|
|
label-name="formatted_nm"
|
|
:placeholder="tm('Coupon_Alert_EnterCharacter')"
|
|
@update:model-value="updateSelectCharacter"
|
|
/>
|
|
|
|
<div
|
|
class="relative flex items-center justify-center gap-[8px] w-full"
|
|
>
|
|
<AtomsButtonModal
|
|
variant="outlined"
|
|
class="max-w-[128px]"
|
|
@click="closeSelectCharacterModal"
|
|
>
|
|
{{ tm('Text_Cancel') }}
|
|
</AtomsButtonModal>
|
|
<AtomsButtonModal
|
|
:disabled="isSelectCharacter ? false : true"
|
|
class="max-w-[128px]"
|
|
@click="handleCouponRegister"
|
|
>
|
|
{{ tm('Text_Confirm') }}
|
|
</AtomsButtonModal>
|
|
</div>
|
|
</div>
|
|
</BlocksModalLayer>
|
|
</Teleport>
|
|
</ClientOnly>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Button Style */
|
|
.btn-period {
|
|
@apply border border-solid border-[#D9D9D9] rounded-[0] w-auto h-[40px] px-[16px] font-[700] before:content-none after:content-none
|
|
transition-all duration-300 ease-in-out
|
|
hover:z-[4] hover:!bg-[#FFFFFF] hover:border-[#404040] hover:!text-[#1F1F1F];
|
|
}
|
|
.btn-period + .btn-period {
|
|
@apply ml-[-1px];
|
|
}
|
|
.btn-period:first-child {
|
|
@apply rounded-l-[8px];
|
|
}
|
|
.btn-period:last-child {
|
|
@apply rounded-r-[8px];
|
|
}
|
|
.btn-period.btn-period-active {
|
|
@apply z-[4] !bg-[#FFFFFF] !border-[#404040] !text-[#1F1F1F];
|
|
}
|
|
|
|
/* Table Style */
|
|
table {
|
|
@apply w-full h-auto border-collapse border-spacing-0 table-auto;
|
|
}
|
|
table th {
|
|
@apply py-[8px] px-[12px] border border-[#D9D9D9] border-t-[0] border-r-[0] bg-[#FAFAFA] text-[#1F1F1F] text-[14px] font-bold leading-[24px] tracking-[-0.42px]
|
|
md:py-[11px] md:px-[20px] md:text-[16px] md:leading-[26px] md:tracking-[-0.48px];
|
|
}
|
|
table td {
|
|
@apply h-[auto] p-[12px] border border-[#D9D9D9] border-t-[0] border-r-[0] bg-[#FFFFFF] text-center text-[#666666] text-[14px] font-[400] leading-[24px] tracking-[-0.42px]
|
|
md:px-[20px] md:text-[16px] md:leading-[26px] md:tracking-[-0.48px];
|
|
}
|
|
table td p {
|
|
@apply line-clamp-2 text-center;
|
|
}
|
|
/* Coupon Item Style */
|
|
.coupon-item {
|
|
@apply relative inline-flex items-center justify-center !w-auto !h-auto py-[2px] px-[6px] border-none rounded-full !bg-[#EBEBEB] text-center !text-[#999999] text-[12px] font-[500] leading-[18px] tracking-[-0.24px]
|
|
md:py-[4px] md:px-[8px] md:text-[14px] md:leading-[20px] md:tracking-[-0.42px];
|
|
}
|
|
.coupon-item.coupon-item-use {
|
|
@apply !bg-[#383838] !text-white;
|
|
}
|
|
.coupon-item.coupon-item-used {
|
|
@apply !bg-[#E2EAFF] !text-[#3C75FF];
|
|
}
|
|
.coupon-item.coupon-item-not-used {
|
|
@apply !bg-[#EEF6F1] !text-[#2B9450];
|
|
}
|
|
</style>
|