|
@@ -3,6 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|
|
import lottie from 'lottie-web'
|
|
import lottie from 'lottie-web'
|
|
|
import { clearSession, loadSession, logout, saveSession, socialLogin } from './services/auth'
|
|
import { clearSession, loadSession, logout, saveSession, socialLogin } from './services/auth'
|
|
|
import { disableGoogleAutoSelect, parseGoogleCredential, renderGoogleSignInButton } from './google-auth.js'
|
|
import { disableGoogleAutoSelect, parseGoogleCredential, renderGoogleSignInButton } from './google-auth.js'
|
|
|
|
|
+import { loadHomeScenes } from './services/home-scenes.js'
|
|
|
import {
|
|
import {
|
|
|
clearStoredAppleAuthState,
|
|
clearStoredAppleAuthState,
|
|
|
getStoredAppleAuthState,
|
|
getStoredAppleAuthState,
|
|
@@ -18,6 +19,8 @@ const APPLE_CLIENT_ID = import.meta.env.VITE_APPLE_CLIENT_ID || ''
|
|
|
const APPLE_REDIRECT_URI = import.meta.env.VITE_APPLE_REDIRECT_URI || `${window.location.origin}/auth/apple/callback`
|
|
const APPLE_REDIRECT_URI = import.meta.env.VITE_APPLE_REDIRECT_URI || `${window.location.origin}/auth/apple/callback`
|
|
|
const APPLE_SCOPE = import.meta.env.VITE_APPLE_SCOPE || 'name email'
|
|
const APPLE_SCOPE = import.meta.env.VITE_APPLE_SCOPE || 'name email'
|
|
|
const PRO_DESTINATION_URL = '#/pro'
|
|
const PRO_DESTINATION_URL = '#/pro'
|
|
|
|
|
+const IOS_DOWNLOAD_URL = 'https://apps.apple.com/app/id6757686667'
|
|
|
|
|
+const ANDROID_DOWNLOAD_URL = 'https://play.google.com/store/apps/details?id=com.snkj.simubus.simulation.wiring'
|
|
|
|
|
|
|
|
const activeQr = ref(null)
|
|
const activeQr = ref(null)
|
|
|
const previewRefs = ref({})
|
|
const previewRefs = ref({})
|
|
@@ -36,21 +39,50 @@ const googleButtonReady = ref(false)
|
|
|
const authLoading = ref(false)
|
|
const authLoading = ref(false)
|
|
|
const authError = ref('')
|
|
const authError = ref('')
|
|
|
const authStatus = ref('')
|
|
const authStatus = ref('')
|
|
|
|
|
+const HERO_SCENE_ID = 'Self-locking-belt-with-slight-movement'
|
|
|
|
|
+const INTERACTIVE_SCENE_ID = 'Family-dual-control'
|
|
|
|
|
+const SIMULATION_SCENE_ID = 'Industrial-self-locking'
|
|
|
|
|
|
|
|
const languageOptions = [
|
|
const languageOptions = [
|
|
|
{ value: 'en', label: 'English' },
|
|
{ value: 'en', label: 'English' },
|
|
|
{ value: 'zh', label: '中文' },
|
|
{ value: 'zh', label: '中文' },
|
|
|
]
|
|
]
|
|
|
|
|
|
|
|
-const heroScene = computed(() => scenes.value.find((scene) => scene.id === '自锁带点动') ?? null)
|
|
|
|
|
-const interactiveScenes = computed(() => scenes.value.filter((scene) => scene.id === '家庭双控'))
|
|
|
|
|
-const simulationScenes = computed(() => scenes.value.filter((scene) => scene.id === '工业自锁'))
|
|
|
|
|
|
|
+const heroScene = computed(() => scenes.value.find((scene) => scene.id === HERO_SCENE_ID) ?? null)
|
|
|
|
|
+const interactiveScenes = computed(() => scenes.value.filter((scene) => scene.id === INTERACTIVE_SCENE_ID))
|
|
|
|
|
+const simulationScenes = computed(() => scenes.value.filter((scene) => scene.id === SIMULATION_SCENE_ID))
|
|
|
const isAuthenticated = computed(() => Boolean(session.value?.token))
|
|
const isAuthenticated = computed(() => Boolean(session.value?.token))
|
|
|
|
|
|
|
|
function openProDestination() {
|
|
function openProDestination() {
|
|
|
window.location.hash = PRO_DESTINATION_URL
|
|
window.location.hash = PRO_DESTINATION_URL
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function isMobileViewport() {
|
|
|
|
|
+ return typeof window !== 'undefined' && window.matchMedia('(max-width: 768px)').matches
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function setActiveQr(platform) {
|
|
|
|
|
+ if (isMobileViewport()) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ activeQr.value = platform
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function clearActiveQr() {
|
|
|
|
|
+ activeQr.value = null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleDownloadClick(platform) {
|
|
|
|
|
+ if (!isMobileViewport()) {
|
|
|
|
|
+ activeQr.value = platform
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const targetUrl = platform === 'ios' ? IOS_DOWNLOAD_URL : ANDROID_DOWNLOAD_URL
|
|
|
|
|
+ window.location.href = targetUrl
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function setPreviewRef(id, el) {
|
|
function setPreviewRef(id, el) {
|
|
|
if (el) {
|
|
if (el) {
|
|
|
previewRefs.value[id] = el
|
|
previewRefs.value[id] = el
|
|
@@ -222,6 +254,8 @@ async function mountGoogleButton() {
|
|
|
await renderGoogleSignInButton(googleButtonRef.value, {
|
|
await renderGoogleSignInButton(googleButtonRef.value, {
|
|
|
clientId: GOOGLE_CLIENT_ID,
|
|
clientId: GOOGLE_CLIENT_ID,
|
|
|
locale: currentLocale.value,
|
|
locale: currentLocale.value,
|
|
|
|
|
+ width: 320,
|
|
|
|
|
+ height: 66,
|
|
|
callback: handleGoogleCredentialResponse,
|
|
callback: handleGoogleCredentialResponse,
|
|
|
})
|
|
})
|
|
|
googleButtonReady.value = true
|
|
googleButtonReady.value = true
|
|
@@ -242,9 +276,8 @@ async function finalizeAppleSignIn(response) {
|
|
|
const authorization = response?.authorization
|
|
const authorization = response?.authorization
|
|
|
const user = response?.user
|
|
const user = response?.user
|
|
|
const identityToken = authorization?.id_token || ''
|
|
const identityToken = authorization?.id_token || ''
|
|
|
- const authorizationCode = authorization?.code || ''
|
|
|
|
|
|
|
|
|
|
- if (!identityToken && !authorizationCode) {
|
|
|
|
|
|
|
+ if (!identityToken) {
|
|
|
throw new Error(messages.value.appleMissingCredential)
|
|
throw new Error(messages.value.appleMissingCredential)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -256,8 +289,8 @@ async function finalizeAppleSignIn(response) {
|
|
|
const nextSession = await socialLogin(
|
|
const nextSession = await socialLogin(
|
|
|
{
|
|
{
|
|
|
provider: 'apple',
|
|
provider: 'apple',
|
|
|
- token: identityToken || authorizationCode,
|
|
|
|
|
- uuid: profile.sub || authorizationCode || '',
|
|
|
|
|
|
|
+ token: identityToken,
|
|
|
|
|
+ uuid: profile.sub || '',
|
|
|
nickname: fullName || profile.email || '',
|
|
nickname: fullName || profile.email || '',
|
|
|
avatarUrl: '',
|
|
avatarUrl: '',
|
|
|
email: user?.email || profile.email || '',
|
|
email: user?.email || profile.email || '',
|
|
@@ -335,7 +368,6 @@ function handleAppleCallbackMessage(event) {
|
|
|
|
|
|
|
|
void finalizeAppleSignIn({
|
|
void finalizeAppleSignIn({
|
|
|
authorization: {
|
|
authorization: {
|
|
|
- code: payload.code || '',
|
|
|
|
|
id_token: payload.id_token || '',
|
|
id_token: payload.id_token || '',
|
|
|
},
|
|
},
|
|
|
user: payload.user || null,
|
|
user: payload.user || null,
|
|
@@ -366,6 +398,13 @@ async function confirmLogout() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
onMounted(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ scenes.value = await loadHomeScenes()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('Failed to load home scenes', error)
|
|
|
|
|
+ scenes.value = []
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
await mountAllAnimations()
|
|
await mountAllAnimations()
|
|
|
document.addEventListener('click', handleDocumentClick)
|
|
document.addEventListener('click', handleDocumentClick)
|
|
|
window.addEventListener('message', handleAppleCallbackMessage)
|
|
window.addEventListener('message', handleAppleCallbackMessage)
|
|
@@ -398,8 +437,8 @@ watch(currentLocale, async () => {
|
|
|
const figma = {
|
|
const figma = {
|
|
|
map: '/figma-assets/asset-01.svg',
|
|
map: '/figma-assets/asset-01.svg',
|
|
|
heroImage: '/figma-assets/asset-02.png',
|
|
heroImage: '/figma-assets/asset-02.png',
|
|
|
- iosQr: '/figma-assets/asset-03.png',
|
|
|
|
|
- androidQr: '/figma-assets/asset-03.png',
|
|
|
|
|
|
|
+ iosQr: '/ios.png',
|
|
|
|
|
+ androidQr: '/google.png',
|
|
|
logoIcon: '/figma-assets/asset-04.png',
|
|
logoIcon: '/figma-assets/asset-04.png',
|
|
|
logoText: '/figma-assets/asset-05.svg',
|
|
logoText: '/figma-assets/asset-05.svg',
|
|
|
footerLogo: '/figma-assets/asset-06.png',
|
|
footerLogo: '/figma-assets/asset-06.png',
|
|
@@ -667,15 +706,15 @@ const logoutBodyText = computed(() => {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="download-buttons">
|
|
<div class="download-buttons">
|
|
|
- <div class="download-item ios" @mouseenter="activeQr = 'ios'" @mouseleave="activeQr = null">
|
|
|
|
|
- <button class="download-button ios" type="button" @focus="activeQr = 'ios'" @blur="activeQr = null">{{ messages.iosDownload }}</button>
|
|
|
|
|
|
|
+ <div class="download-item ios" @mouseenter="setActiveQr('ios')" @mouseleave="clearActiveQr">
|
|
|
|
|
+ <button class="download-button ios" type="button" @focus="setActiveQr('ios')" @blur="clearActiveQr" @click="handleDownloadClick('ios')">{{ messages.iosDownload }}</button>
|
|
|
<div class="qr-popup" :class="{ visible: activeQr === 'ios' }" data-node-id="2911:2309">
|
|
<div class="qr-popup" :class="{ visible: activeQr === 'ios' }" data-node-id="2911:2309">
|
|
|
<img :src="figma.iosQr" :alt="messages.iosQrAlt" />
|
|
<img :src="figma.iosQr" :alt="messages.iosQrAlt" />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="download-item android" @mouseenter="activeQr = 'android'" @mouseleave="activeQr = null">
|
|
|
|
|
- <button class="download-button android" type="button" @focus="activeQr = 'android'" @blur="activeQr = null">{{ messages.androidDownload }}</button>
|
|
|
|
|
|
|
+ <div class="download-item android" @mouseenter="setActiveQr('android')" @mouseleave="clearActiveQr">
|
|
|
|
|
+ <button class="download-button android" type="button" @focus="setActiveQr('android')" @blur="clearActiveQr" @click="handleDownloadClick('android')">{{ messages.androidDownload }}</button>
|
|
|
<div class="qr-popup" :class="{ visible: activeQr === 'android' }" data-node-id="2911:2310">
|
|
<div class="qr-popup" :class="{ visible: activeQr === 'android' }" data-node-id="2911:2310">
|
|
|
<img :src="figma.androidQr" :alt="messages.androidQrAlt" />
|
|
<img :src="figma.androidQr" :alt="messages.androidQrAlt" />
|
|
|
</div>
|
|
</div>
|
|
@@ -803,12 +842,12 @@ const logoutBodyText = computed(() => {
|
|
|
</button>
|
|
</button>
|
|
|
</template>
|
|
</template>
|
|
|
<template v-else>
|
|
<template v-else>
|
|
|
- <div class="google-signin-slot" :style="{ display: 'flex', justifyContent: 'center', marginTop: '20px' }">
|
|
|
|
|
- <div ref="googleButtonRef" class="google-signin-slot__button" :style="{ width: currentLocale === 'zh' ? '280px' : '300px' }"></div>
|
|
|
|
|
|
|
+ <div class="google-signin-slot">
|
|
|
|
|
+ <div ref="googleButtonRef" class="google-signin-slot__button"></div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
- <button class="login-modal__signin-option" type="button" :disabled="authLoading" @click="handleAppleClick">
|
|
|
|
|
|
|
+ <button class="login-modal__signin-option login-modal__signin-option--apple" type="button" :disabled="authLoading" @click="handleAppleClick">
|
|
|
<img class="login-modal__signin-icon" src="/auth-icons/apple-icon.svg" alt="Apple" />
|
|
<img class="login-modal__signin-icon" src="/auth-icons/apple-icon.svg" alt="Apple" />
|
|
|
<span>{{ messages.modalApple }}</span>
|
|
<span>{{ messages.modalApple }}</span>
|
|
|
</button>
|
|
</button>
|