L1L2 gen1Gen2 kialakítása
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
></div>
|
||||
|
||||
<!-- 3D Perspective Container -->
|
||||
<div class="perspective-1000 relative z-10 w-full max-w-md">
|
||||
<div class="perspective-1000 relative z-10 w-full max-w-md max-h-[85vh]">
|
||||
<!-- Flippable Card Body -->
|
||||
<div
|
||||
class="relative transition-transform duration-700 transform-style-3d"
|
||||
@@ -24,7 +24,7 @@
|
||||
<!-- ==================== FRONT FACE: LOGIN ==================== -->
|
||||
<form
|
||||
@submit.prevent="handleLogin"
|
||||
class="backface-hidden w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl"
|
||||
class="backface-hidden w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl overflow-y-auto max-h-[80vh]"
|
||||
:class="{ 'pointer-events-none': authMode !== 'login' }"
|
||||
>
|
||||
<!-- Close Button (X) -->
|
||||
@@ -200,9 +200,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Social Login Buttons -->
|
||||
<div class="mb-6 grid grid-cols-2 gap-3">
|
||||
<div class="mb-6 grid grid-cols-1 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="handleGoogleLogin"
|
||||
class="flex items-center justify-center gap-2 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/50 transition-all duration-200 hover:border-white/20 hover:text-white/80"
|
||||
>
|
||||
<!-- Google Logo Placeholder -->
|
||||
@@ -216,6 +217,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
v-if="false"
|
||||
class="flex items-center justify-center gap-2 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/50 transition-all duration-200 hover:border-white/20 hover:text-white/80"
|
||||
>
|
||||
<!-- Apple Logo Placeholder -->
|
||||
@@ -243,7 +245,7 @@
|
||||
<form
|
||||
v-if="activeBackFace === 'register'"
|
||||
@submit.prevent="handleRegister"
|
||||
class="backface-hidden rotate-y-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl"
|
||||
class="backface-hidden rotate-y-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl overflow-y-auto"
|
||||
>
|
||||
<!-- Close Button (X) -->
|
||||
<button
|
||||
@@ -379,6 +381,21 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Referral Code Input (Meghívó kód) -->
|
||||
<div class="mb-5">
|
||||
<label for="reg-referral" class="mb-2 block text-sm font-medium text-white/80">
|
||||
Meghívó kód
|
||||
</label>
|
||||
<input
|
||||
id="reg-referral"
|
||||
v-model="referredByCode"
|
||||
type="text"
|
||||
placeholder="Pl. ABC123"
|
||||
autocomplete="off"
|
||||
class="w-full rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-white placeholder-white/30 backdrop-blur-sm transition-all duration-200 focus:border-[#75A882] focus:outline-none focus:ring-2 focus:ring-[#75A882]/50"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Error Message (translated via i18n) -->
|
||||
<div
|
||||
v-if="authStore.error && authMode === 'register'"
|
||||
@@ -396,6 +413,47 @@
|
||||
{{ authStore.isLoading ? t('auth.loginLoading') : t('auth.registerButton') }}
|
||||
</button>
|
||||
|
||||
<!-- Social Register Divider (Premium) -->
|
||||
<div class="my-6 flex items-center gap-3">
|
||||
<span class="flex-1 border-t border-white/10"></span>
|
||||
<span class="flex items-center gap-2 text-sm text-white/40">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
||||
</svg>
|
||||
Vagy regisztrálj Social fiókkal
|
||||
</span>
|
||||
<span class="flex-1 border-t border-white/10"></span>
|
||||
</div>
|
||||
|
||||
<!-- Social Register Buttons -->
|
||||
<div class="mb-6 grid grid-cols-1 gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="handleGoogleRegister"
|
||||
class="flex items-center justify-center gap-2 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/50 transition-all duration-200 hover:border-white/20 hover:text-white/80"
|
||||
>
|
||||
<!-- Google Logo Placeholder -->
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"/>
|
||||
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
||||
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
|
||||
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
||||
</svg>
|
||||
Google
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
v-if="false"
|
||||
class="flex items-center justify-center gap-2 rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/50 transition-all duration-200 hover:border-white/20 hover:text-white/80"
|
||||
>
|
||||
<!-- Apple Logo Placeholder -->
|
||||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"/>
|
||||
</svg>
|
||||
Apple
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Switch back to Login -->
|
||||
<p class="mt-6 text-center text-sm text-white/50">
|
||||
{{ t('auth.haveAccount') }}
|
||||
@@ -413,7 +471,7 @@
|
||||
<form
|
||||
v-if="activeBackFace === 'forgot'"
|
||||
@submit.prevent="handleForgotPassword"
|
||||
class="backface-hidden rotate-x-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl"
|
||||
class="backface-hidden rotate-x-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl overflow-y-auto"
|
||||
>
|
||||
<!-- Close Button (X) -->
|
||||
<button
|
||||
@@ -528,7 +586,7 @@
|
||||
<form
|
||||
v-if="activeBackFace === 'resend'"
|
||||
@submit.prevent="handleResend"
|
||||
class="backface-hidden rotate-y-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl"
|
||||
class="backface-hidden rotate-y-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl overflow-y-auto"
|
||||
>
|
||||
<!-- Close Button (X) -->
|
||||
<button
|
||||
@@ -629,7 +687,7 @@
|
||||
<form
|
||||
v-if="activeBackFace === 'restore'"
|
||||
@submit.prevent="handleRestoreStep"
|
||||
class="backface-hidden rotate-y-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl"
|
||||
class="backface-hidden rotate-y-180 absolute inset-0 w-full rounded-2xl border border-[#75A882]/30 bg-[#062535] p-8 shadow-2xl overflow-y-auto"
|
||||
>
|
||||
<!-- Close Button (X) -->
|
||||
<button
|
||||
@@ -883,6 +941,7 @@ const lastName = ref('')
|
||||
const firstName = ref('')
|
||||
const regEmail = ref('')
|
||||
const regPassword = ref('')
|
||||
const referredByCode = ref('')
|
||||
|
||||
// ── Forgot password form fields ──
|
||||
const forgotEmail = ref('')
|
||||
@@ -1071,12 +1130,17 @@ async function handleLogin() {
|
||||
// ── Register Logic ──
|
||||
async function handleRegister() {
|
||||
try {
|
||||
await authStore.register({
|
||||
const payload: Record<string, any> = {
|
||||
email: regEmail.value,
|
||||
password: regPassword.value,
|
||||
first_name: firstName.value,
|
||||
last_name: lastName.value,
|
||||
})
|
||||
}
|
||||
if (referredByCode.value) {
|
||||
payload.referred_by_code = referredByCode.value
|
||||
}
|
||||
|
||||
await authStore.register(payload)
|
||||
|
||||
// Save email to localStorage for next visit
|
||||
localStorage.setItem('saved-email', regEmail.value)
|
||||
@@ -1089,6 +1153,84 @@ async function handleRegister() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Google Login (from Login face) ──
|
||||
async function handleGoogleLogin() {
|
||||
try {
|
||||
const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID
|
||||
if (!googleClientId) {
|
||||
console.error('VITE_GOOGLE_CLIENT_ID is not configured')
|
||||
return
|
||||
}
|
||||
|
||||
// Load Google Identity Services if not already loaded
|
||||
if (!(window as any).google?.accounts) {
|
||||
await loadGoogleScript()
|
||||
}
|
||||
|
||||
// Use Google OAuth2 token client to get an access_token.
|
||||
// The backend will verify this token via Google's tokeninfo endpoint.
|
||||
const accessToken = await new Promise<string>((resolve, reject) => {
|
||||
const client = (window as any).google.accounts.oauth2.initTokenClient({
|
||||
client_id: googleClientId,
|
||||
scope: 'openid email profile',
|
||||
callback: (response: any) => {
|
||||
if (response.error) {
|
||||
reject(new Error(response.error))
|
||||
return
|
||||
}
|
||||
resolve(response.access_token)
|
||||
},
|
||||
})
|
||||
client.requestAccessToken()
|
||||
})
|
||||
|
||||
// Send the token to our backend
|
||||
await authStore.googleAuth(accessToken)
|
||||
emit('close')
|
||||
if (authStore.isKycComplete) {
|
||||
router.push('/dashboard')
|
||||
} else {
|
||||
router.push('/complete-kyc')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Google login failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Google Register (from Register face — saves referral code first) ──
|
||||
async function handleGoogleRegister() {
|
||||
try {
|
||||
// Save referral code to localStorage before redirecting
|
||||
if (referredByCode.value) {
|
||||
localStorage.setItem('pending_referral_code', referredByCode.value)
|
||||
}
|
||||
|
||||
// Same flow as login
|
||||
await handleGoogleLogin()
|
||||
} catch (err) {
|
||||
console.error('Google register failed:', err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically load the Google Identity Services script.
|
||||
*/
|
||||
function loadGoogleScript(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if ((window as any).google?.accounts) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://accounts.google.com/gsi/client'
|
||||
script.async = true
|
||||
script.defer = true
|
||||
script.onload = () => resolve()
|
||||
script.onerror = () => reject(new Error('Failed to load Google Identity Services script'))
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
// ── Forgot Password Logic ──
|
||||
async function handleForgotPassword() {
|
||||
forgotError.value = ''
|
||||
|
||||
@@ -287,6 +287,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
password: string
|
||||
first_name: string
|
||||
last_name: string
|
||||
referred_by_code?: string
|
||||
}) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
@@ -629,6 +630,52 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Google OAuth login/register.
|
||||
* Sends the Google ID token to the backend. If the user typed a referral code
|
||||
* in the registration form before clicking Google, it's passed along.
|
||||
*/
|
||||
async function googleAuth(token: string, referredByCode?: string) {
|
||||
isLoading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const payload: Record<string, any> = { access_token: token }
|
||||
if (referredByCode) {
|
||||
payload.referred_by_code = referredByCode
|
||||
}
|
||||
|
||||
const res = await api.post('/auth/google', payload)
|
||||
|
||||
const data = res.data as {
|
||||
access_token: string
|
||||
refresh_token?: string
|
||||
token_type: string
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
// Persist token
|
||||
localStorage.setItem('access_token', data.access_token)
|
||||
if (data.refresh_token) {
|
||||
localStorage.setItem('refresh_token', data.refresh_token)
|
||||
}
|
||||
token.value = data.access_token
|
||||
|
||||
// Immediately fetch the user profile
|
||||
await fetchUser()
|
||||
|
||||
// Fetch organizations
|
||||
await fetchMyOrganizations()
|
||||
|
||||
return data
|
||||
} catch (err: any) {
|
||||
error.value = getErrorMessage(err, 'auth.googleAuthFailed')
|
||||
throw err
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any stored error message.
|
||||
*/
|
||||
@@ -760,5 +807,6 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
clearError,
|
||||
fetchMyOrganizations,
|
||||
switchOrganization,
|
||||
googleAuth,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user