frontend 2026-06-10 bontva a 2 felület
This commit is contained in:
@@ -1,349 +1,402 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#04151F] flex flex-col items-center justify-center px-4 py-8">
|
||||
<!-- Header Logo / Brand -->
|
||||
<div class="mb-8 text-center">
|
||||
<h1 class="text-3xl font-bold text-white tracking-tight">
|
||||
Service<span class="text-[#00E5A0]">Finder</span>
|
||||
</h1>
|
||||
<p class="text-white/40 text-sm mt-1">Fiók aktiválás — KYC kitöltése</p>
|
||||
<div class="relative flex min-h-screen items-center justify-center text-white">
|
||||
<!-- Background Image (like VerifyEmailView and LandingView) -->
|
||||
<div class="fixed inset-0 z-0">
|
||||
<div class="absolute inset-0 bg-[url('@/assets/garage-bg.png')] bg-cover bg-center bg-fixed"></div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="w-full max-w-lg mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
v-for="(step, idx) in steps"
|
||||
:key="idx"
|
||||
class="flex items-center flex-1"
|
||||
>
|
||||
<!-- Step circle + label -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold transition-all duration-300"
|
||||
:class="stepCircleClass(idx)"
|
||||
>
|
||||
<span v-if="currentStep > idx + 1">✓</span>
|
||||
<span v-else>{{ idx + 1 }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs mt-1.5 transition-colors duration-300"
|
||||
:class="currentStep >= idx + 1 ? 'text-[#00E5A0]' : 'text-white/30'"
|
||||
>
|
||||
{{ step.label }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Connector line -->
|
||||
<!-- Content Container -->
|
||||
<div class="relative z-10 mx-auto w-full max-w-2xl px-4 py-8">
|
||||
<!-- Header Logo / Brand -->
|
||||
<div class="mb-8 text-center">
|
||||
<h1 class="text-3xl font-bold text-white tracking-tight">
|
||||
Service<span class="text-[#00E5A0]">Finder</span>
|
||||
</h1>
|
||||
<p class="text-white/60 text-sm mt-2">{{ t('kyc.title') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar (2 steps only) -->
|
||||
<div class="w-full max-w-lg mx-auto mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
v-if="idx < steps.length - 1"
|
||||
class="flex-1 h-0.5 mx-3 rounded transition-colors duration-300"
|
||||
:class="currentStep > idx + 1 ? 'bg-[#00E5A0]' : 'bg-white/10'"
|
||||
/>
|
||||
v-for="(step, idx) in steps"
|
||||
:key="idx"
|
||||
class="flex items-center flex-1"
|
||||
>
|
||||
<!-- Step circle + label -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div
|
||||
class="w-12 h-12 rounded-full flex items-center justify-center text-sm font-semibold transition-all duration-300"
|
||||
:class="stepCircleClass(idx)"
|
||||
>
|
||||
<span v-if="currentStep > idx + 1">✓</span>
|
||||
<span v-else>{{ idx + 1 }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs mt-2 transition-colors duration-300 font-medium"
|
||||
:class="currentStep >= idx + 1 ? 'text-[#00E5A0]' : 'text-white/40'"
|
||||
>
|
||||
{{ step.label }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Connector line -->
|
||||
<div
|
||||
v-if="idx < steps.length - 1"
|
||||
class="flex-1 h-1 mx-4 rounded transition-all duration-300"
|
||||
:class="currentStep > idx + 1 ? 'bg-[#00E5A0]' : 'bg-white/10'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Glassmorphism Card -->
|
||||
<div class="w-full max-w-lg">
|
||||
<div
|
||||
class="backdrop-blur-xl bg-white/5 border border-white/10 rounded-2xl shadow-2xl p-8 md:p-10 transition-all duration-500"
|
||||
>
|
||||
<!-- ==================== STEP 1: Personal ==================== -->
|
||||
<div v-if="currentStep === 1">
|
||||
<h2 class="text-xl font-semibold text-white mb-1">Személyes adatok</h2>
|
||||
<p class="text-[#00E5A0]/70 text-sm mb-6">
|
||||
Vezeték- és keresztnév megadása
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Last name -->
|
||||
<div>
|
||||
<label class="block text-sm text-white/60 mb-1">Vezetéknév</label>
|
||||
<input
|
||||
v-model="kycForm.last_name"
|
||||
type="text"
|
||||
placeholder="pl. Kovács"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- First name -->
|
||||
<div>
|
||||
<label class="block text-sm text-white/60 mb-1">Keresztnév</label>
|
||||
<input
|
||||
v-model="kycForm.first_name"
|
||||
type="text"
|
||||
placeholder="pl. István"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== STEP 2: Address (Soft KYC) ==================== -->
|
||||
<div v-if="currentStep === 2">
|
||||
<h2 class="text-xl font-semibold text-white mb-1">Lakcím adatok</h2>
|
||||
<p class="text-white/40 text-sm mb-6">Csak az irányítószám és város kötelező <span class="text-[#D4AF37]">(Soft KYC)</span></p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Country selector (EU-ready) -->
|
||||
<div>
|
||||
<label class="block text-sm text-white/60 mb-1">Ország</label>
|
||||
<select
|
||||
v-model="kycForm.region_code"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
>
|
||||
<option value="HU" class="bg-[#04151F]">🇭🇺 Magyarország</option>
|
||||
<option value="AT" class="bg-[#04151F]">🇦🇹 Ausztria</option>
|
||||
<option value="SK" class="bg-[#04151F]">🇸🇰 Szlovákia</option>
|
||||
<option value="DE" class="bg-[#04151F]">🇩🇪 Németország</option>
|
||||
<option value="RO" class="bg-[#04151F]">🇷🇴 Románia</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="col-span-1">
|
||||
<label class="block text-sm text-white/60 mb-1">Irányítószám</label>
|
||||
<input
|
||||
v-model="kycForm.address_zip"
|
||||
type="text"
|
||||
placeholder="1234"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm text-white/60 mb-1">Város</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="kycForm.address_city"
|
||||
type="text"
|
||||
placeholder="Budapest"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition pr-10"
|
||||
:class="{ 'opacity-60': isCityLoading }"
|
||||
/>
|
||||
<!-- Loading spinner -->
|
||||
<div
|
||||
v-if="isCityLoading"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
<svg
|
||||
class="animate-spin h-5 w-5 text-[#00E5A0]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm text-white/60 mb-1">Utca neve <span class="text-white/20">(opcionális)</span></label>
|
||||
<input
|
||||
v-model="kycForm.address_street_name"
|
||||
type="text"
|
||||
placeholder="Kossuth Lajos"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-white/60 mb-1">Típus <span class="text-white/20">(opcionális)</span></label>
|
||||
<select
|
||||
v-model="kycForm.address_street_type"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
>
|
||||
<option value="" disabled class="bg-[#04151F]">Típus</option>
|
||||
<option value="utca" class="bg-[#04151F]">utca</option>
|
||||
<option value="út" class="bg-[#04151F]">út</option>
|
||||
<option value="tér" class="bg-[#04151F]">tér</option>
|
||||
<option value="körút" class="bg-[#04151F]">körút</option>
|
||||
<option value="sugárút" class="bg-[#04151F]">sugárút</option>
|
||||
<option value="köz" class="bg-[#04151F]">köz</option>
|
||||
<option value="sor" class="bg-[#04151F]">sor</option>
|
||||
<option value="liget" class="bg-[#04151F]">liget</option>
|
||||
<option value="park" class="bg-[#04151F]">park</option>
|
||||
<option value="dűlő" class="bg-[#04151F]">dűlő</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm text-white/60 mb-1">Házszám <span class="text-white/20">(opcionális)</span></label>
|
||||
<input
|
||||
v-model="kycForm.address_house_number"
|
||||
type="text"
|
||||
placeholder="10/A"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
@keydown.tab.exact="handleHouseNumberTab"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== STEP 3: Security ==================== -->
|
||||
<div v-if="currentStep === 3">
|
||||
<h2 class="text-xl font-semibold text-white mb-1">Okmányok</h2>
|
||||
<p class="text-white/40 text-sm mb-6">Személyi igazolvány és jogosítvány adatok <span class="text-[#D4AF37]">(opcionális)</span></p>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Identity Documents -->
|
||||
<div>
|
||||
<!-- ID Card -->
|
||||
<div class="bg-white/5 rounded-lg p-4 mb-3 border border-white/5">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-2 h-2 rounded-full bg-white/20" />
|
||||
<span class="text-sm text-white/50">Személyi igazolvány <span class="text-white/20">(opcionális)</span></span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-white/40 mb-1">Okmány száma</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.ID_CARD.number"
|
||||
type="text"
|
||||
placeholder="123456AB"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-white/40 mb-1">Lejárat</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.ID_CARD.expiry_date"
|
||||
type="date"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white [color-scheme:dark] focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Driver's License (optional) -->
|
||||
<div class="bg-white/5 rounded-lg p-4 border border-white/5">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-2 h-2 rounded-full bg-white/20" />
|
||||
<span class="text-sm text-white/50">Jogosítvány <span class="text-white/20">(opcionális)</span></span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-white/40 mb-1">Okmány száma</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.LICENSE.number"
|
||||
type="text"
|
||||
placeholder="HU-1234567"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-white/40 mb-1">Lejárat</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.LICENSE.expiry_date"
|
||||
type="date"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white [color-scheme:dark] focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Language & Currency Preferences -->
|
||||
<div class="grid grid-cols-2 gap-3 pt-2">
|
||||
<div>
|
||||
<label class="block text-sm text-white/60 mb-1">Nyelv</label>
|
||||
<select
|
||||
v-model="kycForm.preferred_language"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
>
|
||||
<option value="hu" class="bg-[#04151F]">Magyar</option>
|
||||
<option value="en" class="bg-[#04151F]">English</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-white/60 mb-1">Pénznem</label>
|
||||
<select
|
||||
v-model="kycForm.preferred_currency"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
>
|
||||
<option value="HUF" class="bg-[#04151F]">HUF (Ft)</option>
|
||||
<option value="EUR" class="bg-[#04151F]">EUR (€)</option>
|
||||
<option value="USD" class="bg-[#04151F]">USD ($)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== Navigation Buttons ==================== -->
|
||||
<div class="flex items-center justify-between mt-8 pt-6 border-t border-white/5">
|
||||
<!-- Back button -->
|
||||
<button
|
||||
v-if="currentStep > 1"
|
||||
@click="prevStep"
|
||||
class="px-5 py-2.5 rounded-lg text-white/60 hover:text-white border border-white/10 hover:border-white/30 transition-all text-sm"
|
||||
>
|
||||
← Vissza
|
||||
</button>
|
||||
<div v-else />
|
||||
|
||||
<!-- Next / Submit -->
|
||||
<button
|
||||
v-if="currentStep < 3"
|
||||
@click="nextStep"
|
||||
ref="nextButton"
|
||||
class="px-6 py-2.5 rounded-lg bg-[#00E5A0] text-[#04151F] font-semibold hover:bg-[#00E5A0]/90 hover:shadow-lg hover:shadow-[#00E5A0]/20 transition-all text-sm"
|
||||
>
|
||||
Tovább →
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else
|
||||
@click="handleSubmit"
|
||||
:disabled="isSubmitting"
|
||||
class="px-6 py-2.5 rounded-lg font-semibold transition-all text-sm flex items-center gap-2"
|
||||
:class="isSubmitting
|
||||
? 'bg-[#00E5A0]/50 text-[#04151F]/70 cursor-not-allowed'
|
||||
: 'bg-[#00E5A0] text-[#04151F] hover:bg-[#00E5A0]/90 hover:shadow-lg hover:shadow-[#00E5A0]/20'"
|
||||
>
|
||||
<!-- Spinner -->
|
||||
<svg
|
||||
v-if="isSubmitting"
|
||||
class="animate-spin h-4 w-4 text-[#04151F]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<span>{{ isSubmitting ? 'Feldolgozás...' : 'Garázs Hitelesítése és Belépés' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error message -->
|
||||
<!-- Glassmorphism Card -->
|
||||
<div class="w-full max-w-lg mx-auto">
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="mt-4 p-3 rounded-lg bg-red-500/10 border border-red-500/20 text-red-400 text-sm"
|
||||
class="rounded-2xl border border-white/[0.08] bg-white/[0.05] p-8 md:p-10 backdrop-blur-xl shadow-xl shadow-black/20 transition-all duration-500"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
<!-- ==================== STEP 1: Address (Soft KYC) ==================== -->
|
||||
<div v-if="currentStep === 1">
|
||||
<h2 class="text-2xl font-semibold text-white mb-2">{{ t('kyc.addressTitle') }}</h2>
|
||||
<p class="text-white/60 text-sm mb-6 leading-relaxed">
|
||||
{{ t('kyc.addressDescription') }}
|
||||
<span class="text-[#00E5A0]">{{ t('kyc.softKyc') }}</span>
|
||||
</p>
|
||||
|
||||
<div class="space-y-5">
|
||||
<!-- Country selector (EU-ready) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('kyc.country') }}</label>
|
||||
<select
|
||||
v-model="kycForm.region_code"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
>
|
||||
<option value="HU" class="bg-[#04151F]">🇭🇺 Magyarország</option>
|
||||
<option value="AT" class="bg-[#04151F]">🇦🇹 Ausztria</option>
|
||||
<option value="SK" class="bg-[#04151F]">🇸🇰 Szlovákia</option>
|
||||
<option value="DE" class="bg-[#04151F]">🇩🇪 Németország</option>
|
||||
<option value="RO" class="bg-[#04151F]">🇷🇴 Románia</option>
|
||||
<option value="CZ" class="bg-[#04151F]">🇨🇿 Csehország</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="col-span-1">
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('kyc.zipCode') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="kycForm.address_zip"
|
||||
type="text"
|
||||
placeholder="1234"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('kyc.city') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="kycForm.address_city"
|
||||
type="text"
|
||||
placeholder="Budapest"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all pr-10"
|
||||
:class="{ 'opacity-60': isCityLoading }"
|
||||
/>
|
||||
<!-- Loading spinner -->
|
||||
<div
|
||||
v-if="isCityLoading"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
<svg
|
||||
class="animate-spin h-5 w-5 text-[#00E5A0]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Optional street details (collapsible) -->
|
||||
<div class="border-t border-white/10 pt-4">
|
||||
<button
|
||||
@click="showOptionalAddress = !showOptionalAddress"
|
||||
type="button"
|
||||
class="flex items-center justify-between w-full text-sm text-white/60 hover:text-white/80 transition-colors"
|
||||
>
|
||||
<span>{{ t('kyc.optionalAddress') }}</span>
|
||||
<svg
|
||||
class="w-5 h-5 transition-transform"
|
||||
:class="{ 'rotate-180': showOptionalAddress }"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div v-if="showOptionalAddress" class="mt-4 space-y-4">
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="col-span-2">
|
||||
<label class="block text-xs text-white/60 mb-1">{{ t('kyc.streetName') }}</label>
|
||||
<input
|
||||
v-model="kycForm.address_street_name"
|
||||
type="text"
|
||||
placeholder="Kossuth Lajos"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-white/60 mb-1">{{ t('kyc.streetType') }}</label>
|
||||
<select
|
||||
v-model="kycForm.address_street_type"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-white focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
>
|
||||
<option value="" disabled class="bg-[#04151F]">{{ t('kyc.streetType') }}</option>
|
||||
<option value="utca" class="bg-[#04151F]">utca</option>
|
||||
<option value="út" class="bg-[#04151F]">út</option>
|
||||
<option value="tér" class="bg-[#04151F]">tér</option>
|
||||
<option value="körút" class="bg-[#04151F]">körút</option>
|
||||
<option value="sugárút" class="bg-[#04151F]">sugárút</option>
|
||||
<option value="köz" class="bg-[#04151F]">köz</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs text-white/60 mb-1">{{ t('kyc.houseNumber') }}</label>
|
||||
<input
|
||||
v-model="kycForm.address_house_number"
|
||||
type="text"
|
||||
placeholder="10/A"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== STEP 2: Optional Extras ==================== -->
|
||||
<div v-if="currentStep === 2">
|
||||
<h2 class="text-2xl font-semibold text-white mb-2">{{ t('kyc.extrasTitle') }}</h2>
|
||||
<p class="text-white/60 text-sm mb-6 leading-relaxed">
|
||||
{{ t('kyc.extrasDescription') }} <span class="text-white/40">{{ t('kyc.extrasOptional') }}</span>
|
||||
</p>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Phone number -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('kyc.phoneNumber') }}</label>
|
||||
<input
|
||||
v-model="kycForm.phone_number"
|
||||
type="tel"
|
||||
placeholder="+36 30 123 4567"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Identity Documents (collapsible) -->
|
||||
<div class="border-t border-white/10 pt-4">
|
||||
<button
|
||||
@click="showDocuments = !showDocuments"
|
||||
type="button"
|
||||
class="flex items-center justify-between w-full text-sm text-white/60 hover:text-white/80 transition-colors"
|
||||
>
|
||||
<span>{{ t('kyc.documentsTitle') }}</span>
|
||||
<svg
|
||||
class="w-5 h-5 transition-transform"
|
||||
:class="{ 'rotate-180': showDocuments }"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div v-if="showDocuments" class="mt-4 space-y-4">
|
||||
<!-- ID Card -->
|
||||
<div class="bg-white/5 rounded-lg p-4 border border-white/5">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-2 h-2 rounded-full bg-white/30" />
|
||||
<span class="text-sm text-white/70">{{ t('kyc.idCard') }}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-white/50 mb-1">{{ t('kyc.docNumber') }}</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.ID_CARD.number"
|
||||
type="text"
|
||||
placeholder="123456AB"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-white/50 mb-1">{{ t('kyc.docExpiry') }}</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.ID_CARD.expiry_date"
|
||||
type="date"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white [color-scheme:dark] focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Driver's License -->
|
||||
<div class="bg-white/5 rounded-lg p-4 border border-white/5">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<div class="w-2 h-2 rounded-full bg-white/30" />
|
||||
<span class="text-sm text-white/70">{{ t('kyc.drivingLicense') }}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs text-white/50 mb-1">{{ t('kyc.docNumber') }}</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.LICENSE.number"
|
||||
type="text"
|
||||
placeholder="HU-1234567"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs text-white/50 mb-1">{{ t('kyc.docExpiry') }}</label>
|
||||
<input
|
||||
v-model="kycForm.identity_docs.LICENSE.expiry_date"
|
||||
type="date"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-white [color-scheme:dark] focus:outline-none focus:border-[#00E5A0] focus:ring-1 focus:ring-[#00E5A0]/30 transition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Language & Currency Preferences -->
|
||||
<div class="grid grid-cols-2 gap-4 pt-2">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('kyc.language') }}</label>
|
||||
<select
|
||||
v-model="kycForm.preferred_language"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
>
|
||||
<option value="hu" class="bg-[#04151F]">Magyar</option>
|
||||
<option value="en" class="bg-[#04151F]">English</option>
|
||||
<option value="de" class="bg-[#04151F]">Deutsch</option>
|
||||
<option value="sk" class="bg-[#04151F]">Slovenčina</option>
|
||||
<option value="ro" class="bg-[#04151F]">Română</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('kyc.currency') }}</label>
|
||||
<select
|
||||
v-model="kycForm.preferred_currency"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
>
|
||||
<option value="HUF" class="bg-[#04151F]">HUF (Ft)</option>
|
||||
<option value="EUR" class="bg-[#04151F]">EUR (€)</option>
|
||||
<option value="USD" class="bg-[#04151F]">USD ($)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== Navigation Buttons ==================== -->
|
||||
<div class="flex items-center justify-between mt-8 pt-6 border-t border-white/5">
|
||||
<!-- Back button -->
|
||||
<button
|
||||
v-if="currentStep > 1"
|
||||
@click="prevStep"
|
||||
class="px-5 py-3 rounded-xl text-white/70 hover:text-white border border-white/10 hover:border-white/30 transition-all text-sm font-medium flex items-center gap-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
{{ t('kyc.back') }}
|
||||
</button>
|
||||
<div v-else />
|
||||
|
||||
<!-- Next / Submit -->
|
||||
<button
|
||||
v-if="currentStep < 2"
|
||||
@click="nextStep"
|
||||
ref="nextButton"
|
||||
class="px-6 py-3 rounded-xl bg-[#00E5A0] text-[#04151F] font-semibold hover:bg-[#00E5A0]/90 hover:shadow-lg hover:shadow-[#00E5A0]/30 transition-all text-sm flex items-center gap-2"
|
||||
>
|
||||
{{ t('kyc.next') }}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else
|
||||
@click="handleSubmit"
|
||||
:disabled="isSubmitting"
|
||||
class="px-6 py-3 rounded-xl font-semibold transition-all text-sm flex items-center gap-2"
|
||||
:class="isSubmitting
|
||||
? 'bg-[#00E5A0]/50 text-[#04151F]/70 cursor-not-allowed'
|
||||
: 'bg-[#00E5A0] text-[#04151F] hover:bg-[#00E5A0]/90 hover:shadow-lg hover:shadow-[#00E5A0]/30'"
|
||||
>
|
||||
<!-- Spinner -->
|
||||
<svg
|
||||
v-if="isSubmitting"
|
||||
class="animate-spin h-4 w-4 text-[#04151F]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<span>{{ isSubmitting ? t('kyc.submitting') : t('kyc.submit') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error message -->
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="mt-4 p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Skip link (optional) -->
|
||||
<div class="text-center mt-6">
|
||||
<button
|
||||
@click="handleSkip"
|
||||
class="text-sm text-white/50 hover:text-white/70 transition-colors underline"
|
||||
>
|
||||
{{ t('kyc.skip') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, nextTick } from 'vue'
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// ── Steps definition ──
|
||||
// ── Steps definition (2 steps only) ──
|
||||
const steps = [
|
||||
{ label: 'Személyes', key: 'personal' },
|
||||
{ label: 'Lakcím', key: 'address' },
|
||||
{ label: 'Okmányok', key: 'security' },
|
||||
{ label: t('kyc.stepAddress'), key: 'address' },
|
||||
{ label: t('kyc.stepExtras'), key: 'extras' },
|
||||
]
|
||||
|
||||
const currentStep = ref(1)
|
||||
@@ -351,17 +404,18 @@ const isSubmitting = ref(false)
|
||||
const errorMessage = ref<string | null>(null)
|
||||
const isCityLoading = ref(false)
|
||||
const nextButton = ref<HTMLButtonElement | null>(null)
|
||||
const showOptionalAddress = ref(false)
|
||||
const showDocuments = ref(false)
|
||||
|
||||
// ── KYC Form (minimal — only DB-required fields) ──
|
||||
// ── KYC Form (names removed - already provided in registration) ──
|
||||
const kycForm = reactive({
|
||||
last_name: '',
|
||||
first_name: '',
|
||||
region_code: 'HU',
|
||||
address_zip: '',
|
||||
address_city: '',
|
||||
address_street_name: '',
|
||||
address_street_type: '',
|
||||
address_house_number: '',
|
||||
phone_number: '',
|
||||
identity_docs: {
|
||||
ID_CARD: { number: '', expiry_date: '' },
|
||||
LICENSE: { number: '', expiry_date: '' },
|
||||
@@ -391,7 +445,7 @@ watch(
|
||||
} finally {
|
||||
isCityLoading.value = false
|
||||
}
|
||||
}, 600) // 600ms késleltetés a gépelés után
|
||||
}, 600) // 600ms delay after typing
|
||||
} else {
|
||||
isCityLoading.value = false
|
||||
}
|
||||
@@ -402,38 +456,37 @@ watch(
|
||||
function stepCircleClass(idx: number): string {
|
||||
const stepNum = idx + 1
|
||||
if (currentStep.value > stepNum) {
|
||||
return 'bg-[#00E5A0] text-[#04151F]'
|
||||
return 'bg-[#00E5A0] text-[#04151F] shadow-lg shadow-[#00E5A0]/30'
|
||||
}
|
||||
if (currentStep.value === stepNum) {
|
||||
return 'bg-[#00E5A0]/20 text-[#00E5A0] border-2 border-[#00E5A0]'
|
||||
return 'bg-[#00E5A0]/20 text-[#00E5A0] border-2 border-[#00E5A0] shadow-lg shadow-[#00E5A0]/20'
|
||||
}
|
||||
return 'bg-white/5 text-white/30 border border-white/10'
|
||||
return 'bg-white/5 text-white/40 border border-white/10'
|
||||
}
|
||||
|
||||
// ── Simple step validation (Soft KYC) ──
|
||||
// ── Validation (Soft KYC) ──
|
||||
function validateStep(): boolean {
|
||||
errorMessage.value = null
|
||||
|
||||
if (currentStep.value === 1) {
|
||||
if (!kycForm.last_name) { errorMessage.value = 'Kérlek add meg a vezetéknevedet.'; return false }
|
||||
if (!kycForm.first_name) { errorMessage.value = 'Kérlek add meg a keresztnevedet.'; return false }
|
||||
if (!kycForm.address_zip) {
|
||||
errorMessage.value = t('kyc.zipRequired')
|
||||
return false
|
||||
}
|
||||
if (!kycForm.address_city) {
|
||||
errorMessage.value = t('kyc.cityRequired')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStep.value === 2) {
|
||||
if (!kycForm.address_zip) { errorMessage.value = 'Kérlek add meg az irányítószámot.'; return false }
|
||||
if (!kycForm.address_city) { errorMessage.value = 'Kérlek add meg a várost.'; return false }
|
||||
// Soft KYC: Utca, típus, házszám már NEM kötelező
|
||||
}
|
||||
|
||||
// Step 3 has no required validations anymore (Soft KYC - all optional)
|
||||
|
||||
// Step 2 has no required fields (all optional)
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Navigation ──
|
||||
function nextStep() {
|
||||
if (!validateStep()) return
|
||||
if (currentStep.value < 3) {
|
||||
if (currentStep.value < 2) {
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
@@ -445,16 +498,6 @@ function prevStep() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tab order fix: Házszám → Tovább gomb ──
|
||||
function handleHouseNumberTab(event: KeyboardEvent) {
|
||||
if (currentStep.value === 2 && nextButton.value) {
|
||||
event.preventDefault()
|
||||
nextTick(() => {
|
||||
nextButton.value?.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ── Submit ──
|
||||
async function handleSubmit() {
|
||||
if (!validateStep()) return
|
||||
@@ -463,10 +506,8 @@ async function handleSubmit() {
|
||||
errorMessage.value = null
|
||||
|
||||
try {
|
||||
// Build the payload matching UserKYCComplete schema (Soft KYC)
|
||||
// Build the payload matching UserKYCComplete schema (no names - already in registration)
|
||||
const payload: any = {
|
||||
first_name: kycForm.first_name,
|
||||
last_name: kycForm.last_name,
|
||||
region_code: kycForm.region_code,
|
||||
address_zip: kycForm.address_zip,
|
||||
address_city: kycForm.address_city,
|
||||
@@ -474,10 +515,11 @@ async function handleSubmit() {
|
||||
preferred_currency: kycForm.preferred_currency,
|
||||
}
|
||||
|
||||
// Soft KYC: Csak akkor küldjük a részletes címet, ha ki van töltve
|
||||
// Optional fields
|
||||
if (kycForm.address_street_name) payload.address_street_name = kycForm.address_street_name
|
||||
if (kycForm.address_street_type) payload.address_street_type = kycForm.address_street_type
|
||||
if (kycForm.address_house_number) payload.address_house_number = kycForm.address_house_number
|
||||
if (kycForm.phone_number) payload.phone_number = kycForm.phone_number
|
||||
|
||||
// Only include identity_docs if at least one document is filled
|
||||
const hasIdCard = kycForm.identity_docs.ID_CARD.number || kycForm.identity_docs.ID_CARD.expiry_date
|
||||
@@ -507,9 +549,21 @@ async function handleSubmit() {
|
||||
errorMessage.value =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
'A KYC kitöltése sikertelen. Kérlek ellenőrizd az adatokat és próbáld újra.'
|
||||
t('kyc.submitError')
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Skip (minimal KYC - just address required) ──
|
||||
async function handleSkip() {
|
||||
// Can only skip if step 1 (address) is completed
|
||||
if (!kycForm.address_zip || !kycForm.address_city) {
|
||||
errorMessage.value = t('kyc.addressRequired')
|
||||
return
|
||||
}
|
||||
|
||||
// Submit with minimal data
|
||||
await handleSubmit()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="relative min-h-screen text-white">
|
||||
<!-- Garage Background Image (no dark overlay — bright, light garage) -->
|
||||
<!-- Garage Background Image -->
|
||||
<div class="fixed inset-0 z-0">
|
||||
<div class="absolute inset-0 bg-[url('@/assets/garage-bg.png')] bg-cover bg-center bg-fixed"></div>
|
||||
|
||||
@@ -21,117 +21,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Spatial UI Layer: Hotspots (always visible, even in Zen mode) ── -->
|
||||
<div class="fixed inset-0 z-[5] pointer-events-none">
|
||||
<div class="relative w-full h-full">
|
||||
<!-- Hotspot 1: Szervizkönyv/Naptár (bal fal) -->
|
||||
<div class="absolute top-[30%] left-[20%] pointer-events-auto group">
|
||||
<!-- Pulzáló hotspot kör -->
|
||||
<div class="relative flex items-center justify-center cursor-pointer">
|
||||
<div class="absolute w-10 h-10 rounded-full bg-[#FF3366]/20 animate-hotspot-ring"></div>
|
||||
<div class="absolute w-10 h-10 rounded-full bg-[#FF3366]/20 animate-hotspot-ring-delayed"></div>
|
||||
<div class="relative w-6 h-6 rounded-full bg-[#FF3366] shadow-[0_0_20px_#FF3366] animate-hotspot-pulse"></div>
|
||||
</div>
|
||||
<!-- Glassmorphism Bento Box (hover opens) -->
|
||||
<div class="absolute mt-4 w-64 opacity-0 pointer-events-none scale-95 transition-all duration-300 group-hover:opacity-100 group-hover:scale-100 group-hover:pointer-events-auto bg-[#04151F]/80 backdrop-blur-xl border border-white/20 rounded-xl p-4 shadow-2xl z-40">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<svg class="w-6 h-6 text-[#00E5A0]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-bold text-white">Szerviznaptár</h3>
|
||||
</div>
|
||||
<p class="text-sm text-white/60">Közelgő események</p>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div class="flex items-center justify-between text-xs text-white/40 border-b border-white/5 pb-1">
|
||||
<span>Olajcsere</span>
|
||||
<span class="text-[#00E5A0]">2 hét</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-white/40 border-b border-white/5 pb-1">
|
||||
<span>Műszaki vizsga</span>
|
||||
<span class="text-[#00E5A0]">3 hónap</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-white/40">
|
||||
<span>Gumi szezonális</span>
|
||||
<span class="text-yellow-400">1 hónap</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hotspot 2: Szerszámok/Beállítások (jobb fal) -->
|
||||
<div class="absolute top-[30%] right-[20%] pointer-events-auto group">
|
||||
<div class="relative flex items-center justify-center cursor-pointer">
|
||||
<div class="absolute w-10 h-10 rounded-full bg-[#FF3366]/20 animate-hotspot-ring"></div>
|
||||
<div class="absolute w-10 h-10 rounded-full bg-[#FF3366]/20 animate-hotspot-ring-delayed"></div>
|
||||
<div class="relative w-6 h-6 rounded-full bg-[#FF3366] shadow-[0_0_20px_#FF3366] animate-hotspot-pulse"></div>
|
||||
</div>
|
||||
<div class="absolute mt-4 w-64 opacity-0 pointer-events-none scale-95 transition-all duration-300 group-hover:opacity-100 group-hover:scale-100 group-hover:pointer-events-auto bg-[#04151F]/80 backdrop-blur-xl border border-white/20 rounded-xl p-4 shadow-2xl z-40">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<svg class="w-6 h-6 text-[#418890]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-bold text-white">Felszerelés</h3>
|
||||
</div>
|
||||
<p class="text-sm text-white/60">Alkatrészek és Skinek</p>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div class="flex items-center justify-between text-xs text-white/40 border-b border-white/5 pb-1">
|
||||
<span>Alkatrész katalógus</span>
|
||||
<span class="text-[#418890]">42 tétel</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-white/40 border-b border-white/5 pb-1">
|
||||
<span>Skin csomagok</span>
|
||||
<span class="text-[#418890]">6 elérhető</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-white/40">
|
||||
<span>Beállítások</span>
|
||||
<span class="text-white/30">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hotspot 3: Diagnosztika (monitor) -->
|
||||
<div class="absolute top-[60%] right-[30%] pointer-events-auto group">
|
||||
<div class="relative flex items-center justify-center cursor-pointer">
|
||||
<div class="absolute w-10 h-10 rounded-full bg-[#FF3366]/20 animate-hotspot-ring"></div>
|
||||
<div class="absolute w-10 h-10 rounded-full bg-[#FF3366]/20 animate-hotspot-ring-delayed"></div>
|
||||
<div class="relative w-6 h-6 rounded-full bg-[#FF3366] shadow-[0_0_20px_#FF3366] animate-hotspot-pulse"></div>
|
||||
</div>
|
||||
<div class="absolute mt-4 w-64 opacity-0 pointer-events-none scale-95 transition-all duration-300 group-hover:opacity-100 group-hover:scale-100 group-hover:pointer-events-auto bg-[#04151F]/80 backdrop-blur-xl border border-white/20 rounded-xl p-4 shadow-2xl z-40">
|
||||
<div class="flex items-center gap-3 mb-3">
|
||||
<svg class="w-6 h-6 text-[#70BC84]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-bold text-white">Élő Diagnosztika</h3>
|
||||
</div>
|
||||
<p class="text-sm text-white/60">Rendszerállapot</p>
|
||||
<div class="mt-3 space-y-2">
|
||||
<div class="flex items-center justify-between text-xs text-white/40 border-b border-white/5 pb-1">
|
||||
<span>Motor állapot</span>
|
||||
<span class="text-[#70BC84]">● Optimális</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-white/40 border-b border-white/5 pb-1">
|
||||
<span>Akkumulátor</span>
|
||||
<span class="text-[#70BC84]">● 87%</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-white/40">
|
||||
<span>Hibakódok</span>
|
||||
<span class="text-white/30">0 találat</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Zen Mode FAB (always visible, above everything) ── -->
|
||||
<!-- ── Zen Mode FAB ── -->
|
||||
<button
|
||||
@click="isUiVisible = !isUiVisible"
|
||||
class="fixed bottom-8 right-8 z-50 btn-premium rounded-full p-4 shadow-2xl shadow-black/40"
|
||||
:title="isUiVisible ? 'UI elrejtése (Zen mód)' : 'UI megjelenítése'"
|
||||
:title="isUiVisible ? t('zen.hide') : t('zen.show')"
|
||||
>
|
||||
<!-- Eye icon when visible, eye-off when hidden -->
|
||||
<svg
|
||||
v-if="isUiVisible"
|
||||
class="w-6 h-6"
|
||||
@@ -153,114 +48,304 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- ── Top Navbar (always visible, even in Zen mode) ──────────────── -->
|
||||
<DashboardHeader
|
||||
:first-name="authStore.user?.first_name || 'Vendég'"
|
||||
subtitle="Irányítópultod és járműveid áttekintése"
|
||||
/>
|
||||
|
||||
<!-- ── Content (above the overlay) with fade transition ── -->
|
||||
<!-- ── Content with fade transition ── -->
|
||||
<Transition name="fade">
|
||||
<div v-if="isUiVisible" class="relative z-10">
|
||||
<div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<!-- ── Loading State ──────────────────────────────────────────── -->
|
||||
<div
|
||||
v-if="vehicleStore.isLoading"
|
||||
class="flex items-center justify-center py-20"
|
||||
>
|
||||
<!-- ── Bottom-aligned 5-card container ── -->
|
||||
<div class="flex flex-col justify-end min-h-[85vh] pb-8">
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 w-full">
|
||||
<!-- ── Loading State ── -->
|
||||
<div
|
||||
class="h-10 w-10 animate-spin rounded-full border-4 border-white/10 border-t-[#70BC84]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- ── Garage Grid ────────────────────────────────────────────── -->
|
||||
<div
|
||||
v-if="!vehicleStore.isLoading"
|
||||
class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3"
|
||||
>
|
||||
<div
|
||||
v-for="vehicle in vehicleStore.vehicles"
|
||||
:key="vehicle.id"
|
||||
class="group relative overflow-hidden rounded-2xl border border-white/[0.08] bg-white/[0.05] p-6 backdrop-blur-xl shadow-xl shadow-black/20 transition-all duration-300 hover:border-[#70BC84]/30 hover:bg-white/[0.08] hover:shadow-2xl hover:shadow-black/30"
|
||||
v-if="vehicleStore.isLoading"
|
||||
class="flex items-center justify-center py-20"
|
||||
>
|
||||
<!-- ── License Plate (EU-style) ──────────────────────────── -->
|
||||
<div class="h-10 w-10 animate-spin rounded-full border-4 border-white/10 border-t-[#70BC84]" />
|
||||
</div>
|
||||
|
||||
<!-- ── 5-Card Grid ── -->
|
||||
<div
|
||||
v-if="!vehicleStore.isLoading"
|
||||
class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-4"
|
||||
style="perspective: 1200px;"
|
||||
>
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
Card 1: 🚗 My Garage (Járműveim)
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div
|
||||
v-if="vehicle.license_plate"
|
||||
class="mb-4 inline-flex items-center overflow-hidden rounded-md border border-gray-600 shadow-sm"
|
||||
class="bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden flex flex-col h-[350px] relative cursor-pointer transition-all duration-300 ease-out transform hover:-translate-y-3 hover:scale-[1.02] hover:shadow-2xl"
|
||||
@click="openCard('vehicles')"
|
||||
>
|
||||
<!-- Blue EU band -->
|
||||
<div class="flex h-9 w-9 items-center justify-center bg-[#003399] text-[10px] font-bold leading-tight text-white">
|
||||
<span class="block text-center leading-tight">SF</span>
|
||||
<!-- Top header bar -->
|
||||
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4">
|
||||
<span class="text-white font-bold text-sm tracking-wide">🚗 {{ t('dashboard.myVehicles') }}</span>
|
||||
<span class="ml-auto text-xs text-white/60">{{ vehicleStore.vehicles.length }} {{ t('dashboard.vehicles') }}</span>
|
||||
</div>
|
||||
<!-- White plate area -->
|
||||
<div class="flex h-9 items-center bg-white px-3">
|
||||
<span class="font-mono text-sm font-bold tracking-widest text-gray-900">
|
||||
{{ vehicle.license_plate }}
|
||||
<!-- Content -->
|
||||
<div class="p-4 flex-1 flex flex-col text-slate-800 overflow-hidden">
|
||||
<div class="flex-1 overflow-y-auto space-y-2">
|
||||
<div
|
||||
v-for="vehicle in vehicleStore.vehicles.slice(0, 4)"
|
||||
:key="vehicle.id"
|
||||
class="rounded-xl border border-slate-200 bg-slate-50 p-3 transition-colors hover:bg-slate-100 cursor-pointer"
|
||||
>
|
||||
<!-- License Plate -->
|
||||
<div
|
||||
v-if="vehicle.license_plate"
|
||||
class="mb-1 inline-flex items-center overflow-hidden rounded-md border border-gray-400 shadow-sm"
|
||||
>
|
||||
<div class="flex h-6 w-6 items-center justify-center bg-[#003399] text-[7px] font-bold leading-tight text-white">
|
||||
<span class="block text-center leading-tight">SF</span>
|
||||
</div>
|
||||
<div class="flex h-6 items-center bg-white px-2">
|
||||
<span class="font-mono text-xs font-bold tracking-widest text-gray-900">
|
||||
{{ vehicle.license_plate }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Brand + Model -->
|
||||
<h3 class="text-sm font-bold text-slate-800">
|
||||
{{ vehicle.brand || t('dashboard.statusUnknown') }}
|
||||
<span v-if="vehicle.model" class="font-normal text-slate-500"> {{ vehicle.model }}</span>
|
||||
</h3>
|
||||
<!-- Mileage -->
|
||||
<p class="mt-0.5 text-xs text-slate-400">
|
||||
{{ vehicle.current_mileage?.toLocaleString() || 0 }} {{ t('dashboard.mileage') }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Empty state -->
|
||||
<div
|
||||
v-if="vehicleStore.vehicles.length === 0"
|
||||
class="flex flex-col items-center justify-center py-6 text-slate-400"
|
||||
>
|
||||
<svg class="w-10 h-10 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<p class="text-sm">{{ t('dashboard.noVehicles') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom CTA button -->
|
||||
<button class="mt-auto mb-4 mx-auto px-8 py-3 bg-slate-700 hover:bg-slate-800 text-white rounded-2xl font-medium text-sm transition-colors shadow-md">
|
||||
+ {{ t('dashboard.addVehicle') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
Card 2: 💰 Costs & TCO (Költségek & Analitika)
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div
|
||||
class="bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden flex flex-col h-[350px] relative cursor-pointer transition-all duration-300 ease-out transform hover:-translate-y-3 hover:scale-[1.02] hover:shadow-2xl"
|
||||
@click="openCard('financials')"
|
||||
>
|
||||
<!-- Top header bar -->
|
||||
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4">
|
||||
<span class="text-white font-bold text-sm tracking-wide">💰 {{ t('dashboard.fuelCost') }} & TCO</span>
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div class="p-4 flex-1 flex flex-col text-slate-800 overflow-hidden">
|
||||
<div class="flex-1 space-y-3">
|
||||
<!-- Monthly cost summary -->
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3">
|
||||
<p class="text-xs text-slate-500 uppercase tracking-wider font-semibold">{{ t('dashboard.monthlyScore') }}</p>
|
||||
<p class="text-2xl font-extrabold text-slate-800 mt-1">₿ 2,450</p>
|
||||
</div>
|
||||
<!-- Stat rows -->
|
||||
<div class="space-y-1.5">
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
|
||||
<span class="text-slate-500">{{ t('dashboard.totalKm') }}</span>
|
||||
<span class="font-semibold text-slate-800">12,430 km</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
|
||||
<span class="text-slate-500">{{ t('dashboard.fuelCost') }}</span>
|
||||
<span class="font-semibold text-slate-800">₿ 0.42</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
|
||||
<span class="text-slate-500">{{ t('dashboard.serviceCost') }}</span>
|
||||
<span class="font-semibold text-slate-800">₿ 1.28</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50 px-3 py-2 text-sm">
|
||||
<span class="text-slate-500">{{ t('dashboard.co2Savings') }}</span>
|
||||
<span class="font-semibold text-emerald-600">-340 kg</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom CTA button -->
|
||||
<button class="mt-auto mb-4 mx-auto px-8 py-3 bg-slate-700 hover:bg-slate-800 text-white rounded-2xl font-medium text-sm transition-colors shadow-md">
|
||||
{{ t('menu.finance') }} →
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
Card 3: 🔧 Service Finder (Szerviz kereső)
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div
|
||||
class="bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden flex flex-col h-[350px] relative cursor-pointer transition-all duration-300 ease-out transform hover:-translate-y-3 hover:scale-[1.02] hover:shadow-2xl"
|
||||
@click="openCard('costs')"
|
||||
>
|
||||
<!-- Top header bar -->
|
||||
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4">
|
||||
<span class="text-white font-bold text-sm tracking-wide">🔧 Service Finder</span>
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div class="p-4 flex-1 flex flex-col text-slate-800 overflow-hidden">
|
||||
<div class="flex-1 space-y-3">
|
||||
<!-- Active notifications / service alerts -->
|
||||
<div
|
||||
v-for="(notif, idx) in notifications"
|
||||
:key="idx"
|
||||
class="flex items-start gap-3 rounded-lg border border-slate-200 bg-slate-50 p-3 text-sm"
|
||||
>
|
||||
<span class="mt-0.5 text-base">{{ notif.icon }}</span>
|
||||
<div>
|
||||
<p class="text-slate-800 font-medium">{{ notif.title }}</p>
|
||||
<p class="text-slate-400 text-xs mt-0.5">{{ notif.time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Empty state -->
|
||||
<div
|
||||
v-if="notifications.length === 0"
|
||||
class="flex flex-col items-center justify-center py-6 text-slate-400"
|
||||
>
|
||||
<svg class="w-10 h-10 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<p class="text-sm">{{ t('dashboard.noNotifications') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom CTA button -->
|
||||
<button class="mt-auto mb-4 mx-auto px-8 py-3 bg-slate-700 hover:bg-slate-800 text-white rounded-2xl font-medium text-sm transition-colors shadow-md">
|
||||
{{ t('menu.features') }} 🔍
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
Card 4: 🏆 Gamification (Játékosítás)
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div
|
||||
class="bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden flex flex-col h-[350px] relative cursor-pointer transition-all duration-300 ease-out transform hover:-translate-y-3 hover:scale-[1.02] hover:shadow-2xl"
|
||||
@click="openCard('gamification')"
|
||||
>
|
||||
<!-- Top header bar -->
|
||||
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4">
|
||||
<span class="text-white font-bold text-sm tracking-wide">🏆 {{ t('dashboard.statsAndPoints') }}</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-medium text-emerald-700">
|
||||
{{ t('dashboard.live') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Brand + Model ─────────────────────────────────────── -->
|
||||
<h3 class="text-xl font-bold text-white">
|
||||
{{ vehicle.brand || 'Ismeretlen' }}
|
||||
<span v-if="vehicle.model" class="font-normal text-white/70">{{ vehicle.model }}</span>
|
||||
</h3>
|
||||
|
||||
<!-- Year of manufacture -->
|
||||
<p v-if="vehicle.year_of_manufacture" class="mt-0.5 text-sm text-white/40">
|
||||
{{ vehicle.year_of_manufacture }}
|
||||
</p>
|
||||
|
||||
<!-- ── Divider ────────────────────────────────────────────── -->
|
||||
<div class="my-4 border-t border-white/[0.06]" />
|
||||
|
||||
<!-- ── Condition Score Bar ────────────────────────────────── -->
|
||||
<div class="mb-3">
|
||||
<div class="mb-1 flex items-center justify-between text-xs">
|
||||
<span class="text-white/50">Állapot</span>
|
||||
<span class="font-semibold text-[#70BC84]">{{ vehicle.condition_score }}%</span>
|
||||
<!-- Content -->
|
||||
<div class="p-4 flex-1 flex flex-col text-slate-800 overflow-hidden">
|
||||
<div class="flex-1 space-y-3">
|
||||
<!-- Score card -->
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3 text-center">
|
||||
<p class="text-3xl font-extrabold text-emerald-600">2,450</p>
|
||||
<p class="text-xs text-slate-500 mt-1">{{ t('dashboard.monthlyScore') }}</p>
|
||||
</div>
|
||||
<!-- Achievement badges -->
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">
|
||||
{{ t('dashboard.achievements') }}
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="badge in badges"
|
||||
:key="badge.label"
|
||||
class="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-slate-50 px-3 py-1 text-xs text-slate-600"
|
||||
>
|
||||
{{ badge.icon }} {{ badge.label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-2 overflow-hidden rounded-full bg-white/10">
|
||||
<div
|
||||
class="h-full rounded-full bg-gradient-to-r from-[#70BC84] to-[#008CA4] transition-all duration-500"
|
||||
:style="{ width: Math.min(vehicle.condition_score, 100) + '%' }"
|
||||
/>
|
||||
<!-- Bottom CTA button -->
|
||||
<button class="mt-auto mb-4 mx-auto px-8 py-3 bg-slate-700 hover:bg-slate-800 text-white rounded-2xl font-medium text-sm transition-colors shadow-md">
|
||||
{{ t('dashboard.refresh') }} 🏆
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
Card 5: 🛡️ My Profile & Trust (Profil & Trust Score)
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div
|
||||
class="bg-white/95 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.12)] overflow-hidden flex flex-col h-[350px] relative cursor-pointer transition-all duration-300 ease-out transform hover:-translate-y-3 hover:scale-[1.02] hover:shadow-2xl"
|
||||
@click="openCard('stats')"
|
||||
>
|
||||
<!-- Top header bar -->
|
||||
<div class="h-12 bg-slate-700 w-full shrink-0 flex items-center px-4">
|
||||
<span class="text-white font-bold text-sm tracking-wide">🛡️ {{ t('header.profile') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Mileage ────────────────────────────────────────────── -->
|
||||
<div class="flex items-center gap-2 text-sm text-white/60">
|
||||
<svg class="h-4 w-4 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M13 10V3L4 14h7v7l9-11h-7z"
|
||||
/>
|
||||
</svg>
|
||||
<span>{{ vehicle.current_mileage?.toLocaleString() || 0 }} km</span>
|
||||
</div>
|
||||
|
||||
<!-- ── Status Badge ───────────────────────────────────────── -->
|
||||
<div class="mt-4 flex items-center gap-2">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||||
:class="vehicle.status === 'active'
|
||||
? 'bg-[#70BC84]/15 text-[#70BC84]'
|
||||
: 'bg-white/5 text-white/40'"
|
||||
>
|
||||
<span
|
||||
class="mr-1.5 inline-block h-1.5 w-1.5 rounded-full"
|
||||
:class="vehicle.status === 'active' ? 'bg-[#70BC84]' : 'bg-white/20'"
|
||||
/>
|
||||
{{ vehicle.status === 'active' ? 'Aktív' : vehicle.status }}
|
||||
</span>
|
||||
<!-- Content -->
|
||||
<div class="p-4 flex-1 flex flex-col text-slate-800 overflow-hidden">
|
||||
<div class="flex-1 space-y-3">
|
||||
<!-- User info -->
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3">
|
||||
<p class="text-sm font-bold text-slate-800">{{ authStore.userName || t('dashboard.guest') }}</p>
|
||||
<p class="text-xs text-slate-400 mt-0.5">{{ authStore.user?.email }}</p>
|
||||
</div>
|
||||
<!-- Trust Score -->
|
||||
<div class="rounded-xl border border-slate-200 bg-slate-50 p-3">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-xs text-slate-500 font-semibold uppercase tracking-wider">Trust Score</span>
|
||||
<span class="text-sm font-bold text-emerald-600">A+</span>
|
||||
</div>
|
||||
<div class="h-2 overflow-hidden rounded-full bg-slate-200">
|
||||
<div class="h-full rounded-full bg-gradient-to-r from-emerald-400 to-emerald-600 transition-all duration-500" style="width: 92%" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Quick stats -->
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="rounded-lg border border-slate-200 bg-slate-50 p-2 text-center">
|
||||
<p class="text-lg font-bold text-slate-800">{{ authStore.myOrganizations.length }}</p>
|
||||
<p class="text-xs text-slate-400">{{ t('header.myCompanies') }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-slate-200 bg-slate-50 p-2 text-center">
|
||||
<p class="text-lg font-bold text-slate-800">{{ vehicleStore.vehicles.length }}</p>
|
||||
<p class="text-xs text-slate-400">{{ t('dashboard.totalVehicles') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bottom CTA button -->
|
||||
<button class="mt-auto mb-4 mx-auto px-8 py-3 bg-slate-700 hover:bg-slate-800 text-white rounded-2xl font-medium text-sm transition-colors shadow-md">
|
||||
{{ t('header.profile') }} →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end relative z-10 content wrapper -->
|
||||
</Transition>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════════
|
||||
Flip Modal – 3D átforduló részletes nézet
|
||||
═══════════════════════════════════════════════════════════════════ -->
|
||||
<div
|
||||
v-if="activeCard"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center bg-slate-900/85 backdrop-blur-md"
|
||||
@click.self="activeCard = null"
|
||||
>
|
||||
<div class="bg-white w-11/12 max-w-6xl h-[85vh] rounded-3xl shadow-2xl flex flex-col overflow-hidden animate-flip-in relative">
|
||||
<!-- Close button (X) -->
|
||||
<button
|
||||
class="absolute top-4 right-4 z-10 flex h-10 w-10 items-center justify-center rounded-full bg-slate-200/80 text-slate-600 hover:bg-slate-300 hover:text-slate-800 transition-colors text-xl font-bold"
|
||||
@click="activeCard = null"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
|
||||
<!-- Dynamic content area -->
|
||||
<div class="flex-1 p-8 overflow-y-auto text-slate-800">
|
||||
<h2 class="text-2xl font-bold mb-6">
|
||||
{{ t('dashboard.detailedView') }}: <span class="text-slate-500">{{ activeCard }}</span>
|
||||
</h2>
|
||||
<p class="text-slate-500">
|
||||
{{ t('dashboard.modalPlaceholder') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- end min-h-screen -->
|
||||
</template>
|
||||
|
||||
@@ -269,17 +354,55 @@ import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useVehicleStore } from '../stores/vehicle'
|
||||
import DashboardHeader from '../components/DashboardHeader.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const vehicleStore = useVehicleStore()
|
||||
|
||||
// ── Zen Mode: UI láthatóság állapota ─────────────────────────────────
|
||||
// ── Zen Mode ──
|
||||
const isUiVisible = ref(true)
|
||||
|
||||
// ── Flip Modal State ──
|
||||
const activeCard = ref<string | null>(null)
|
||||
const openCard = (cardId: string) => {
|
||||
activeCard.value = cardId
|
||||
}
|
||||
|
||||
// ── Placeholder notifications ──
|
||||
const notifications = ref([
|
||||
{ icon: '🔧', title: 'Service due for BMW X5', time: '2 hours ago' },
|
||||
{ icon: '⛽', title: 'Fuel cost updated — +₿ 0.08', time: '5 hours ago' },
|
||||
{ icon: '🏆', title: 'You earned "Eco Driver" badge!', time: '1 day ago' },
|
||||
{ icon: '📊', title: 'Monthly report is ready', time: '2 days ago' },
|
||||
])
|
||||
|
||||
// ── Placeholder badges ──
|
||||
const badges = ref([
|
||||
{ icon: '🌱', label: 'Eco Driver' },
|
||||
{ icon: '📏', label: '10k km Club' },
|
||||
{ icon: '🔧', label: 'Service Pro' },
|
||||
{ icon: '⭐', label: 'Top Rater' },
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
vehicleStore.fetchVehicles()
|
||||
authStore.fetchMyOrganizations()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@keyframes flipIn {
|
||||
from {
|
||||
transform: perspective(1200px) rotateY(-90deg);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: perspective(1200px) rotateY(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.animate-flip-in {
|
||||
animation: flipIn 0.5s cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-[#062535]/95 via-[#062535]/70 to-[#062535]/40"></div>
|
||||
</div>
|
||||
|
||||
<!-- FIXED Top Bar (Kőbe vésett fejléc) -->
|
||||
<!-- FIXED Top Bar -->
|
||||
<header class="fixed top-0 left-0 w-full z-50 bg-[#062535]/80 backdrop-blur-md border-b border-[#65A5A0]/20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
|
||||
|
||||
@@ -17,14 +17,14 @@
|
||||
<span class="text-2xl font-extrabold tracking-wider hidden sm:block"><span class="text-white">SERVICE</span> <span class="text-[#418890]">FINDER</span></span>
|
||||
</router-link>
|
||||
|
||||
<!-- Right: Language Switcher + "Garázs Nyitása" Button (Premium) -->
|
||||
<!-- Right: Language Switcher + "Open Garage" Button (Premium) -->
|
||||
<div class="flex items-center gap-3">
|
||||
<LanguageSwitcher />
|
||||
<button
|
||||
@click="showLogin = true"
|
||||
class="btn-premium px-6 py-2 text-sm"
|
||||
>
|
||||
Garázs Nyitása
|
||||
{{ t('landing.openGarage') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,52 +35,49 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 min-h-screen items-center py-12">
|
||||
|
||||
<!-- LEFT COLUMN: Text Content (Lassan megy lefelé) -->
|
||||
<div
|
||||
<!-- LEFT COLUMN: Text Content -->
|
||||
<div
|
||||
class="space-y-6"
|
||||
:style="{ transform: `translateY(${scrollY * 0.15}px)` }"
|
||||
>
|
||||
<h1 class="text-5xl md:text-6xl lg:text-7xl font-extrabold leading-tight">
|
||||
Digitális Flotta<br/>
|
||||
<span class="text-gradient-premium">Menedzsment</span>
|
||||
{{ t('landing.heroTitle') }}<br/>
|
||||
<span class="text-gradient-premium">{{ t('landing.heroTitleAccent') }}</span>
|
||||
</h1>
|
||||
<p class="text-white/80 text-lg md:text-xl max-w-xl">
|
||||
Valós idejű költségkövetés, automatikus értesítések és gamifikált pontrendszer – minden egy helyen.
|
||||
{{ t('landing.heroSubtitle') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT COLUMN: Játékos Aszimmetrikus Kártyák (Gyorsan úszik felfelé) -->
|
||||
<div
|
||||
<!-- RIGHT COLUMN: Asymmetric Cards -->
|
||||
<div
|
||||
class="relative h-[600px]"
|
||||
:style="{ transform: `translateY(${scrollY * -0.25}px)` }"
|
||||
>
|
||||
|
||||
<!-- Kártya 1: Statisztika (Hátul, fent, kisebb) -->
|
||||
<div
|
||||
<!-- Card 1: Statistics -->
|
||||
<div
|
||||
class="absolute top-0 left-0 w-[280px] z-10 transform -rotate-2 transition-transform duration-300 hover:scale-105 hover:rotate-0"
|
||||
>
|
||||
<!-- Fejléc Sáv (Sötétebb) -->
|
||||
<div class="bg-[#04151f]/60 backdrop-blur-md rounded-t-xl px-5 py-3 border-t border-x border-[#38bdf8]/20">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-[#38bdf8]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/>
|
||||
<polyline points="16 7 22 7 22 13"/>
|
||||
</svg>
|
||||
<span class="text-white/90 text-sm font-semibold uppercase tracking-wide">Statisztika</span>
|
||||
<span class="text-white/90 text-sm font-semibold uppercase tracking-wide">{{ t('landing.cardStats') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tartalom (Világosabb Üveg) -->
|
||||
<div class="bg-[#062535]/40 backdrop-blur-md rounded-b-xl px-5 py-4 border-b border-x border-[#38bdf8]/20 shadow-[0_8px_30px_rgba(6,37,53,0.6)]">
|
||||
<div class="text-white/60 text-xs uppercase tracking-wide mb-2">Kezelt járművek</div>
|
||||
<div class="text-[#38bdf8] text-4xl font-bold font-mono">1,245<span class="text-2xl">+</span></div>
|
||||
<div class="text-white/60 text-xs uppercase tracking-wide mb-2">{{ t('landing.cardStatsLabel') }}</div>
|
||||
<div class="text-[#38bdf8] text-4xl font-bold font-mono">{{ t('landing.cardStatsValue') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kártya 2: Gamifikáció (Középen, kiemelve, NEON) -->
|
||||
<div
|
||||
<!-- Card 2: Gamification -->
|
||||
<div
|
||||
class="absolute top-[120px] right-0 w-[320px] z-30 transform rotate-1 transition-transform duration-300 hover:scale-105 hover:rotate-0"
|
||||
>
|
||||
<!-- Fejléc Sáv (Sötétebb) -->
|
||||
<div class="bg-[#04151f]/60 backdrop-blur-md rounded-t-xl px-5 py-3 border-t border-x border-[#38bdf8]">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-[#38bdf8]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
@@ -91,12 +88,11 @@
|
||||
<path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/>
|
||||
<path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/>
|
||||
</svg>
|
||||
<span class="text-white/90 text-sm font-semibold uppercase tracking-wide">Gamifikáció</span>
|
||||
<span class="text-white/90 text-sm font-semibold uppercase tracking-wide">{{ t('landing.cardGamification') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tartalom (Világosabb Üveg + NEON Keret) -->
|
||||
<div class="bg-[#062535]/40 backdrop-blur-md rounded-b-xl px-5 py-5 border-b border-x border-[#38bdf8] shadow-[0_0_25px_rgba(56,189,248,0.3)]">
|
||||
<div class="text-white/60 text-xs uppercase tracking-wide mb-3">Havi Pontszám</div>
|
||||
<div class="text-white/60 text-xs uppercase tracking-wide mb-3">{{ t('landing.cardGamificationLabel') }}</div>
|
||||
<div class="text-[#38bdf8] text-5xl font-bold font-mono mb-4">2,840</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 h-2 bg-[#0B212F]/50 rounded-full overflow-hidden">
|
||||
@@ -107,21 +103,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kártya 3: Költségek (Lent, elöl, átfedi a gamifikációt) -->
|
||||
<div
|
||||
<!-- Card 3: Costs -->
|
||||
<div
|
||||
class="absolute top-[320px] left-[40px] w-[300px] z-40 transform -rotate-1 transition-transform duration-300 hover:scale-105 hover:rotate-0"
|
||||
>
|
||||
<!-- Fejléc Sáv (Sötétebb) -->
|
||||
<div class="bg-[#04151f]/60 backdrop-blur-md rounded-t-xl px-5 py-3 border-t border-x border-[#65A5A0]/30">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-5 h-5 text-[#65A5A0]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="12" y1="1" x2="12" y2="23"/>
|
||||
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/>
|
||||
</svg>
|
||||
<span class="text-white/90 text-sm font-semibold uppercase tracking-wide">Költségek</span>
|
||||
<span class="text-white/90 text-sm font-semibold uppercase tracking-wide">{{ t('landing.cardCosts') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tartalom (Világosabb Üveg) -->
|
||||
<div class="bg-[#062535]/40 backdrop-blur-md rounded-b-xl px-5 py-4 border-b border-x border-[#65A5A0]/30 shadow-[0_8px_30px_rgba(6,37,53,0.6)]">
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -132,7 +126,7 @@
|
||||
<path d="M14 22V4a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v18"/>
|
||||
<path d="M14 13h2a2 2 0 0 1 2 2v2a2 2 0 0 0 2 2h0a2 2 0 0 0 2-2V9.83a2 2 0 0 0-.59-1.42L18 5"/>
|
||||
</svg>
|
||||
<span class="text-white/70 text-xs">Üzemanyag</span>
|
||||
<span class="text-white/70 text-xs">{{ t('landing.cardCostsFuel') }}</span>
|
||||
</div>
|
||||
<span class="text-white text-lg font-bold font-mono">32.5k</span>
|
||||
</div>
|
||||
@@ -141,14 +135,14 @@
|
||||
<svg class="w-4 h-4 text-[#65A5A0]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
|
||||
</svg>
|
||||
<span class="text-white/70 text-xs">Szerviz</span>
|
||||
<span class="text-white/70 text-xs">{{ t('landing.cardCostsService') }}</span>
|
||||
</div>
|
||||
<span class="text-white text-lg font-bold font-mono">48.0k</span>
|
||||
</div>
|
||||
<div class="pt-2 border-t border-white/10">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-white/60 text-xs uppercase">Összesen</span>
|
||||
<span class="text-[#38bdf8] text-xl font-bold font-mono">80.5k Ft</span>
|
||||
<span class="text-white/60 text-xs uppercase">{{ t('landing.cardCostsTotal') }}</span>
|
||||
<span class="text-[#38bdf8] text-xl font-bold font-mono">80.5k {{ t('landing.cardCostsUnit') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,14 +163,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import Logo from '@/components/Logo.vue'
|
||||
import LoginModal from '@/components/LoginModal.vue'
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const showLogin = ref(false)
|
||||
const scrollY = ref(0)
|
||||
|
||||
// JS Alapú Parallax Görgetés
|
||||
// JS-based Parallax Scrolling
|
||||
function handleScroll() {
|
||||
scrollY.value = window.scrollY
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,8 +21,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="mb-4 text-2xl font-bold text-white">Fiók aktiválása...</h1>
|
||||
<p class="mb-4 text-white/60">Kérlek várj, amíg ellenőrizzük a megerősítő linket.</p>
|
||||
<h1 class="mb-4 text-2xl font-bold text-white">{{ t('verifyEmail.verifying') }}</h1>
|
||||
<p class="mb-4 text-white/60">{{ t('verifyEmail.verifyingDesc') }}</p>
|
||||
</template>
|
||||
|
||||
<!-- ── STATE: Verify Success (Magic Link - Auto-Login) ── -->
|
||||
@@ -35,17 +35,17 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="mb-4 text-2xl font-bold text-white">Sikeres aktiválás és bejelentkezés!</h1>
|
||||
<h1 class="mb-4 text-2xl font-bold text-white">{{ t('verifyEmail.success') }}</h1>
|
||||
<p class="mb-8 text-white/60 leading-relaxed">
|
||||
A fiókod aktív lett, és automatikusan be vagy jelentkezve.
|
||||
{{ autoRedirectCountdown > 0 ? `Átirányítás ${autoRedirectCountdown} másodperc múlva...` : 'Átirányítás...' }}
|
||||
{{ t('verifyEmail.successDesc') }}
|
||||
{{ autoRedirectCountdown > 0 ? t('verifyEmail.redirectCountdown', { seconds: autoRedirectCountdown }) : t('verifyEmail.redirectNow') }}
|
||||
</p>
|
||||
<!-- Premium CTA Button (opcionális gyorsabb átirányításhoz) -->
|
||||
<button
|
||||
@click="goToKyc"
|
||||
class="btn-premium w-full px-4 py-3 text-lg"
|
||||
>
|
||||
Tovább a Profilhoz
|
||||
{{ t('verifyEmail.goToProfile') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
|
||||
<!-- Title -->
|
||||
<h1 class="mb-4 text-2xl font-bold text-white">
|
||||
Erősítsd meg az e-mail címed
|
||||
{{ t('verifyEmail.checkEmail') }}
|
||||
</h1>
|
||||
|
||||
<!-- Error message (shown when token verification failed) -->
|
||||
@@ -74,20 +74,19 @@
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="mb-8 text-white/60 leading-relaxed">
|
||||
Kérlek, erősítsd meg az e-mail címedet a kiküldött linkre kattintva!
|
||||
<p class="mb-8 text-white/60 leading-relaxed" v-html="t('verifyEmail.checkEmailDesc')">
|
||||
</p>
|
||||
|
||||
<!-- Email Input -->
|
||||
<div class="mb-6">
|
||||
<label for="verify-email" class="mb-2 block text-sm font-medium text-white/80 text-left">
|
||||
E-mail cím
|
||||
{{ t('verifyEmail.emailLabel') }}
|
||||
</label>
|
||||
<input
|
||||
id="verify-email"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="pelda@email.com"
|
||||
:placeholder="t('verifyEmail.emailPlaceholder')"
|
||||
autocomplete="email"
|
||||
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"
|
||||
/>
|
||||
@@ -99,7 +98,7 @@
|
||||
:disabled="isResending || !email"
|
||||
class="btn-premium w-full px-4 py-3 text-lg"
|
||||
>
|
||||
{{ isResending ? 'Küldés...' : 'Nem kaptam meg az e-mailt, küldjétek újra' }}
|
||||
{{ isResending ? t('verifyEmail.resending') : t('verifyEmail.resendButton') }}
|
||||
</button>
|
||||
|
||||
<!-- Resend Feedback Message -->
|
||||
@@ -110,6 +109,16 @@
|
||||
>
|
||||
{{ feedbackMessage }}
|
||||
</div>
|
||||
|
||||
<!-- Back to Login Link -->
|
||||
<div class="mt-6 text-center">
|
||||
<router-link
|
||||
to="/"
|
||||
class="text-sm text-white/60 hover:text-[#75A882] transition-colors duration-200 underline"
|
||||
>
|
||||
{{ t('verifyEmail.backToLogin') }}
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,11 +128,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import FingerprintJS from '@fingerprintjs/fingerprintjs'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// ── Device Fingerprinting (FingerprintJS) ──
|
||||
@@ -207,7 +218,7 @@ async function processToken(token: string) {
|
||||
// Failure — token expired or invalid
|
||||
isVerifying.value = false
|
||||
verifySuccess.value = false
|
||||
verifyError.value = 'A link lejárt vagy érvénytelen.'
|
||||
verifyError.value = t('verifyEmail.tokenExpired')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,10 +254,10 @@ async function handleResend() {
|
||||
try {
|
||||
await authStore.resendVerification(email.value)
|
||||
feedbackType.value = 'success'
|
||||
feedbackMessage.value = 'Az új megerősítő e-mail elküldésre került!'
|
||||
feedbackMessage.value = t('verifyEmail.resendSuccess')
|
||||
} catch (err: any) {
|
||||
feedbackType.value = 'error'
|
||||
feedbackMessage.value = authStore.error || 'Az újraküldés sikertelen. Kérlek próbáld újra később.'
|
||||
feedbackMessage.value = authStore.error || t('verifyEmail.resendFailed')
|
||||
} finally {
|
||||
isResending.value = false
|
||||
}
|
||||
|
||||
283
frontend/src/views/organization/CompanyGarageView.vue
Normal file
283
frontend/src/views/organization/CompanyGarageView.vue
Normal file
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<div class="relative min-h-screen">
|
||||
<!-- Background Image: garage_clean.png (clean garage, no hotspots) -->
|
||||
<div class="fixed inset-0 z-0">
|
||||
<div class="absolute inset-0 bg-[url('/garage_clean.png')] bg-cover bg-center bg-fixed"></div>
|
||||
</div>
|
||||
|
||||
<!-- 8-Column Bento Box Exhibition Layout — floor-aligned grid with perspective -->
|
||||
<div class="relative z-20 mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<div
|
||||
class="grid grid-cols-1 lg:grid-cols-8 gap-6 lg:gap-12 xl:gap-16 p-4 xl:p-8 min-h-[85vh] items-end"
|
||||
style="perspective: 1200px;"
|
||||
>
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
CARD 1 — LEFT TOWER (col 1-2)
|
||||
Fleet Overview — solid 95% white background, grounded 3D
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div class="rollup-left col-span-1 lg:col-span-2 h-[40vh]">
|
||||
<GlassCard
|
||||
variant="standard"
|
||||
:hoverable="false"
|
||||
customClass="bg-white/95 backdrop-blur-sm hover:bg-slate-100/95 shadow-2xl drop-shadow-xl rounded-t-lg rounded-b-sm border-b-[8px] border-slate-300"
|
||||
class="h-full flex flex-col"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-8 h-8 text-slate-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-semibold text-slate-900">{{ t('company.fleetTitle') }}</h3>
|
||||
</div>
|
||||
<p class="text-2xl font-bold text-slate-900">{{ t('company.fleetVehicles', { count: 12 }) }}</p>
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-2 h-2 rounded-full bg-green-400"></span>
|
||||
<span class="text-green-700">10 {{ t('company.fleetActive') }}</span>
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5">
|
||||
<span class="w-2 h-2 rounded-full bg-red-400"></span>
|
||||
<span class="text-red-700">2 {{ t('company.fleetInactive') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- Fleet breakdown list — subtle inner boxes -->
|
||||
<div class="mt-2 space-y-2">
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<span class="text-slate-600">{{ t('company.totalKm') || 'Total KM' }}</span>
|
||||
<span class="font-semibold text-slate-900">48,230 km</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<span class="text-slate-600">{{ t('company.avgAge') || 'Avg. Age' }}</span>
|
||||
<span class="font-semibold text-slate-900">3.2 years</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<span class="text-slate-600">{{ t('company.nextService') || 'Next Service' }}</span>
|
||||
<span class="font-semibold text-slate-900">4 vehicles</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
CENTER WRAPPER (col 3-6) — flex column, bottom-aligned
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div class="col-span-1 lg:col-span-4 h-[80vh] flex flex-col justify-end pb-0">
|
||||
<!-- CARD 2 — CENTER-TOP FLOATING (half-width, floats up)
|
||||
Dark blue background, glowing white/yellow text -->
|
||||
<div class="w-1/2 mx-auto aspect-square mb-auto mt-4 floating-center">
|
||||
<GlassCard
|
||||
variant="premium"
|
||||
:hoverable="false"
|
||||
customClass="bg-slate-900/95 backdrop-blur-sm hover:bg-slate-800/95 shadow-[0_10px_40px_rgba(0,0,0,0.3)] rounded-3xl border border-white/10"
|
||||
class="h-full flex flex-col"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="inline-block h-2 w-2 rounded-full bg-yellow-400 animate-pulse" />
|
||||
<h3 class="text-lg font-semibold text-white drop-shadow-md">
|
||||
{{ t('company.notificationsTitle') || 'Daily Notifications' }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="(notif, idx) in notifications"
|
||||
:key="idx"
|
||||
class="flex items-start gap-3 rounded-lg bg-white/10 p-3 text-sm"
|
||||
>
|
||||
<span class="mt-0.5 text-base text-yellow-400">{{ notif.icon }}</span>
|
||||
<div>
|
||||
<p class="text-gray-100 font-medium">{{ notif.title }}</p>
|
||||
<p class="text-gray-400 text-xs mt-0.5">{{ notif.time }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="notifications.length === 0"
|
||||
class="flex flex-col items-center justify-center py-8 text-gray-400"
|
||||
>
|
||||
<svg class="w-10 h-10 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<p class="text-sm">{{ t('dashboard.noNotifications') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GlassCard>
|
||||
</div>
|
||||
|
||||
<!-- CARD 4 — CENTER-BOTTOM ROLLUP (full-width, sits on floor)
|
||||
95% white background, dark text -->
|
||||
<div class="w-full h-[50vh] rollup-base mt-auto">
|
||||
<GlassCard
|
||||
variant="standard"
|
||||
:hoverable="false"
|
||||
customClass="bg-white/95 backdrop-blur-sm hover:bg-slate-100/95 shadow-2xl drop-shadow-xl rounded-t-lg rounded-b-sm border-b-[8px] border-slate-300"
|
||||
class="h-full flex flex-col"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-3 p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-8 h-8 text-slate-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-semibold text-slate-900">
|
||||
{{ t('company.recentCostsTitle') || 'Recent Costs & Repairs' }}
|
||||
</h3>
|
||||
</div>
|
||||
<!-- Cost summary row -->
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<span class="text-slate-600">{{ t('company.totalCosts') || 'Total Costs' }}</span>
|
||||
<span class="font-semibold text-slate-900">₿ 12,450</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<span class="text-slate-600">{{ t('company.avgPerVehicle') || 'Avg / Vehicle' }}</span>
|
||||
<span class="font-semibold text-slate-900">₿ 1,037</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<span class="text-slate-600">{{ t('company.monthlyBudget') || 'Monthly Budget' }}</span>
|
||||
<span class="font-semibold text-[#70BC84]">₿ 15,000</span>
|
||||
</div>
|
||||
<!-- Recent cost items list -->
|
||||
<div class="mt-2 pt-2 border-t border-slate-200">
|
||||
<p class="text-xs font-semibold text-slate-600 uppercase tracking-wider mb-2">
|
||||
{{ t('company.latestEntries') || 'Latest Entries' }}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium text-slate-900">Oil Change — BMW X5</span>
|
||||
<span class="text-xs text-slate-600">Budapest, Kárász Auto</span>
|
||||
</div>
|
||||
<span class="font-semibold text-slate-900">₿ 0.18</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium text-slate-900">Brake Pads — Audi A3</span>
|
||||
<span class="text-xs text-slate-600">Győr, AutoCenter</span>
|
||||
</div>
|
||||
<span class="font-semibold text-slate-900">₿ 0.42</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between rounded-lg bg-slate-50/80 border border-slate-200 px-3 py-2 text-sm">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium text-slate-900">Tire Change — VW Golf</span>
|
||||
<span class="text-xs text-slate-600">Debrecen, Gumiservice</span>
|
||||
</div>
|
||||
<span class="font-semibold text-slate-900">₿ 0.09</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════════════════════════
|
||||
CARD 3 — RIGHT TOWER (col 7-8)
|
||||
Quick Actions — 95% white background, dark text
|
||||
════════════════════════════════════════════════════════════ -->
|
||||
<div class="rollup-right col-span-1 lg:col-span-2 h-[40vh]">
|
||||
<GlassCard
|
||||
variant="standard"
|
||||
:hoverable="false"
|
||||
customClass="bg-white/95 backdrop-blur-sm hover:bg-slate-100/95 shadow-2xl drop-shadow-xl rounded-t-lg rounded-b-sm border-b-[8px] border-slate-300"
|
||||
class="h-full flex flex-col"
|
||||
>
|
||||
<template #default>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="w-8 h-8 text-slate-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<h3 class="text-lg font-semibold text-slate-900">{{ t('company.quickActionsTitle') }}</h3>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 mt-2">
|
||||
<button class="w-full px-4 py-3 rounded-xl bg-slate-100/80 hover:bg-slate-200/80 text-slate-900 font-medium text-sm transition-all duration-200 border border-slate-200">
|
||||
{{ t('company.quickNewWaybill') }}
|
||||
</button>
|
||||
<button class="w-full px-4 py-3 rounded-xl bg-slate-100/80 hover:bg-slate-200/80 text-slate-900 font-medium text-sm transition-all duration-200 border border-slate-200">
|
||||
{{ t('company.quickReportIssue') }}
|
||||
</button>
|
||||
<button class="w-full px-4 py-3 rounded-xl bg-amber-50/80 hover:bg-amber-100/80 text-slate-900 font-medium text-sm transition-all duration-200 border border-amber-200">
|
||||
{{ t('company.quickSchedule') || 'Schedule Service' }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- Recent activity mini-list -->
|
||||
<div class="mt-4 pt-3 border-t border-slate-200">
|
||||
<p class="text-xs font-semibold text-slate-600 uppercase tracking-wider mb-2">
|
||||
{{ t('company.recentActivity') || 'Recent Activity' }}
|
||||
</p>
|
||||
<div class="space-y-1.5 text-sm text-slate-600">
|
||||
<p>• {{ t('company.activityAdded') || 'Added' }} BMW X5</p>
|
||||
<p>• {{ t('company.activityServiced') || 'Serviced' }} Audi A3</p>
|
||||
<p>• {{ t('company.activityInvoice') || 'Invoice' }} #INV-0421</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GlassCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import GlassCard from '../../components/ui/GlassCard.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// ── Placeholder notifications ──
|
||||
const notifications = ref([
|
||||
{ icon: '🔧', title: 'Service due for BMW X5', time: '2 hours ago' },
|
||||
{ icon: '⛽', title: 'Fuel cost updated — +₿ 0.08', time: '5 hours ago' },
|
||||
{ icon: '📊', title: 'Monthly report is ready', time: '1 day ago' },
|
||||
{ icon: '🛞', title: 'Tire change reminder — VW Golf', time: '2 days ago' },
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* ── 3D Rollup Standee Animations (subtle perspective) ── */
|
||||
|
||||
.rollup-left {
|
||||
transform: rotateY(4deg) scale(0.98);
|
||||
transition: transform 0.5s ease-out;
|
||||
}
|
||||
.rollup-left:hover {
|
||||
transform: rotateY(0deg) scale(1.0);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.rollup-right {
|
||||
transform: rotateY(-4deg) scale(0.98);
|
||||
transition: transform 0.5s ease-out;
|
||||
}
|
||||
.rollup-right:hover {
|
||||
transform: rotateY(0deg) scale(1.0);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.floating-center {
|
||||
transform: translateY(0px);
|
||||
transition: transform 0.5s ease-out;
|
||||
}
|
||||
.floating-center:hover {
|
||||
transform: translateY(-5px);
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.rollup-base {
|
||||
transform: translateY(4px) scale(0.98);
|
||||
transition: transform 0.5s ease-out;
|
||||
}
|
||||
.rollup-base:hover {
|
||||
transform: translateY(0px) scale(1.0);
|
||||
z-index: 20;
|
||||
}
|
||||
</style>
|
||||
761
frontend/src/views/organization/CompanyOnboardingView.vue
Normal file
761
frontend/src/views/organization/CompanyOnboardingView.vue
Normal file
@@ -0,0 +1,761 @@
|
||||
<template>
|
||||
<div class="relative flex min-h-screen items-center justify-center text-white">
|
||||
<!-- Background Image -->
|
||||
<div class="fixed inset-0 z-0">
|
||||
<div class="absolute inset-0 bg-[url('@/assets/garage-bg.png')] bg-cover bg-center bg-fixed"></div>
|
||||
</div>
|
||||
|
||||
<!-- Content Container -->
|
||||
<div class="relative z-10 mx-auto w-full max-w-2xl px-4 py-8">
|
||||
<!-- Header Logo / Brand -->
|
||||
<div class="mb-8 text-center">
|
||||
<h1 class="text-3xl font-bold text-white tracking-tight">
|
||||
Service<span class="text-[#00E5A0]">Finder</span>
|
||||
</h1>
|
||||
<p class="text-white/60 text-sm mt-2">{{ t('company.onboardingTitle') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Stepper (3 steps) -->
|
||||
<div class="w-full max-w-lg mx-auto mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div
|
||||
v-for="(step, idx) in steps"
|
||||
:key="idx"
|
||||
class="flex items-center flex-1"
|
||||
>
|
||||
<!-- Step circle + label -->
|
||||
<div class="flex flex-col items-center">
|
||||
<div
|
||||
class="w-12 h-12 rounded-full flex items-center justify-center text-sm font-semibold transition-all duration-300"
|
||||
:class="stepCircleClass(idx)"
|
||||
>
|
||||
<span v-if="currentStep > idx + 1">✓</span>
|
||||
<span v-else>{{ idx + 1 }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs mt-2 transition-colors duration-300 font-medium text-center"
|
||||
:class="currentStep >= idx + 1 ? 'text-[#00E5A0]' : 'text-white/40'"
|
||||
>
|
||||
{{ step.label }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Connector line -->
|
||||
<div
|
||||
v-if="idx < steps.length - 1"
|
||||
class="flex-1 h-1 mx-4 rounded transition-all duration-300"
|
||||
:class="currentStep > idx + 1 ? 'bg-[#00E5A0]' : 'bg-white/10'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Glassmorphism Card with strong shadow -->
|
||||
<div class="w-full max-w-lg mx-auto">
|
||||
<div
|
||||
class="rounded-2xl border border-white/[0.08] bg-slate-800/95 p-8 md:p-10 backdrop-blur-xl shadow-2xl shadow-black/50 transition-all duration-500"
|
||||
>
|
||||
<!-- ==================== STEP 1: Legal Data ==================== -->
|
||||
<div v-if="currentStep === 1">
|
||||
<h2 class="text-2xl font-semibold text-white mb-2">{{ t('company.legalTitle') }}</h2>
|
||||
<p class="text-white/60 text-sm mb-6 leading-relaxed" v-html="t('company.legalDescription')">
|
||||
</p>
|
||||
|
||||
<div class="space-y-5">
|
||||
<!-- Tax number + Lookup button (always visible) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.taxNumber') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<div class="flex gap-2 items-end">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
v-model="form.tax_number"
|
||||
@input="onTaxNumberChange"
|
||||
type="text"
|
||||
placeholder="12345678-1-43"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
@click="lookupTaxNumber"
|
||||
:disabled="isTaxLookupLoading || !form.tax_number"
|
||||
class="px-4 py-3 rounded-xl bg-[#00E5A0]/10 border border-[#00E5A0]/30 text-[#00E5A0] hover:bg-[#00E5A0]/20 hover:border-[#00E5A0]/50 transition-all disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-2 text-sm font-medium whitespace-nowrap"
|
||||
:title="t('company.taxLookupTitle')"
|
||||
>
|
||||
<svg
|
||||
v-if="isTaxLookupLoading"
|
||||
class="animate-spin h-4 w-4 text-[#00E5A0]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<svg
|
||||
v-else
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
{{ t('company.taxLookup') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden fields, shown only after tax lookup or manual reveal -->
|
||||
<template v-if="taxLookedUp">
|
||||
<!-- Full company name -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.fullName') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="form.full_name"
|
||||
type="text"
|
||||
placeholder="pl. Kovács és Társa Kft."
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Short name + Display name -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.shortName') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="form.name"
|
||||
@input="autoFillDisplayName"
|
||||
type="text"
|
||||
placeholder="pl. Kovács Kft."
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.displayName') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="form.display_name"
|
||||
type="text"
|
||||
placeholder="pl. Kovács Kft."
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registration number -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.regNumber') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="form.reg_number"
|
||||
type="text"
|
||||
placeholder="01-02-3456789"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== STEP 2: Address & Finance ==================== -->
|
||||
<div v-if="currentStep === 2">
|
||||
<h2 class="text-2xl font-semibold text-white mb-2">{{ t('company.addressTitle') }}</h2>
|
||||
<p class="text-white/60 text-sm mb-6 leading-relaxed">
|
||||
{{ t('company.addressDescription') }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-5">
|
||||
<!-- Country, Language, Currency -->
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.country') }}</label>
|
||||
<select
|
||||
v-model="form.country_code"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
>
|
||||
<option value="HU" class="bg-[#04151F]">🇭🇺 Magyarország</option>
|
||||
<option value="AT" class="bg-[#04151F]">🇦🇹 Ausztria</option>
|
||||
<option value="SK" class="bg-[#04151F]">🇸🇰 Szlovákia</option>
|
||||
<option value="DE" class="bg-[#04151F]">🇩🇪 Németország</option>
|
||||
<option value="RO" class="bg-[#04151F]">🇷🇴 Románia</option>
|
||||
<option value="CZ" class="bg-[#04151F]">🇨🇿 Csehország</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.language') }}</label>
|
||||
<select
|
||||
v-model="form.language"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
>
|
||||
<option value="hu" class="bg-[#04151F]">Magyar</option>
|
||||
<option value="en" class="bg-[#04151F]">English</option>
|
||||
<option value="de" class="bg-[#04151F]">Deutsch</option>
|
||||
<option value="sk" class="bg-[#04151F]">Slovenčina</option>
|
||||
<option value="ro" class="bg-[#04151F]">Română</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.currency') }}</label>
|
||||
<select
|
||||
v-model="form.default_currency"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
>
|
||||
<option value="HUF" class="bg-[#04151F]">HUF (Ft)</option>
|
||||
<option value="EUR" class="bg-[#04151F]">EUR (€)</option>
|
||||
<option value="USD" class="bg-[#04151F]">USD ($)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ZIP + City -->
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="col-span-1">
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.zipCode') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="form.address_zip"
|
||||
type="text"
|
||||
placeholder="1234"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.city') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="form.address_city"
|
||||
type="text"
|
||||
placeholder="Budapest"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all pr-10"
|
||||
:class="{ 'opacity-60': isCityLoading }"
|
||||
/>
|
||||
<!-- Loading spinner -->
|
||||
<div
|
||||
v-if="isCityLoading"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2"
|
||||
>
|
||||
<svg
|
||||
class="animate-spin h-5 w-5 text-[#00E5A0]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Street details -->
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.streetName') }}</label>
|
||||
<input
|
||||
v-model="form.address_street_name"
|
||||
type="text"
|
||||
placeholder="Kossuth Lajos"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.streetType') }}</label>
|
||||
<select
|
||||
v-model="form.address_street_type"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
>
|
||||
<option value="" disabled class="bg-[#04151F]">{{ t('company.streetType') }}</option>
|
||||
<option value="utca" class="bg-[#04151F]">utca</option>
|
||||
<option value="út" class="bg-[#04151F]">út</option>
|
||||
<option value="tér" class="bg-[#04151F]">tér</option>
|
||||
<option value="körút" class="bg-[#04151F]">körút</option>
|
||||
<option value="sugárút" class="bg-[#04151F]">sugárút</option>
|
||||
<option value="köz" class="bg-[#04151F]">köz</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- House number + additional address fields -->
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.houseNumber') }}</label>
|
||||
<input
|
||||
v-model="form.address_house_number"
|
||||
type="text"
|
||||
placeholder="10/A"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.stairwell') }}</label>
|
||||
<input
|
||||
v-model="form.address_stairwell"
|
||||
type="text"
|
||||
placeholder="1."
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.floor') }}</label>
|
||||
<input
|
||||
v-model="form.address_floor"
|
||||
type="text"
|
||||
placeholder="2."
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.door') }}</label>
|
||||
<input
|
||||
v-model="form.address_door"
|
||||
type="text"
|
||||
placeholder="5"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HRsz (parcel ID) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">{{ t('company.parcelId') }}</label>
|
||||
<input
|
||||
v-model="form.address_hrsz"
|
||||
type="text"
|
||||
placeholder="12345/1"
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5A0] focus:ring-2 focus:ring-[#00E5A0]/30 transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== STEP 3: Contacts ==================== -->
|
||||
<div v-if="currentStep === 3">
|
||||
<h2 class="text-2xl font-semibold text-white mb-2">{{ t('company.contactTitle') }}</h2>
|
||||
<p class="text-white/60 text-sm mb-6 leading-relaxed">
|
||||
{{ t('company.contactDescription') }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-5">
|
||||
<!-- Primary contact name (read-only) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.contactName') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
:value="primaryContact.full_name"
|
||||
type="text"
|
||||
disabled
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white/60 placeholder-white/20 cursor-not-allowed opacity-70"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Primary email (read-only) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.contactEmail') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
:value="primaryContact.email"
|
||||
type="email"
|
||||
disabled
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white/60 placeholder-white/20 cursor-not-allowed opacity-70"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Primary phone (read-only) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-white/80 mb-2">
|
||||
{{ t('company.contactPhone') }} <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
:value="primaryContact.phone"
|
||||
type="tel"
|
||||
disabled
|
||||
class="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white/60 placeholder-white/20 cursor-not-allowed opacity-70"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ==================== Navigation Buttons ==================== -->
|
||||
<div class="flex items-center justify-between mt-8 pt-6 border-t border-white/5">
|
||||
<!-- Cancel / Mégsem button (always visible) -->
|
||||
<button
|
||||
@click="cancelOnboarding"
|
||||
class="px-4 py-3 rounded-xl text-white/50 hover:text-white/80 border border-white/10 hover:border-white/30 transition-all text-sm font-medium flex items-center gap-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
{{ t('company.cancel') }}
|
||||
</button>
|
||||
|
||||
<!-- Back button -->
|
||||
<button
|
||||
v-if="currentStep > 1"
|
||||
@click="prevStep"
|
||||
class="px-5 py-3 rounded-xl text-white/70 hover:text-white border border-white/10 hover:border-white/30 transition-all text-sm font-medium flex items-center gap-2"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
{{ t('company.back') }}
|
||||
</button>
|
||||
<div v-else class="flex-1" />
|
||||
|
||||
<!-- Next / Submit -->
|
||||
<button
|
||||
v-if="currentStep < 3"
|
||||
@click="nextStep"
|
||||
:disabled="isDuplicateCompany"
|
||||
class="px-6 py-3 rounded-xl bg-[#00E5A0] text-[#04151F] font-semibold hover:bg-[#00E5A0]/90 hover:shadow-lg hover:shadow-[#00E5A0]/30 transition-all text-sm flex items-center gap-2"
|
||||
:class="isDuplicateCompany ? 'opacity-40 cursor-not-allowed' : ''"
|
||||
>
|
||||
{{ t('company.next') }}
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-else
|
||||
@click="handleSubmit"
|
||||
:disabled="isSubmitting"
|
||||
class="px-6 py-3 rounded-xl font-semibold transition-all text-sm flex items-center gap-2"
|
||||
:class="isSubmitting
|
||||
? 'bg-[#00E5A0]/50 text-[#04151F]/70 cursor-not-allowed'
|
||||
: 'bg-[#00E5A0] text-[#04151F] hover:bg-[#00E5A0]/90 hover:shadow-lg hover:shadow-[#00E5A0]/30'"
|
||||
>
|
||||
<!-- Spinner -->
|
||||
<svg
|
||||
v-if="isSubmitting"
|
||||
class="animate-spin h-4 w-4 text-[#04151F]"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<span>{{ isSubmitting ? t('company.submitting') : t('company.submit') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Error message -->
|
||||
<div
|
||||
v-if="errorMessage"
|
||||
class="mt-4 p-3 rounded-xl bg-red-500/10 border border-red-500/20 text-red-400 text-sm"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
|
||||
<!-- Success message -->
|
||||
<div
|
||||
v-if="successMessage"
|
||||
class="mt-4 p-3 rounded-xl bg-[#00E5A0]/10 border border-[#00E5A0]/20 text-[#00E5A0] text-sm"
|
||||
>
|
||||
{{ successMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import api from '../../api/axios'
|
||||
import { useAuthStore } from '../../stores/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// ── Steps definition (3 steps) ──
|
||||
const steps = [
|
||||
{ label: t('company.stepLegal'), key: 'legal' },
|
||||
{ label: t('company.stepAddress'), key: 'address' },
|
||||
{ label: t('company.stepContact'), key: 'contact' },
|
||||
]
|
||||
|
||||
const currentStep = ref(1)
|
||||
const isSubmitting = ref(false)
|
||||
const errorMessage = ref<string | null>(null)
|
||||
const successMessage = ref<string | null>(null)
|
||||
const isCityLoading = ref(false)
|
||||
const isTaxLookupLoading = ref(false)
|
||||
const taxLookedUp = ref(false)
|
||||
const isDuplicateCompany = ref(false)
|
||||
|
||||
// ── CorpOnboardIn Form ──
|
||||
const form = reactive({
|
||||
full_name: '',
|
||||
name: '',
|
||||
display_name: '',
|
||||
tax_number: '',
|
||||
reg_number: '',
|
||||
country_code: 'HU',
|
||||
language: 'hu',
|
||||
default_currency: 'HUF',
|
||||
address_zip: '',
|
||||
address_city: '',
|
||||
address_street_name: '',
|
||||
address_street_type: '',
|
||||
address_house_number: '',
|
||||
address_stairwell: '',
|
||||
address_floor: '',
|
||||
address_door: '',
|
||||
address_hrsz: '',
|
||||
})
|
||||
|
||||
// ── Primary contact ──
|
||||
const primaryContact = reactive({
|
||||
email: '',
|
||||
phone: '',
|
||||
full_name: '',
|
||||
})
|
||||
|
||||
// ── Pre-fill primary contact with logged-in user data ──
|
||||
onMounted(() => {
|
||||
if (authStore.user) {
|
||||
const person = authStore.user.person
|
||||
if (authStore.user.email) {
|
||||
primaryContact.email = authStore.user.email
|
||||
}
|
||||
if (person) {
|
||||
if (person.first_name || person.last_name) {
|
||||
primaryContact.full_name = [person.first_name, person.last_name].filter(Boolean).join(' ')
|
||||
}
|
||||
if (person.phone) {
|
||||
primaryContact.phone = person.phone
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// ── Auto-fill display_name from name if display_name is empty ──
|
||||
function autoFillDisplayName() {
|
||||
if (!form.display_name) {
|
||||
form.display_name = form.name
|
||||
}
|
||||
}
|
||||
|
||||
// ── Step circle styling ──
|
||||
function stepCircleClass(idx: number): string {
|
||||
const stepNum = idx + 1
|
||||
if (currentStep.value > stepNum) {
|
||||
return 'bg-[#00E5A0] text-[#04151F] shadow-lg shadow-[#00E5A0]/30'
|
||||
}
|
||||
if (currentStep.value === stepNum) {
|
||||
return 'bg-[#00E5A0]/20 text-[#00E5A0] border-2 border-[#00E5A0] shadow-lg shadow-[#00E5A0]/20'
|
||||
}
|
||||
return 'bg-white/5 text-white/40 border border-white/10'
|
||||
}
|
||||
|
||||
// ── Validation per step ──
|
||||
function validateStep(): boolean {
|
||||
errorMessage.value = null
|
||||
|
||||
if (currentStep.value === 1) {
|
||||
if (!form.tax_number) {
|
||||
errorMessage.value = t('company.taxRequired')
|
||||
return false
|
||||
}
|
||||
if (taxLookedUp.value) {
|
||||
if (!form.full_name) {
|
||||
errorMessage.value = t('company.fullNameRequired')
|
||||
return false
|
||||
}
|
||||
if (!form.name) {
|
||||
errorMessage.value = t('company.shortNameRequired')
|
||||
return false
|
||||
}
|
||||
if (!form.display_name) {
|
||||
errorMessage.value = t('company.displayNameRequired')
|
||||
return false
|
||||
}
|
||||
if (!form.reg_number) {
|
||||
errorMessage.value = t('company.regNumberRequired')
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStep.value === 2) {
|
||||
if (!form.address_zip) {
|
||||
errorMessage.value = t('company.zipRequired')
|
||||
return false
|
||||
}
|
||||
if (!form.address_city) {
|
||||
errorMessage.value = t('company.cityRequired')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStep.value === 3) {
|
||||
if (!primaryContact.email) {
|
||||
errorMessage.value = t('company.emailRequired')
|
||||
return false
|
||||
}
|
||||
if (!primaryContact.phone) {
|
||||
errorMessage.value = t('company.phoneRequired')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ── Reset duplicate flag when tax number changes ──
|
||||
function onTaxNumberChange() {
|
||||
if (isDuplicateCompany.value) {
|
||||
isDuplicateCompany.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Navigation ──
|
||||
function nextStep() {
|
||||
// Block navigation if duplicate company was detected
|
||||
if (isDuplicateCompany.value) return
|
||||
if (!validateStep()) return
|
||||
if (currentStep.value < 3) {
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
function prevStep() {
|
||||
if (currentStep.value > 1) {
|
||||
currentStep.value--
|
||||
errorMessage.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// ── Cancel / Mégsem ──
|
||||
function cancelOnboarding() {
|
||||
router.push('/dashboard')
|
||||
}
|
||||
|
||||
// ── Build contacts array ──
|
||||
function buildContacts(): Array<Record<string, any>> {
|
||||
const contacts: Array<Record<string, any>> = []
|
||||
|
||||
// Primary contact
|
||||
const primary: Record<string, any> = {
|
||||
email: primaryContact.email,
|
||||
phone: primaryContact.phone,
|
||||
is_primary: true,
|
||||
}
|
||||
if (primaryContact.full_name) primary.full_name = primaryContact.full_name
|
||||
contacts.push(primary)
|
||||
|
||||
return contacts
|
||||
}
|
||||
|
||||
// ── Tax number lookup ──
|
||||
async function lookupTaxNumber() {
|
||||
if (!form.tax_number) return
|
||||
isTaxLookupLoading.value = true
|
||||
errorMessage.value = null
|
||||
isDuplicateCompany.value = false
|
||||
try {
|
||||
const res = await api.get('/organizations/lookup-tax/' + form.tax_number)
|
||||
const data = res.data
|
||||
// Auto-fill form fields from response
|
||||
if (data.full_name) form.full_name = data.full_name
|
||||
if (data.name) {
|
||||
form.name = data.name
|
||||
// Auto-fill display_name if empty
|
||||
if (!form.display_name) {
|
||||
form.display_name = data.name
|
||||
}
|
||||
}
|
||||
if (data.address_zip) form.address_zip = data.address_zip
|
||||
if (data.address_city) form.address_city = data.address_city
|
||||
if (data.address_street_name) form.address_street_name = data.address_street_name
|
||||
if (data.address_street_type) form.address_street_type = data.address_street_type
|
||||
if (data.address_house_number) form.address_house_number = data.address_house_number
|
||||
// Reveal the hidden fields
|
||||
taxLookedUp.value = true
|
||||
} catch (err: any) {
|
||||
// Detect 409 Conflict — duplicate company
|
||||
if (err.response?.status === 409) {
|
||||
isDuplicateCompany.value = true
|
||||
errorMessage.value =
|
||||
err.response?.data?.detail ||
|
||||
t('company.duplicateCompany')
|
||||
} else {
|
||||
errorMessage.value =
|
||||
err.response?.data?.detail ||
|
||||
t('company.lookupError')
|
||||
}
|
||||
} finally {
|
||||
isTaxLookupLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Submit ──
|
||||
async function handleSubmit() {
|
||||
if (!validateStep()) return
|
||||
|
||||
isSubmitting.value = true
|
||||
errorMessage.value = null
|
||||
successMessage.value = null
|
||||
|
||||
try {
|
||||
const payload: Record<string, any> = {
|
||||
full_name: form.full_name,
|
||||
name: form.name,
|
||||
display_name: form.display_name,
|
||||
tax_number: form.tax_number,
|
||||
reg_number: form.reg_number,
|
||||
country_code: form.country_code,
|
||||
language: form.language,
|
||||
default_currency: form.default_currency,
|
||||
address_zip: form.address_zip,
|
||||
address_city: form.address_city,
|
||||
contacts: buildContacts(),
|
||||
}
|
||||
|
||||
// Optional address fields
|
||||
if (form.address_street_name) payload.address_street_name = form.address_street_name
|
||||
if (form.address_street_type) payload.address_street_type = form.address_street_type
|
||||
if (form.address_house_number) payload.address_house_number = form.address_house_number
|
||||
if (form.address_stairwell) payload.address_stairwell = form.address_stairwell
|
||||
if (form.address_floor) payload.address_floor = form.address_floor
|
||||
if (form.address_door) payload.address_door = form.address_door
|
||||
if (form.address_hrsz) payload.address_hrsz = form.address_hrsz
|
||||
|
||||
await api.post('/organizations/onboard', payload)
|
||||
|
||||
successMessage.value = t('company.successMessage')
|
||||
|
||||
// Refresh the organization list so the header updates immediately
|
||||
await authStore.fetchMyOrganizations()
|
||||
|
||||
// Navigate to company garage after a short delay
|
||||
setTimeout(() => {
|
||||
router.push('/company/garage')
|
||||
}, 1500)
|
||||
} catch (err: any) {
|
||||
errorMessage.value =
|
||||
err.response?.data?.detail ||
|
||||
err.response?.data?.message ||
|
||||
t('company.submitError')
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user