diff --git a/layers/assets/data/countryData.ts b/layers/assets/data/countryData.ts new file mode 100644 index 0000000..e9f4ff1 --- /dev/null +++ b/layers/assets/data/countryData.ts @@ -0,0 +1,44 @@ +/** + * 국가 번호 정의 + */ +const countryDialingCodes: Record = { + KR: '82', // 대한민국 + US: '1', // 미국 + GB: '44', + CA: '1', // 캐나다 + AU: '61', + IE: '353', + NZ: '64', + ZA: '27', + IN: '91', + SG: '65', + PH: '63', + JP: '81', + DE: '49', + AT: '43', + CH: '41', + LI: '423', + FR: '33', + BE: '32', + LU: '352', + MC: '377', + PT: '351', + BR: '55', + ES: '34', + MX: '52', + AR: '54', + CO: '57', + PE: '51', + VE: '58', + CL: '56', + EC: '593', + GT: '502', + CU: '53', + TH: '66', + TW: '886', // 대만 + HK: '852', // 홍콩 + MO: '853', // 마카오 + CN: '86', +} + +export { countryDialingCodes } diff --git a/layers/components/blocks/modal/Alert.vue b/layers/components/blocks/modal/Alert.vue index e2da05f..568bd06 100644 --- a/layers/components/blocks/modal/Alert.vue +++ b/layers/components/blocks/modal/Alert.vue @@ -18,9 +18,9 @@ const { tm } = useI18n() const isOpen = defineModel('isOpen', { default: false }) -const setButtonEvent = (event?: () => void | void) => { - if (typeof event === 'function') { - return event() +const setButtonEvent = (event?: () => void) => { + if (event) { + event() } isOpen.value = false } diff --git a/layers/components/layouts/Main.vue b/layers/components/layouts/Main.vue index 8bdd6bf..80160cd 100644 --- a/layers/components/layouts/Main.vue +++ b/layers/components/layouts/Main.vue @@ -19,7 +19,7 @@ const mainRef = ref() const { getTemplateComponent } = useTemplateRegistry() // 개별 메타 태그 표시 여부 확인 -const shouldShowMetaTag = computed(() => props.pageData.meta_tag_type === 2) +const shouldShowMetaTag = computed(() => props.pageData?.meta_tag_type === 2) // 템플릿 표시 여부 확인 const isTemplateVisible = (template: PageDataTemplate): boolean => { @@ -31,7 +31,7 @@ const isTemplateVisible = (template: PageDataTemplate): boolean => { // 템플릿 목록 계산 const visibleTemplates = computed(() => - Object.values(props.pageData.templates).filter(isTemplateVisible) + Object.values(props.pageData?.templates).filter(isTemplateVisible) ) // SEO 메타 태그 설정 diff --git a/layers/components/widgets/modal/Preregist.vue b/layers/components/widgets/modal/Preregist.vue new file mode 100644 index 0000000..957a164 --- /dev/null +++ b/layers/components/widgets/modal/Preregist.vue @@ -0,0 +1,660 @@ + + + + + diff --git a/layers/composables/useGds.ts b/layers/composables/useGds.ts new file mode 100644 index 0000000..646b003 --- /dev/null +++ b/layers/composables/useGds.ts @@ -0,0 +1,119 @@ +import type { + ReqGetGdsClientPolicyTotal, + ResGetGdsClientPolicyTotal, +} from '#layers/types/GdsType' +import { DEFAULT_LOCALE_CODE } from '@/i18n.config' + +/** + * GDS + * + * @description https://wiki.smilegate.net/display/SDKAPIDOCU/51-09.+gds + */ +const useGds = () => { + const logPrefix = { + exception: '[Exception] /composables/useGds', + failure: '[Failure] /composables/useGds', + } + + const countryCode = ref('') // 국가 코드 + const isKorea = ref(false) // 대한민국 여부 + const isTaiwanHongKongMacau = ref(false) // 대홍마 여부 + const isNorthAmerica = ref(false) // 북미 여부 + + // [Setter] 사전등록 - 국가 코드 세팅 + const setCountryCode = (newCountryCode: string) => { + countryCode.value = newCountryCode + } + // [Setter] 사전등록 - 대한민국 여부 세팅 + const setIsKorea = (newIsKorea: boolean) => { + isKorea.value = newIsKorea + } + // [Setter] 사전등록 - 대홍마 여부 세팅 + const setIsTaiwanHongKongMacau = (newIsTaiwanHongKongMacau: boolean) => { + isTaiwanHongKongMacau.value = newIsTaiwanHongKongMacau + } + // [Setter] 사전등록 - 북미 여부 세팅 + const setIsNorthAmerica = (newIsNorthAmerica: boolean) => { + isNorthAmerica.value = newIsNorthAmerica + } + + // 사전 등록 - 특정 국가 여부 조회 (IP 기반) + const checkCountryByIp = async (req: ReqGetGdsClientPolicyTotal) => { + let res: ResGetGdsClientPolicyTotal + const arrKorea = ['KR'] // cf. UM: 미국령 군소 제도의 기 미국령 군소 제도, VI: 미국령 버진아일랜드의 기 미국령 버진아일랜드 + const arrNorthAmerica = ['US', 'CA'] // cf. UM: 미국령 군소 제도의 기 미국령 군소 제도, VI: 미국령 버진아일랜드의 기 미국령 버진아일랜드 + const arrTaiwanHongKongMacau = ['TW', 'HK', 'MO'] // 대만, 홍콩, 마카오 국가 코드 + try { + if (`${req.runType}` !== 'live' && req.qc != null && req.qc !== '') { + // Live가 아닌 환경에서 qc가 있으면 국가 코드로 설정하여 판별 + setCountryCode(req.qc || '') + setIsKorea(arrKorea.includes(req.qc)) + setIsTaiwanHongKongMacau(arrTaiwanHongKongMacau.includes(req.qc)) + setIsNorthAmerica(arrNorthAmerica.includes(req.qc)) + + res = { + code: 0, + message: '', + res_code: 0, + res_data: { + is_default: true, + nation: req.qc, + regulation: '', + timezone: '', + utc_offset: 0, + lang: req.client_lang || DEFAULT_LOCALE_CODE, + coverages: [], + }, + } as ResGetGdsClientPolicyTotal + } else { + const config = useRuntimeConfig() + const stoveApiBaseUrl = config.public.stoveApiUrl + + const url = `${stoveApiBaseUrl}/gds/v2/client/policy/total` + const query = { + policy_grp: req.policy_grp || 'onstove', + device_nation: req.device_nation || 'KR', + client_lang: req.client_lang || DEFAULT_LOCALE_CODE, + include_coverages: req.include_coverages || false, + } + res = (await commonFetch('GET', url, { + query, + })) as ResGetGdsClientPolicyTotal + + if (res != null && res.res_code === 0) { + // is_default = false : 국가코드를 정상 처리했으나 타임존을 처리하지 못한 경우 + // is_default = true : device_nation 또는 policy_grp 별 디폴트 값으로 처리된 경우 + if (res.res_data != null && res.res_data.nation != null) { + setCountryCode(res.res_data.nation || '') + setIsKorea(arrKorea.includes(res.res_data.nation)) + setIsTaiwanHongKongMacau( + arrTaiwanHongKongMacau.includes(res.res_data.nation) + ) + setIsNorthAmerica(arrNorthAmerica.includes(res.res_data.nation)) + } + } + } + } catch (e) { + console.error(`${logPrefix.exception}.checkCountryByIp: `, e) + res = { + code: -99999, + message: `${e}`, + } + setCountryCode('') + setIsKorea(false) + setIsTaiwanHongKongMacau(false) + setIsNorthAmerica(false) + } + return res + } + + return { + isKorea, + isTaiwanHongKongMacau, + isNorthAmerica, + countryCode, + checkCountryByIp, + } +} + +export { useGds } diff --git a/layers/composables/usePreregist.ts b/layers/composables/usePreregist.ts new file mode 100644 index 0000000..fc14c56 --- /dev/null +++ b/layers/composables/usePreregist.ts @@ -0,0 +1,242 @@ +import type { + ReqPreorderSelectEvent, + ResPreorderSelectEvent, + ReqPreorderReserveDataUpdate, + ResPreorderReserveDataUpdate, +} from '#layers/types/PreregistType' + +import { DEFAULT_LOCALE_CODE } from '@/i18n.config' +import { countryDialingCodes } from '#layers/assets/data/countryData' + +/** + * 프로모션 - 사전등록 + */ +const usePreregist = () => { + const promotion = ref({ + id: 'preregist', + title: '사전등록 프로모션', + link: '/#preregist', + startDate: '2025-07-09 10:00:00', + endDate: '2025-12-31 23:59:59', + }) // 사전 등록 프로모션 정보 + const preregistDate = ref(Date.now()) // 사전 등록일 + + // [Setter] 사전등록 - 사전 등록일 세팅 + const setPreregistDate = (newPreregistDate: number) => { + preregistDate.value = newPreregistDate + } + + // 프로모션 기간 여부 + const isPromotionPeriod = computed(() => { + if ( + !promotion.value || + !promotion.value.startDate || + !promotion.value.endDate + ) { + return false + } + // return isBetweenPeriod( + // kstStringToTimestamp(promotion.value.startDate), + // kstStringToTimestamp(promotion.value.endDate) + // ) + }) + // 국가 번호 조회 + const countryDialingCode = computed(() => { + return countryDialingCodes[countryCode.value.toUpperCase()] + }) + + // 사전 등록 - 특정 국가 여부 조회 (IP 기반) + const { + isKorea, + isTaiwanHongKongMacau, + isNorthAmerica, + countryCode, + checkCountryByIp, + } = useGds() + + // 사전 등록 - (등록 여부) 조회 + const getPreregist = async (req: ReqPreorderSelectEvent) => { + let res: ResPreorderSelectEvent = {} as ResPreorderSelectEvent + try { + const config = useRuntimeConfig() + const stoveApiBaseUrl = config.public.stoveApiUrl + + const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/SelectEvent` + const headers = { + Authorization: `Bearer ${req.accessToken}`, + } + const body = { + event_code: req.event_code, + lang: req.lang || DEFAULT_LOCALE_CODE, + terms_type: req.terms_type, + } + res = (await commonFetch('POST', url, { + headers, + body, + })) as ResPreorderSelectEvent + if (res != null) { + if (res.code === 0) { + setPreregistDate(res.value?.terms_time_long || Date.now()) + } else if (res.code === -1) { + //= 조회 된 데이터가 없습니다. -> 최초 + } else if (res.code === -90002) { + //= 사전 등록 기간이 아닙니다. + } else if (`${res.code}`.startsWith('401')) { + //= [01] 로그인 후 이용하실 수 있습니다. + res = { code: -90028, message: res.message || '' } // 401은 -90028로 처리 + } else if (res.code === -90028) { + //= [01] 로그인 후 이용하실 수 있습니다. + } else { + // [-90003] 점검 진행 중 + res = { code: res.code, message: res.message || '' } + console.log( + '[Failure] /composables/promotion/usePreregist.getPreregist: ', + res + ) + res.code = -99999 // else 알럿 띄우기 용 세팅 + } + } else { + res = { code: -99999, message: '' } + console.log( + '[Failure] /composables/promotion/usePreregist.getPreregist: ', + res + ) + res.code = -99999 // else 알럿 띄우기 용 세팅 + } + } catch (e) { + console.error( + '[Exception] /composables/promotion/usePreregist.getPreregist: ', + e + ) + res = { + code: -99999, + message: `${e}`, + } + } + return res + } + + // 사전 등록 - 저장 + const setPreregist = async (req: ReqPreorderReserveDataUpdate) => { + let res: ResPreorderReserveDataUpdate = {} as ResPreorderReserveDataUpdate + try { + const config = useRuntimeConfig() + const stoveApiBaseUrl = config.public.stoveApiUrl + + const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/ReserveDataUpdate` + const headers = { + Authorization: `Bearer ${req.accessToken}`, + } + const body = { + necessary_consent1: req.necessary_consent1, + necessary_consent2: req.necessary_consent2, + necessary_consent3: req.necessary_consent3, + event_code: req.event_code, + terms_type: req.terms_type, + c_num: req.c_num, + lang_code: req.lang_code, + hp: req.hp, + email: req.email, + metric_seq: req.metric_seq, //= mcode + g_server: req.g_server, + world_id: req.world_id, + game_unique_num: req.game_unique_num, + event_info1: req.event_info1, // 각 이벤트 필요할때 사용 + event_info2: req.event_info2, // 각 이벤트 필요할때 사용 + event_info3: req.event_info3, // 각 이벤트 필요할때 사용 + event_info4: req.event_info4, // 각 이벤트 필요할때 사용 + under14_terms: req.under14_terms, // 14세미만 진행 하는 구분값 + device_type: req.device_type, // 접속 기기 pc / mobile + country_code: req.country_code, + country_dialing_code: req.country_dialing_code, // 국가 번호 + birth_date: req.birth_date, // 생년월일 - 북미(미국, 캐나다)의 경우 + } + + res = (await commonFetch('POST', url, { + headers, + body, + })) as ResPreorderReserveDataUpdate + + if (res != null) { + if (res.code === 0) { + //= [08] [사전 등록 완료 레이어] 노출 + if ( + res.message != null && + res.message !== '' && + isNumeric(res.message) + ) { + // 사전 등록일이 숫자면 날짜로 변환 + setPreregistDate(Number(res.message)) + } else { + setPreregistDate(Date.now()) + } + } else if (`${res.code}`.startsWith('401')) { + //= [01] 로그인 후 이용하실 수 있습니다. + res = { code: -90028, message: res.message || '' } // 401은 -90028로 처리 + } else if (res.code === -90028) { + //= [01] 로그인 후 이용하실 수 있습니다. + } else if (res.code === -90002) { + //= [02] 사전 등록 기간이 아닙니다. + } else if (res.code === -90018) { + //= [04] 생년 월일을 입력해 주세요. + } else if (res.code === -90022) { + //= [05] 사전 등록 가능한 연령이 아닙니다. + } else if (res.code === -90000) { + //= [06] 필수 약관을 모두 선택해 주세요. + } else if (res.code === -90023) { + //= [07] 이미 사전 등록이 완료된 계정입니다. + if ( + res.message != null && + res.message !== '' && + isNumeric(res.message) + ) { + // 사전 등록일이 숫자면 날짜로 변환 + setPreregistDate(Number(res.message)) + } else { + setPreregistDate(Date.now()) + } + } else { + //= [03] 오류가 발생했습니다.\n계속 오류가 발생하면 고객센터로 문의해 주세요. + res = { code: res.code, message: res.message || '' } + console.log( + '[Failure] /composables/promotion/usePreregist.setPreregist: ', + res + ) + res.code = -99999 // else 알럿 띄우기 용 세팅 + } + } else { + res = { code: -99999, message: '' } + console.log( + '[Failure] /composables/promotion/usePreregist.setPreregist: ', + res + ) + } + } catch (e) { + console.error( + '[Exception] /composables/promotion/usePreregist.setPreregist: ', + e + ) + res = { + code: -99999, + message: `${e}`, + } + } + return res + } + + return { + promotion, + isKorea, + isTaiwanHongKongMacau, + isNorthAmerica, + countryCode, + isPromotionPeriod, + countryDialingCode, + preregistDate, + checkCountryByIp, + getPreregist, + setPreregist, + } +} + +export { usePreregist } diff --git a/layers/templates/FxPreregist01/index.vue b/layers/templates/FxPreregist01/index.vue index 6e63163..e491f40 100644 --- a/layers/templates/FxPreregist01/index.vue +++ b/layers/templates/FxPreregist01/index.vue @@ -2,6 +2,8 @@ import { SplideSlide } from '@splidejs/vue-splide' import { getComponentGroup, getComponentGroupAry } from '#layers/utils/dataUtil' import type { PageDataTemplateComponents } from '#layers/types/api/pageData' +import type { OperateGroupItem } from '#layers/types/api/resourcesData' +import type { Platform } from '#layers/types/components/button' interface Props { components: PageDataTemplateComponents @@ -10,598 +12,357 @@ interface Props { const props = defineProps() -const { tm } = useI18n() - -const test = { - subTitle: { - groups: [ - { - display: { - text: '사전 등록 기간 + 2024.06.04 ~ 정식 오픈 전까지', - color_code: '', - color_name: 'primary', - }, - resource_type: 'TXT', - }, - ], - }, - mainTitle: { - groups: [ - { - display: { - text: '로드나인 사전 등록', - color_code: '', - color_name: 'primary', - }, - resource_type: 'TXT', - }, - ], - }, - background: { - groups: [ - { - res_path: { - path_mo: '/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg_m.jpg', - path_pc: '/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg.jpg', - }, - resource_type: 'IMG_COMM', - }, - ], - }, - txtSnsLink: { - groups: [ - { - display: { - text: 'https://pf.kakao.com/_xmyxjpG', - }, - resource_type: 'TXT', - }, - { - display: { - text: 'https://www.youtube.com/@LORDNINE_KR', - }, - resource_type: 'TXT', - }, - ], - }, - description: { - groups: [ - { - display: { - text: '로드나인은 PC, GooglePlay, AppStore에서 즐기실 수 있습니다', - color_code: '', - color_name: 'text-primary', - }, - resource_type: 'TXT', - }, - ], - }, - imgAccReward: { - groups: [ - { - display: { - text: '골드 100,000', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward01_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward01.png', - }, - resource_type: 'IMG_LANG', - }, - { - display: { - text: '중급 물약 x100', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward02_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward02.png', - }, - resource_type: 'IMG_LANG', - }, - { - display: { - text: '방어구 강화석 x3', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward03_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward03.png', - }, - resource_type: 'IMG_LANG', - }, - { - display: { - text: '무기 강화석 x3', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward04_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward04.png', - }, - resource_type: 'IMG_LANG', - }, - // { - // display: { - // text: '순수한 무기 강화석 x1', - // }, - // res_path: { - // path_mo: - // '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward05_m.png', - // path_pc: - // '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward05.png', - // }, - // resource_type: 'IMG_LANG', - // }, - ], - }, - imgSnsButton: { - groups: [ - { - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button01_m.jpg', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button01.jpg', - }, - tracking: { - click_item: 'imgSnsButton_7. 사전등록-SNS버튼이미지(1)', - action_type: 'click', - click_sarea: 'promotionPreregist_tmpl_01__imgSnsButton', - }, - resource_type: 'IMG_LANG', - }, - { - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button02_m.jpg', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/sns_button02.jpg', - }, - tracking: { - click_item: 'imgSnsButton_7. 사전등록-SNS버튼이미지(2)', - action_type: 'click', - click_sarea: 'promotionPreregist_tmpl_01__imgSnsButton', - }, - resource_type: 'IMG_LANG', - }, - ], - }, - imgSnsReward: { - groups: [ - { - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward_sns_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward_sns.png', - }, - resource_type: 'IMG_LANG', - }, - ], - }, - subTitleAccReward: { - groups: [ - { - display: { - text: '이벤트 기간 + 2024.06.04 ~ 정식 오픈 전까지', - color_code: '#c5902f', - color_name: '', - }, - resource_type: 'TXT', - }, - ], - }, - imgPreregistReward: { - groups: [ - { - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_preregist_reward.png', - }, - resource_type: 'IMG_LANG', - }, - ], - }, - mainTitleAccReward: { - groups: [ - { - display: { - text: '카카오톡 공식 채널 구독 이벤트', - color_code: '#c5902f', - color_name: '', - }, - resource_type: 'TXT', - }, - ], - }, - backgroundAccReward: { - groups: [ - { - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg_acc_reward_m.jpg', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/common/bg_acc_reward.jpg', - }, - resource_type: 'IMG_COMM', - }, - ], - }, - descriptionAccReward: { - groups: [ - { - display: { - text: '※ 달성 여부는 실시간으로 반영되지 않을 수 있습니다.', - color_code: '#737474', - color_name: '', - }, - resource_type: 'TXT', - }, - ], - }, - isAccRewardCompleted: {}, - preregistButtonColor: { - groups: [ - { - display: { - color_code: '#cc0000', - color_name: '', - }, - resource_type: 'COLOR', - }, - { - display: { - color_code: '#ccff00', - color_name: '', - }, - resource_type: 'COLOR', - }, - ], - }, - rewardTitleAccReward: { - groups: [ - { - display: { - text: '누적 목표를 달성할수록 더 많은 보상을 드립니다!', - color_code: '#33312e', - color_name: '', - }, - resource_type: 'TXT', - }, - ], - }, - imgAccRewardIncomplete: { - groups: [ - { - display: { - text: '골드 100,000', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete01_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete01.png', - }, - resource_type: 'IMG_LANG', - }, - { - display: { - text: '중급 물약 x100', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete02_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete02.png', - }, - resource_type: 'IMG_LANG', - }, - { - display: { - text: '방어구 강화석 x3', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete03_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete03.png', - }, - resource_type: 'IMG_LANG', - }, - { - display: { - text: '무기 강화석 x3', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete04_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete04.png', - }, - resource_type: 'IMG_LANG', - }, - { - display: { - text: '순수한 무기 강화석 x1', - }, - res_path: { - path_mo: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete05_m.png', - path_pc: - '/local/template/l9/16/1/1/FX_PREREGIST_01/ko/img_acc_reward_incomplete05.png', - }, - resource_type: 'IMG_LANG', - }, - ], - }, -} as any +const { locale } = useI18n() +const { getOperateResourcesData } = useResourcesData() +const { gameData } = storeToRefs(useGameDataStore()) +const { pageData } = storeToRefs(usePageDataStore()) // Constants -const PREREGIST_PLATFORMS = [ - { platform: 'stove', label: '사전 등록 하기' }, - { platform: 'google_play', label: '사전 등록 하기' }, - { platform: 'app_store', label: '사전 등록 하기' }, -] as const +const COLOR_INDEX = { BACKGROUND: 0, TEXT: 1 } as const +const OS_TYPE_MAP: Record = { + '1': ['google_play'], + '2': ['app_store'], + '3': ['google_play', 'app_store'], +} -const COLOR_INDEX = { - BACKGROUND: 0, - TEXT: 1, -} as const +const preregistModalRef = ref<{ handlePreregist: () => void } | null>(null) // Preregist Section -const backgroundData = computed(() => +const preregistCode = computed( + () => getComponentGroup(props.components, 'preregistCode')?.display?.text +) +const prdBackgroundData = computed(() => getComponentGroup(props.components, 'background') ) -const mainTitleData = computed(() => +const preMainTitleData = computed(() => getComponentGroup(props.components, 'mainTitle') ) -const subTitleData = computed(() => +const preSubTitleData = computed(() => getComponentGroup(props.components, 'subTitle') ) -const imgPreregistRewardData = computed(() => +const preImgPreregistdData = computed(() => getComponentGroup(props.components, 'imgPreregistReward') ) -const imgSnsRewardData = computed(() => +const preImgSnsData = computed(() => getComponentGroup(props.components, 'imgSnsReward') ) -const preregistButtonColorData = computed(() => - getComponentGroupAry(props.components, 'preregistButtonColor') -) -const imgSnsButtonData = computed(() => - getComponentGroupAry(props.components, 'imgSnsButton') -) -const txtSnsLinkData = computed(() => - getComponentGroupAry(props.components, 'txtSnsLink') -) -const descriptionData = computed(() => +const preDescriptionData = computed(() => getComponentGroup(props.components, 'description') ) -const snsButtons = computed(() => { - const buttons = imgSnsButtonData.value - const links = txtSnsLinkData.value +// SNS Buttons +const snsButtonsData = computed(() => { + const buttons = getComponentGroupAry(props.components, 'imgSnsButton') + const links = getComponentGroupAry(props.components, 'txtSnsLink') if (!buttons?.length) return [] return buttons.map((button, index) => ({ image: button, link: links?.[index]?.display?.text ?? '', + id: button.id ?? `sns-${index}`, })) }) -const buttonColors = computed(() => { - const colorData = preregistButtonColorData.value - if (!colorData?.length) { - return { background: undefined, text: undefined } - } +// Button Colors +const buttonColors = computed(() => { + const colorData = getComponentGroupAry( + props.components, + 'preregistButtonColor' + ) + + if (!colorData?.length) + return { backgroundColor: undefined, textColor: undefined } return { - background: getColorCode({ + backgroundColor: getColorCode({ colorName: colorData[COLOR_INDEX.BACKGROUND]?.display?.color_name, colorCode: colorData[COLOR_INDEX.BACKGROUND]?.display?.color_code, }), - text: getColorCode({ + textColor: getColorCode({ colorName: colorData[COLOR_INDEX.TEXT]?.display?.color_name, colorCode: colorData[COLOR_INDEX.TEXT]?.display?.color_code, }), } }) +// Platform Buttons +const platformButtons = computed(() => { + const osType = String(gameData.value?.os_type ?? '') + return OS_TYPE_MAP[osType] ?? [] +}) + // Reward Section -const backgroundAccRewardData = computed(() => +const accBackgroundData = computed(() => getComponentGroup(props.components, 'backgroundAccReward') ) -const mainTitleAccRewardData = computed(() => +const accMainTitleData = computed(() => getComponentGroup(props.components, 'mainTitleAccReward') ) -const subTitleAccRewardData = computed(() => +const accSubTitleData = computed(() => getComponentGroup(props.components, 'subTitleAccReward') ) -const rewardTitleAccRewardData = computed(() => +const accRewardTitleData = computed(() => getComponentGroup(props.components, 'rewardTitleAccReward') ) -const imgAccRewardData = computed(() => - getComponentGroupAry(test, 'imgAccReward') -) -const imgAccRewardLength = computed(() => imgAccRewardData.value?.length ?? 0) -const descriptionAccRewardData = computed(() => +const accDescriptionData = computed(() => getComponentGroup(props.components, 'descriptionAccReward') ) +const accPaginationData = computed(() => + getComponentGroupAry(props.components, 'pagination') +) +// Async Data - 리워드 완료 데이터 +const { data: rewardCompletedData } = await useAsyncData( + `fx-preregist-resources-${pageData.value?.page_seq}-${pageData.value?.page_ver}-${props.pageVerTmplSeq}`, + async () => { + const { page_seq, page_ver } = pageData.value ?? {} + + if (!page_seq || !page_ver) return [] + + try { + const operateGroupList = await getOperateResourcesData({ + pageSeq: page_seq, + pageVer: page_ver, + pageVerTmplSeq: props.pageVerTmplSeq, + langCode: locale.value, + }) + + return getComponentContainer(operateGroupList, 'isAccRewardCompleted', { + isGroup: true, + }) as OperateGroupItem[] + } catch (error) { + if (import.meta.dev) { + // eslint-disable-next-line no-console + console.error('[FxPreregist01] Failed to fetch reward data:', error) + } + return [] + } + }, + { + default: () => [], + server: false, + } +) + +// Reward Images +const rewardImages = computed(() => { + const defaultList = getComponentGroupAry(props.components, 'imgAccReward') + const incompleteList = getComponentGroupAry( + props.components, + 'imgAccRewardIncomplete' + ) + + if (!defaultList?.length) return [] + + return defaultList.map((defaultItem, index) => ({ + id: defaultItem.id ?? `reward-${index}`, + default: defaultItem, + incomplete: incompleteList?.[index] ?? null, + flagType: rewardCompletedData.value?.[index]?.flag_type ?? 0, + })) +}) + +// Splide Options const splideOptions = computed(() => { + const length = rewardImages.value.length + return { - type: 'slide', + type: 'slide' as const, gap: 16, arrows: false, pagination: false, destroy: true, breakpoints: { - [BREAKPOINTS.md - 1]: { - destroy: imgAccRewardLength.value <= 3, - perPage: 'auto', - focus: 'center', - drag: imgAccRewardLength.value > 3, - padding: { - left: 40, - right: 40, - }, + 937: { + destroy: length < 5, + gap: 12, + padding: { left: 40, right: 40 }, }, [BREAKPOINTS.sm - 1]: { + padding: { left: 20, right: 20 }, + }, + 723: { + destroy: length < 4, + }, + 561: { destroy: false, - gap: 12, - drag: true, - padding: { - left: 20, - right: 20, - }, }, }, } }) + +// Handler +const handlePreregistClick = () => { + preregistModalRef.value?.handlePreregist() +} diff --git a/layers/types/GdsType.ts b/layers/types/GdsType.ts new file mode 100644 index 0000000..1a939f6 --- /dev/null +++ b/layers/types/GdsType.ts @@ -0,0 +1,34 @@ +/** + * GDS + */ +interface ReqGetGdsClientPolicyTotal { + policy_grp: string + device_nation?: string + client_lang: string + include_coverages?: boolean + + // QA + runType?: string + qc?: string +} + +interface DtoGetGdsClientPolicyTotal { + is_default: boolean + nation: string + regulation: string + timezone: string + utc_offset: number + lang: string + coverages: Array +} + +interface ResGetGdsClientPolicyTotal { + code?: number + message?: string + res_code?: number + res_message?: string + res_data?: DtoGetGdsClientPolicyTotal + req_data?: ReqGetGdsClientPolicyTotal +} + +export type { ReqGetGdsClientPolicyTotal, ResGetGdsClientPolicyTotal } diff --git a/layers/types/PreregistType.ts b/layers/types/PreregistType.ts new file mode 100644 index 0000000..f7e2a2b --- /dev/null +++ b/layers/types/PreregistType.ts @@ -0,0 +1,102 @@ +interface ReqPreorderSelectEvent { + // Header + accessToken?: string + + // Body + event_code: string + lang: string + terms_type: number // 1 : hp / 2 : email / 3 : 스토브 번호 + hp?: string + email?: string +} +interface DtoResPreorderSelectEvent { + game_code: number + game_id: string + game_name: string + lang_code: string | null + world_id: string | null + stove_num: number + game_unique_num: number + event_code: string + event_name: string + event_info1: string | null + event_info2: string | null + event_info3: number | null + event_info4: number | null + terms_type: number + event_info_set_terms_type: number | null + hp: string | null + email: string | null + terms_yn: number + terms_count: number + terms_time: string | null + cn_send_time: string | null + cn_send_count: number + under14_terms: number + delete_date: string | null + necessary_consent1: string | null + necessary_consent2: string | null + necessary_consent3: string | null + choice_consent: string | null + con_region: string | null + device_type: string | null + metric_seq: number + email_auto_type: string | null + option01: number | null + option02: string | null + option03: number | null + caller_id: string | null + caller_uuid: string | null + user_ip: string | null + chk_age: number + country_code: string | null + country_dialing_code: number | null + birth_date: string | null + terms_time_long: number + g_server: string | null + c_num: number +} +interface ResPreorderSelectEvent extends CommonResponseType { + value?: DtoResPreorderSelectEvent +} + +interface ReqPreorderReserveDataUpdate { + baseApiUrl?: string + + // Header + accessToken?: string + + // Body + necessary_consent1: string + necessary_consent2: string + necessary_consent3: string + event_code: string + terms_type: number // 1 : hp / 2 : email / 3 : 스토브 번호 + c_num?: number + lang_code: string + hp?: string + email?: string + metric_seq?: number //= mcode + g_server?: string + world_id?: string + game_unique_num?: number + event_info1?: string + event_info2?: string + event_info3?: number + event_info4?: number + under14_terms?: string + device_type: string + country_code: string + country_dialing_code?: string + birth_date?: string +} +interface ResPreorderReserveDataUpdate extends CommonResponseType { + value?: number +} + +export type { + ReqPreorderSelectEvent, + ResPreorderSelectEvent, + ReqPreorderReserveDataUpdate, + ResPreorderReserveDataUpdate, +}