452 lines
14 KiB
Vue
452 lines
14 KiB
Vue
<script setup lang="ts">
|
|
import { PREREGIST_ERROR_CODE } from '#layers/composables/usePreregist'
|
|
|
|
interface Props {
|
|
preregistCode?: string
|
|
tm?: (key: string) => string
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
// Composables
|
|
const { locale } = useI18n()
|
|
const device = useDevice()
|
|
const runtimeConfig = useRuntimeConfig()
|
|
const gameDataStore = useGameDataStore()
|
|
const modalStore = useModalStore()
|
|
const { handleTokenValidation } = useTokenValidation()
|
|
const {
|
|
countryCode,
|
|
preregistDate,
|
|
checkCountryByIp,
|
|
getPreregist,
|
|
setPreregist,
|
|
} = usePreregist()
|
|
|
|
const { gameData } = storeToRefs(gameDataStore)
|
|
|
|
// Constants
|
|
const stoveCs = runtimeConfig.public.stoveCs
|
|
const customerServiceUrl = `${stoveCs}/${gameData.value?.game_id}`
|
|
|
|
/**
|
|
* 번역 함수 (Props로 전달받은 tm 또는 key 반환)
|
|
*/
|
|
const tm = (key: string): string => {
|
|
return props.tm?.(key) ?? key
|
|
}
|
|
|
|
const isModalOpen = ref(false)
|
|
const currentStep = ref<1 | 2>(1)
|
|
const isSubmitting = ref(false)
|
|
const isCheckedMarketing = ref(false)
|
|
const isExpandedMarketing = ref(false)
|
|
const isValidated = ref(false) // 검증 완료 여부 (중복 검증 방지)
|
|
|
|
const canSubmit = computed(() => isCheckedMarketing.value)
|
|
const errorMessages = computed<Record<number, string>>(() => ({
|
|
[PREREGIST_ERROR_CODE.NOT_PERIOD]: tm('Preregist_Alert_Date'),
|
|
[PREREGIST_ERROR_CODE.REQUIRED_TERMS]: tm('Preregist_Alert_Agree'),
|
|
[PREREGIST_ERROR_CODE.AGE_RESTRICTION]: tm('Preregist_Alert_Age'),
|
|
[PREREGIST_ERROR_CODE.ALREADY_REGISTERED]: tm('Preregist_Alert_Already'),
|
|
}))
|
|
|
|
const tmWithGameName = (key: string): string => {
|
|
const text = tm(key)
|
|
if (typeof text === 'string' && text.includes('%게임명%')) {
|
|
const gameName = gameData.value?.game_name ?? ''
|
|
return text.replace(/%게임명%/g, gameName)
|
|
}
|
|
return text
|
|
}
|
|
|
|
const toggleMarketing = () => {
|
|
isCheckedMarketing.value = !isCheckedMarketing.value
|
|
}
|
|
|
|
const toggleExpand = (event: Event) => {
|
|
event.stopPropagation()
|
|
isExpandedMarketing.value = !isExpandedMarketing.value
|
|
}
|
|
|
|
/**
|
|
* 에러 모달 표시
|
|
*/
|
|
const showErrorModal = (code: number): void => {
|
|
if (!code) return
|
|
|
|
// 일반 에러 메시지
|
|
const message = errorMessages.value[code]
|
|
if (message) {
|
|
modalStore.handleOpenAlert({ contentText: message })
|
|
return
|
|
}
|
|
|
|
// 로그인 필요
|
|
if (code === PREREGIST_ERROR_CODE.LOGIN_REQUIRED) {
|
|
modalStore.handleOpenConfirm({
|
|
contentText: tm('Alert_StoveLogin'),
|
|
confirmButtonText: tm('Text_StoveLogin'),
|
|
confirmButtonEvent: () => {
|
|
csrGoStoveLogin()
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
// 기타 오류
|
|
modalStore.handleOpenConfirm({
|
|
contentText: tm('Alert_Error'),
|
|
confirmButtonText: tm('Text_Customer'),
|
|
confirmButtonEvent: () => {
|
|
window.open(customerServiceUrl, '_blank')
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 토큰 및 사전등록 여부 검증
|
|
*/
|
|
const checkValidation = async (): Promise<boolean> => {
|
|
if (!props.preregistCode) {
|
|
if (import.meta.dev) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('[Preregist] preregistCode is required')
|
|
}
|
|
return false
|
|
}
|
|
|
|
try {
|
|
// 토큰 검증
|
|
const accessToken = csrGetAccessToken()
|
|
const isValidToken = await handleTokenValidation(accessToken)
|
|
if (!isValidToken) return false
|
|
|
|
// 사전등록 여부 조회
|
|
const result = await getPreregist({
|
|
accessToken,
|
|
event_code: props.preregistCode,
|
|
lang: locale.value,
|
|
terms_type: 3,
|
|
})
|
|
|
|
// 사전등록 가능
|
|
if (result.code === PREREGIST_ERROR_CODE.NO_DATA) {
|
|
return true
|
|
}
|
|
|
|
// 이미 사전등록 완료
|
|
if (result.code === PREREGIST_ERROR_CODE.SUCCESS) {
|
|
showErrorModal(PREREGIST_ERROR_CODE.ALREADY_REGISTERED)
|
|
return false
|
|
}
|
|
|
|
// 기타 오류
|
|
showErrorModal(result.code)
|
|
return false
|
|
} catch (error) {
|
|
if (import.meta.dev) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('[Preregist.checkValidation]', error)
|
|
}
|
|
showErrorModal(PREREGIST_ERROR_CODE.UNKNOWN)
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 사전등록 모달 오픈
|
|
*/
|
|
const handleOpenPreregist = async (): Promise<void> => {
|
|
if (isSubmitting.value) return
|
|
|
|
const isValid = await checkValidation()
|
|
if (!isValid) return
|
|
|
|
// 국가 정보 조회
|
|
if (!countryCode.value) {
|
|
await checkCountryByIp({
|
|
policy_grp: 'onstove',
|
|
device_nation: csrGetCountry().toUpperCase(),
|
|
client_lang: locale.value,
|
|
include_coverages: false,
|
|
qc: csrGetQc(),
|
|
runType: runtimeConfig.public.runType,
|
|
})
|
|
}
|
|
|
|
isValidated.value = true // 검증 완료 플래그
|
|
isModalOpen.value = true
|
|
currentStep.value = 1
|
|
}
|
|
|
|
/**
|
|
* 사전등록 제출
|
|
*/
|
|
const handleSubmit = async (): Promise<void> => {
|
|
if (!props.preregistCode) return
|
|
if (isSubmitting.value || currentStep.value !== 1) return
|
|
|
|
// 유효성 검사
|
|
if (!canSubmit.value) {
|
|
showErrorModal(PREREGIST_ERROR_CODE.REQUIRED_TERMS)
|
|
return
|
|
}
|
|
|
|
// 이미 검증을 통과한 경우 재검증 스킵
|
|
if (!isValidated.value) {
|
|
const isValid = await checkValidation()
|
|
if (!isValid) return
|
|
}
|
|
|
|
isSubmitting.value = true
|
|
try {
|
|
const result = await setPreregist({
|
|
accessToken: csrGetAccessToken(),
|
|
event_code: props.preregistCode,
|
|
lang_code: locale.value,
|
|
terms_type: 3,
|
|
device_type: device.isMobile ? 'mobile' : 'pc',
|
|
country_code: countryCode.value || 'KR',
|
|
necessary_consent1: 'Y',
|
|
necessary_consent2: 'Y',
|
|
necessary_consent3: isCheckedMarketing.value ? 'Y' : 'N',
|
|
birth_date: '',
|
|
})
|
|
|
|
if (result.code === PREREGIST_ERROR_CODE.SUCCESS) {
|
|
currentStep.value = 2
|
|
return
|
|
}
|
|
|
|
showErrorModal(result.code)
|
|
} catch (error) {
|
|
if (import.meta.dev) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('[Preregist.handleSubmit]', error)
|
|
}
|
|
showErrorModal(PREREGIST_ERROR_CODE.UNKNOWN)
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 모달 닫기 및 상태 초기화
|
|
*/
|
|
const handleCloseModal = (): void => {
|
|
if (isSubmitting.value) return
|
|
|
|
currentStep.value = 1
|
|
isCheckedMarketing.value = false
|
|
isExpandedMarketing.value = false
|
|
isSubmitting.value = false
|
|
isValidated.value = false // 검증 플래그도 초기화
|
|
}
|
|
|
|
defineExpose({
|
|
handleOpenPreregist,
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<BlocksModalLayer
|
|
v-model:is-open="isModalOpen"
|
|
area-class="h-full bg-[#292929] pt-[60px] md:w-[476px] md:h-[680px] md:pt-[64px] md:rounded-[20px] md:shadow-[0_2px_4px_rgba(0,0,0,0.06)]"
|
|
close-class="absolute top-[19px] right-[26px] md:top-[20px] md:right-[24px]"
|
|
@close="handleCloseModal"
|
|
>
|
|
<!-- Step 1: Terms Agreement -->
|
|
<div v-if="currentStep === 1" class="flex flex-col h-full">
|
|
<div class="flex gap-5 px-5 pt-5 pb-[12px] md:px-10 md:pt-6 md:pb-[16px]">
|
|
<h4
|
|
class="flex-1 text-xl font-bold leading-[30px] tracking-[-0.6px] text-[#ebebeb] md:text-2xl md:leading-[34px] md:tracking-[-0.72px]"
|
|
>
|
|
{{ tm('Preregist_Modal_Title01') }}
|
|
</h4>
|
|
<div
|
|
class="flex h-[30px] items-center gap-1 text-base leading-6 tracking-[-0.48px] md:h-[34px]"
|
|
>
|
|
<span class="font-bold text-[#b2b2b2]">1</span>
|
|
<span class="text-[#666666]">/</span>
|
|
<span class="text-[#666666]">2</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- content area -->
|
|
<div class="overflow-hidden relative">
|
|
<div
|
|
class="absolute left-0 right-0 top-0 bg-gradient-to-b from-[#292929] to-transparent z-[1] h-[24px] md:h-[32px]"
|
|
></div>
|
|
<div
|
|
class="overflow-y-auto h-full py-[24px] px-5 md:py-[32px] md:px-10"
|
|
>
|
|
<div class="px-3 py-4 md:px-6">
|
|
<div class="flex cursor-pointer items-center gap-3 md:gap-4">
|
|
<div class="shrink-0">
|
|
<AtomsIconsCheckBoldLine
|
|
:color="isCheckedMarketing ? 'var(--primary)' : '#666666'"
|
|
/>
|
|
</div>
|
|
<span
|
|
class="flex-1 text-sm font-medium leading-6 tracking-[-0.42px] text-[#ebebeb] md:text-[15px] md:tracking-[-0.45px]"
|
|
@click="toggleMarketing"
|
|
>
|
|
{{ tmWithGameName('Preregist_Agree_News') }}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
class="flex items-center justify-center transition-transform duration-200"
|
|
:class="{ 'rotate-180': isExpandedMarketing }"
|
|
@click="toggleExpand($event)"
|
|
>
|
|
<AtomsIconsArrowDownLine />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Marketing Detail Content -->
|
|
<div
|
|
v-if="isExpandedMarketing"
|
|
class="mt-4 max-h-[160px] overflow-y-auto rounded-lg bg-white/[0.04] px-4 py-3"
|
|
>
|
|
<p
|
|
v-dompurify-html="tmWithGameName('Preregist_Agree_News_Info')"
|
|
class="text-[13px] font-normal leading-[22px] tracking-[-0.325px] text-[#b2b2b2]"
|
|
></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="absolute left-0 right-0 bottom-0 bg-gradient-to-t from-[#292929] to-transparent z-[1] h-[24px] md:h-[32px]"
|
|
></div>
|
|
</div>
|
|
|
|
<div class="mt-auto px-5 pb-10 md:px-10 md:pb-12">
|
|
<AtomsButton
|
|
class="w-full"
|
|
button-size="size-small md:size-medium"
|
|
:disabled="!canSubmit || isSubmitting"
|
|
@click="handleSubmit"
|
|
>
|
|
{{ tm('Preregist_Btn_Preegist') }}
|
|
</AtomsButton>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Success -->
|
|
<div v-if="currentStep === 2" class="flex flex-1 flex-col h-full">
|
|
<div class="flex gap-5 px-5 pb-10 pt-5 md:px-10 md:pb-12 md:pt-6">
|
|
<h4
|
|
class="flex-1 text-xl font-bold leading-[30px] tracking-[-0.6px] text-[#ebebeb] md:text-2xl md:leading-[34px] md:tracking-[-0.72px]"
|
|
>
|
|
{{ tm('Preregist_Modal_Title02') }}
|
|
</h4>
|
|
<div
|
|
class="flex h-[30px] items-center gap-1 text-base leading-6 tracking-[-0.48px] md:h-[34px]"
|
|
>
|
|
<span class="font-bold text-[#b2b2b2]">2</span>
|
|
<span class="text-[#666666]">/</span>
|
|
<span class="text-[#666666]">2</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-10 px-5 pb-10 md:px-10">
|
|
<!-- Success Info -->
|
|
<div
|
|
class="flex flex-col gap-1 rounded-lg border border-white/10 bg-[#383838] px-5 py-4 md:gap-2 md:px-6"
|
|
>
|
|
<p
|
|
class="text-[13px] font-normal leading-[22px] tracking-[-0.325px] text-[#b2b2b2] opacity-50 md:text-[15px] md:leading-6 md:tracking-[-0.45px]"
|
|
>
|
|
{{ preregistDate }}
|
|
</p>
|
|
<h3
|
|
class="text-xl font-bold leading-[30px] tracking-[-0.6px] text-[#ebebeb] md:text-2xl md:leading-[34px] md:tracking-[-0.72px]"
|
|
>
|
|
{{ gameData?.game_name }}
|
|
</h3>
|
|
<p
|
|
class="text-[13px] font-normal leading-[22px] tracking-[-0.325px] text-[#ebebeb] md:text-[15px] md:leading-6 md:tracking-[-0.45px]"
|
|
>
|
|
{{ tm('Preregist_Agree_News_Complete') }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- STOVE App Download -->
|
|
<div class="flex flex-col gap-5">
|
|
<p
|
|
class="text-left text-sm font-medium leading-6 tracking-[-0.42px] text-[#ebebeb] md:text-center md:text-base md:leading-[26px] md:tracking-[-0.48px]"
|
|
>
|
|
{{ tm('Preregist_Stove_Download') }}
|
|
</p>
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="flex size-[108px] p-4 shrink-0 items-center justify-center rounded-lg bg-white/[0.04] backdrop-blur-[15px] md:size-[124px] md:p-4.5"
|
|
>
|
|
<AtomsImg
|
|
src="/images/common/stove_app_qr.png"
|
|
alt="STOVE APP QR Code"
|
|
image-type="common"
|
|
class="w-full h-full object-contain"
|
|
/>
|
|
</div>
|
|
<div class="flex flex-1 flex-col gap-3">
|
|
<a
|
|
href="https://play.google.com/store/search?q=stove&c=apps"
|
|
target="_blank"
|
|
class="flex h-12 w-full items-center justify-center gap-1.5 rounded-lg bg-white/[0.04] px-8 pl-8 pr-10 text-sm font-medium leading-5 tracking-[-0.42px] text-white no-underline backdrop-blur-[15px] transition-colors duration-200 hover:bg-white/[0.08] md:h-14 md:gap-2 md:text-base md:leading-6 md:tracking-[-0.48px]"
|
|
>
|
|
<AtomsIconsLogoGoogle />
|
|
<span>Google Play</span>
|
|
</a>
|
|
<a
|
|
href="https://apps.apple.com/app/stove-app-stove-app/id1342134971"
|
|
target="_blank"
|
|
class="flex h-12 w-full items-center justify-center gap-1.5 rounded-lg bg-white/[0.04] px-8 pl-8 pr-10 text-sm font-medium leading-5 tracking-[-0.42px] text-white no-underline backdrop-blur-[15px] transition-colors duration-200 hover:bg-white/[0.08] md:h-14 md:gap-2 md:text-base md:leading-6 md:tracking-[-0.48px]"
|
|
>
|
|
<AtomsIconsLogoApple />
|
|
<span>App Store</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</BlocksModalLayer>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.modal-wrap {
|
|
@apply p-0 md:p-5;
|
|
}
|
|
.modal-wrap:deep(.modal-content) {
|
|
@apply h-full;
|
|
}
|
|
.modal-wrap:deep(.modal-close) svg {
|
|
@apply fill-white;
|
|
}
|
|
|
|
/* Custom scrollbar for accordion content */
|
|
:deep(.overflow-y-auto) {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
|
|
}
|
|
|
|
:deep(.overflow-y-auto::-webkit-scrollbar) {
|
|
width: 4px;
|
|
}
|
|
|
|
:deep(.overflow-y-auto::-webkit-scrollbar-track) {
|
|
background: transparent;
|
|
}
|
|
|
|
:deep(.overflow-y-auto::-webkit-scrollbar-thumb) {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-radius: 999px;
|
|
}
|
|
|
|
:deep(.overflow-y-auto::-webkit-scrollbar-thumb:hover) {
|
|
background: rgba(255, 255, 255, 0.25);
|
|
}
|
|
</style>
|