Initialer Laravel Commit für BetiX
This commit is contained in:
281
resources/js/pages/auth/ResetPassword.vue
Normal file
281
resources/js/pages/auth/ResetPassword.vue
Normal file
@@ -0,0 +1,281 @@
|
||||
<script setup lang="ts">
|
||||
import { Head, useForm } from '@inertiajs/vue3';
|
||||
import { Eye, EyeOff } from 'lucide-vue-next';
|
||||
import { computed, ref } from 'vue';
|
||||
import InputError from '@/components/InputError.vue';
|
||||
import Button from '@/components/ui/button.vue';
|
||||
import Input from '@/components/ui/input.vue';
|
||||
import Label from '@/components/ui/label.vue';
|
||||
import Spinner from '@/components/ui/spinner.vue';
|
||||
import UserLayout from '@/layouts/user/userlayout.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
token: string;
|
||||
email: string;
|
||||
}>();
|
||||
|
||||
const form = useForm({
|
||||
token: props.token,
|
||||
email: props.email,
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
});
|
||||
|
||||
const showPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
|
||||
const submit = () => {
|
||||
form.post('/reset-password', {
|
||||
onFinish: () => form.reset('password', 'password_confirmation'),
|
||||
});
|
||||
};
|
||||
|
||||
// Password Strength Logic
|
||||
const passwordStrength = computed(() => {
|
||||
const pwd = form.password;
|
||||
let score = 0;
|
||||
if (!pwd) return 0;
|
||||
if (pwd.length > 6) score += 20;
|
||||
if (pwd.length > 10) score += 20;
|
||||
if (/[A-Z]/.test(pwd)) score += 20;
|
||||
if (/[0-9]/.test(pwd)) score += 20;
|
||||
if (/[^A-Za-z0-9]/.test(pwd)) score += 20;
|
||||
return score;
|
||||
});
|
||||
|
||||
const strengthColor = computed(() => {
|
||||
const s = passwordStrength.value;
|
||||
if (s < 40) return 'bg-red-500';
|
||||
if (s < 80) return 'bg-yellow-500';
|
||||
return 'bg-green-500';
|
||||
});
|
||||
|
||||
const strengthLabel = computed(() => {
|
||||
const s = passwordStrength.value;
|
||||
if (s < 40) return 'Weak';
|
||||
if (s < 80) return 'Medium';
|
||||
return 'Strong';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UserLayout>
|
||||
<Head title="Reset Password" />
|
||||
|
||||
<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">
|
||||
<h1 class="title">NEW <span class="highlight">PASSWORD</span></h1>
|
||||
<p class="subtitle">Secure your account</p>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="submit" class="form-content">
|
||||
|
||||
<div class="input-group">
|
||||
<Label for="email">Email Address</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
v-model="form.email"
|
||||
required
|
||||
readonly
|
||||
class="opacity-50 cursor-not-allowed"
|
||||
/>
|
||||
<InputError :message="form.errors.email" />
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<Label for="password">New Password</Label>
|
||||
<div class="input-wrapper">
|
||||
<Input
|
||||
id="password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
v-model="form.password"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="new-password"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
<div class="status-icon cursor-pointer pointer-events-auto hover:text-white text-[#666] transition-colors" @click="showPassword = !showPassword">
|
||||
<Eye v-if="!showPassword" class="w-4 h-4" />
|
||||
<EyeOff v-else class="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Password Strength Indicator -->
|
||||
<div class="h-1 w-full bg-gray-800 rounded-full mt-2 overflow-hidden">
|
||||
<div class="h-full transition-all duration-500" :class="strengthColor" :style="{ width: `${passwordStrength}%` }"></div>
|
||||
</div>
|
||||
<p class="text-xs text-right mt-1" :class="{'text-red-500': passwordStrength < 40, 'text-yellow-500': passwordStrength >= 40 && passwordStrength < 80, 'text-green-500': passwordStrength >= 80}">{{ strengthLabel }}</p>
|
||||
<InputError :message="form.errors.password" />
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<Label for="password_confirmation">Confirm Password</Label>
|
||||
<div class="input-wrapper">
|
||||
<Input
|
||||
id="password_confirmation"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
v-model="form.password_confirmation"
|
||||
required
|
||||
autocomplete="new-password"
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
<div class="status-icon cursor-pointer pointer-events-auto hover:text-white text-[#666] transition-colors" @click="showConfirmPassword = !showConfirmPassword">
|
||||
<Eye v-if="!showConfirmPassword" class="w-4 h-4" />
|
||||
<EyeOff v-else class="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<InputError :message="form.errors.password_confirmation" />
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<Button
|
||||
type="submit"
|
||||
class="w-full h-12 text-base font-bold tracking-widest uppercase neon-button relative overflow-hidden"
|
||||
:disabled="form.processing"
|
||||
>
|
||||
<div v-if="form.processing" class="absolute inset-0 bg-black/20 flex items-center justify-center">
|
||||
<Spinner class="w-5 h-5" />
|
||||
</div>
|
||||
<span :class="{ 'opacity-0': form.processing }">Reset Password</span>
|
||||
</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: 450px;
|
||||
background: rgba(10, 10, 10, 0.8);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 24px;
|
||||
padding: 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;
|
||||
}
|
||||
.title {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 900;
|
||||
color: white;
|
||||
letter-spacing: 2px;
|
||||
margin: 0;
|
||||
}
|
||||
.highlight {
|
||||
color: #ff007a;
|
||||
text-shadow: 0 0 20px rgba(255, 0, 122, 0.5);
|
||||
}
|
||||
.subtitle {
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Form Content */
|
||||
.form-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
.input-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Input Wrapper for Icons */
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.status-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 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); }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user