268 lines
8.6 KiB
Vue
268 lines
8.6 KiB
Vue
<script setup lang="ts">
|
||
import { Head, useForm } from '@inertiajs/vue3';
|
||
import { Mail, LogOut, ArrowRight } from 'lucide-vue-next';
|
||
import Button from '@/components/ui/button.vue';
|
||
import Spinner from '@/components/ui/spinner.vue';
|
||
import UserLayout from '@/layouts/user/userlayout.vue';
|
||
|
||
defineProps<{
|
||
status?: string;
|
||
verificationUrl?: string | null;
|
||
hasCode?: boolean;
|
||
}>();
|
||
|
||
const form = useForm({});
|
||
const codeForm = useForm({ code: '' });
|
||
|
||
const submit = () => {
|
||
form.post('/email/verification-notification');
|
||
};
|
||
|
||
const submitCode = () => {
|
||
// Normalize to digits-only and limit to 6 characters before submitting
|
||
const digits = (codeForm.code || '').toString().replace(/\D+/g, '').slice(0, 6);
|
||
// @ts-expect-error inertia form model is mutable
|
||
codeForm.code = digits;
|
||
codeForm.post('/email/verify/code');
|
||
};
|
||
|
||
|
||
const logout = () => {
|
||
useForm({}).post('/logout');
|
||
};
|
||
</script>
|
||
|
||
<template>
|
||
<UserLayout>
|
||
<Head title="Verify Email" />
|
||
|
||
<div class="login-container">
|
||
<!-- Animated Background -->
|
||
<div class="glow-orb orb-1"></div>
|
||
<div class="glow-orb orb-2"></div>
|
||
|
||
<div class="login-card">
|
||
<div class="card-header">
|
||
<div class="icon-wrapper mb-6">
|
||
<Mail class="w-10 h-10 text-[#ff007a] animate-pulse-slow" />
|
||
</div>
|
||
<h1 class="title">UNLOCK <span class="highlight">ACCESS</span></h1>
|
||
<p class="subtitle">Verify your email to start playing</p>
|
||
</div>
|
||
|
||
<div v-if="status === 'verification-link-sent'" class="mb-6 text-center text-sm font-bold text-green-400 bg-green-500/10 p-4 rounded-xl border border-green-500/20 shadow-[0_0_15px_rgba(74,222,128,0.1)]">
|
||
<div class="flex items-center justify-center gap-2">
|
||
<span class="w-2 h-2 bg-green-400 rounded-full animate-ping"></span>
|
||
Link sent! Check your inbox.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="text-center text-[#888] text-sm mb-8 leading-relaxed px-4">
|
||
We've sent a verification link to your email address. <br>
|
||
Please click it to activate your account and claim your welcome bonus.
|
||
</div>
|
||
|
||
<form @submit.prevent="submit" class="form-content">
|
||
<Button
|
||
type="submit"
|
||
class="w-full h-14 text-base font-black tracking-widest uppercase neon-button group"
|
||
:disabled="form.processing"
|
||
>
|
||
<Spinner v-if="form.processing" class="mr-2" />
|
||
<span v-else class="flex items-center justify-center gap-2">
|
||
Resend Email <ArrowRight class="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||
</span>
|
||
</Button>
|
||
|
||
|
||
<!-- Code entry (fallback #3) -->
|
||
<div v-if="hasCode" class="mt-6">
|
||
<div class="text-xs text-[#888] text-center mb-2">Oder gib den Verifizierungs‑Code aus der Email ein:</div>
|
||
<div class="flex items-center gap-2">
|
||
<input
|
||
type="text"
|
||
inputmode="numeric"
|
||
autocomplete="one-time-code"
|
||
maxlength="6"
|
||
placeholder="XXXXXX"
|
||
class="flex-1 bg-[#0b0b0b] border border-[#222] rounded-lg px-3 py-3 text-sm tracking-[0.4em] text-center text-[#ddd]"
|
||
v-model="codeForm.code"
|
||
@input="codeForm.code = String(($event.target as HTMLInputElement).value).replace(/\D+/g,'').slice(0,6)"
|
||
@keydown.enter.prevent="submitCode"
|
||
/>
|
||
<Button type="button" class="px-4 py-3" :disabled="codeForm.processing" @click="submitCode">
|
||
<Spinner v-if="codeForm.processing" class="mr-2" />
|
||
<span v-else>Verify</span>
|
||
</Button>
|
||
</div>
|
||
<div v-if="codeForm.errors.code" class="mt-2 text-xs text-red-400 text-center">{{ codeForm.errors.code }}</div>
|
||
</div>
|
||
|
||
<div class="text-center mt-8">
|
||
<button
|
||
type="button"
|
||
@click="logout"
|
||
class="text-[#444] hover:text-white transition-colors flex items-center justify-center gap-2 text-xs font-bold uppercase tracking-widest border border-transparent hover:border-[#333] px-4 py-2 rounded-lg"
|
||
>
|
||
<LogOut class="w-3 h-3" /> Log Out
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</UserLayout>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* Container & Layout */
|
||
.login-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: calc(100vh - 100px);
|
||
padding: 20px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.login-card {
|
||
width: 100%;
|
||
max-width: 480px;
|
||
background: rgba(10, 10, 10, 0.8);
|
||
backdrop-filter: blur(20px);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 24px;
|
||
padding: 50px 40px;
|
||
position: relative;
|
||
z-index: 10;
|
||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
||
animation: slideUp 0.8s cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
|
||
/* Header */
|
||
.card-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
.icon-wrapper {
|
||
background: rgba(255, 0, 122, 0.05);
|
||
padding: 20px;
|
||
border-radius: 50%;
|
||
border: 1px solid rgba(255, 0, 122, 0.2);
|
||
box-shadow: 0 0 30px rgba(255, 0, 122, 0.1);
|
||
position: relative;
|
||
}
|
||
.icon-wrapper::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -5px;
|
||
border-radius: 50%;
|
||
border: 1px solid rgba(255, 0, 122, 0.1);
|
||
animation: spin 10s linear infinite;
|
||
border-top-color: transparent;
|
||
border-bottom-color: transparent;
|
||
}
|
||
|
||
.title {
|
||
font-size: 2rem;
|
||
font-weight: 900;
|
||
color: white;
|
||
letter-spacing: 2px;
|
||
margin: 0;
|
||
line-height: 1;
|
||
}
|
||
.highlight {
|
||
color: #ff007a;
|
||
text-shadow: 0 0 20px rgba(255, 0, 122, 0.5);
|
||
}
|
||
.subtitle {
|
||
color: #666;
|
||
font-size: 0.85rem;
|
||
margin-top: 8px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1.5px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* Neon Button */
|
||
.neon-button {
|
||
background: linear-gradient(90deg, #ff007a, #be005b);
|
||
border: none;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition: all 0.3s ease;
|
||
}
|
||
.neon-button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 0 30px rgba(255, 0, 122, 0.6);
|
||
filter: brightness(1.1);
|
||
}
|
||
.neon-button:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
filter: grayscale(1);
|
||
}
|
||
|
||
/* Background Orbs */
|
||
.glow-orb {
|
||
position: absolute;
|
||
border-radius: 50%;
|
||
filter: blur(80px);
|
||
opacity: 0.4;
|
||
z-index: 0;
|
||
animation: float 10s infinite ease-in-out;
|
||
}
|
||
.orb-1 {
|
||
width: 300px;
|
||
height: 300px;
|
||
background: #ff007a;
|
||
top: -50px;
|
||
left: -100px;
|
||
}
|
||
.orb-2 {
|
||
width: 400px;
|
||
height: 400px;
|
||
background: #00f2ff;
|
||
bottom: -100px;
|
||
right: -100px;
|
||
animation-delay: -5s;
|
||
}
|
||
|
||
/* Animations */
|
||
@keyframes slideUp {
|
||
from { opacity: 0; transform: translateY(40px) scale(0.95); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
@keyframes float {
|
||
0%, 100% { transform: translate(0, 0); }
|
||
50% { transform: translate(20px, 30px); }
|
||
}
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
.animate-pulse-slow {
|
||
animation: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: .7; }
|
||
}
|
||
|
||
/* Mobile tweaks */
|
||
@media (max-width: 480px) {
|
||
.login-container { padding: calc(env(safe-area-inset-top, 0) + 10px) 12px 12px; }
|
||
.login-card { padding: 28px 16px; border-radius: 18px; }
|
||
.title { font-size: clamp(20px, 6vw, 28px); letter-spacing: 1px; }
|
||
.subtitle { font-size: 12px; }
|
||
.icon-wrapper { padding: 14px; }
|
||
.glow-orb { filter: blur(60px); }
|
||
.orb-1 { width: 180px; height: 180px; top: -40px; left: -60px; }
|
||
.orb-2 { width: 220px; height: 220px; bottom: -60px; right: -60px; }
|
||
/* Prevent iOS zoom on input */
|
||
input[type="text"] { font-size: 16px; }
|
||
}
|
||
</style>
|