Files
web-temp/layers/templates/FxCoupon01/index.vue
2026-01-16 14:55:02 +09:00

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>