Landing: - hero cu un singur CTA (scoase „Vezi cum arată", „Rezervă fără cont" și nota) - „Spații deschise rezervării acum" pe poziția 2, în bandă evidențiată cu accent; listează toate proprietățile publice active (fără filtrul list_on_landing) - showcase-ul mutat în bandă neutră etichetată „Demonstrație" ca să nu se confunde cu controale reale Pagini publice: - PublicNav.vue + PublicFooter.vue extrase și refolosite de Landing, LegalPage și noul PublicShell.vue (login/register/book primesc header/footer de landing, cu cele 8 teme; tokenii --color-* remapați la paleta landing în scope-ul shell) - pe mobil: nav compact cu meniu hamburger (drawer ancorat dreapta, iconițe lucide, închidere la tap în afară/Escape); logo-ul Romfast rămâne în footer Fix-uri: - QR-ul proprietății encodează URL-ul cu slug (fallback pe id) - deep-link-urile către rutele manager/admin nu mai sar la /dashboard: guard-ul așteaptă authStore.ready înainte de verificarea rolurilor - drawer-ul nu se mai închidea singur la tap pe iconiță (listener pe pointerdown + gardă pentru target detașat după re-render) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
205 lines
4.6 KiB
Vue
205 lines
4.6 KiB
Vue
<template>
|
|
<PublicShell hide-register>
|
|
<div class="register-container">
|
|
<div class="register-card card">
|
|
<h2>Create Account</h2>
|
|
<p class="subtitle">Sign up for Space Booking</p>
|
|
|
|
<form @submit.prevent="handleRegister">
|
|
<div class="form-group">
|
|
<label for="email">Email</label>
|
|
<input
|
|
id="email"
|
|
v-model="form.email"
|
|
type="email"
|
|
required
|
|
placeholder="your@email.com"
|
|
autocomplete="email"
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">Password</label>
|
|
<input
|
|
id="password"
|
|
v-model="form.password"
|
|
type="password"
|
|
required
|
|
placeholder="Enter password"
|
|
autocomplete="new-password"
|
|
/>
|
|
<small class="help-text"
|
|
>At least 8 characters, with uppercase, lowercase, and digit</small
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="confirm_password">Confirm Password</label>
|
|
<input
|
|
id="confirm_password"
|
|
v-model="form.confirm_password"
|
|
type="password"
|
|
required
|
|
placeholder="Confirm password"
|
|
autocomplete="new-password"
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="full_name">Full Name</label>
|
|
<input
|
|
id="full_name"
|
|
v-model="form.full_name"
|
|
type="text"
|
|
required
|
|
placeholder="John Doe"
|
|
autocomplete="name"
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="organization">Organization</label>
|
|
<input
|
|
id="organization"
|
|
v-model="form.organization"
|
|
type="text"
|
|
required
|
|
placeholder="Your Organization"
|
|
autocomplete="organization"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="errorMessage" class="error">
|
|
{{ errorMessage }}
|
|
</div>
|
|
|
|
<div v-if="successMessage" class="success">
|
|
{{ successMessage }}
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary btn-block" :disabled="loading">
|
|
{{ loading ? 'Creating account...' : 'Register' }}
|
|
</button>
|
|
</form>
|
|
|
|
<p class="login-link">
|
|
Already have an account? <router-link to="/login">Login</router-link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</PublicShell>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { authApi, handleApiError } from '@/services/api'
|
|
import PublicShell from '@/components/PublicShell.vue'
|
|
|
|
const router = useRouter()
|
|
|
|
const form = ref({
|
|
email: '',
|
|
password: '',
|
|
confirm_password: '',
|
|
full_name: '',
|
|
organization: ''
|
|
})
|
|
|
|
const loading = ref(false)
|
|
const errorMessage = ref('')
|
|
const successMessage = ref('')
|
|
|
|
const handleRegister = async () => {
|
|
try {
|
|
loading.value = true
|
|
errorMessage.value = ''
|
|
successMessage.value = ''
|
|
|
|
const response = await authApi.register(form.value)
|
|
successMessage.value = response.message
|
|
|
|
// Redirect to login after 3 seconds
|
|
setTimeout(() => {
|
|
router.push('/login')
|
|
}, 3000)
|
|
} catch (error: unknown) {
|
|
errorMessage.value = handleApiError(error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.register-container {
|
|
flex: 1;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 3rem 1rem;
|
|
}
|
|
|
|
.register-card {
|
|
width: 100%;
|
|
max-width: 450px;
|
|
}
|
|
|
|
h2 {
|
|
text-align: center;
|
|
margin-bottom: 0.5rem;
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.subtitle {
|
|
text-align: center;
|
|
color: var(--color-text-secondary);
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.help-text {
|
|
display: block;
|
|
margin-top: 0.25rem;
|
|
font-size: 0.85rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.btn-block {
|
|
width: 100%;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.login-link {
|
|
text-align: center;
|
|
margin-top: 1.5rem;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.login-link a {
|
|
color: var(--color-accent);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.login-link a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.error {
|
|
margin-top: 1rem;
|
|
padding: 0.75rem;
|
|
background: color-mix(in srgb, var(--color-danger) 10%, transparent);
|
|
border-left: 3px solid var(--color-danger);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--color-danger);
|
|
}
|
|
|
|
.success {
|
|
margin-top: 1rem;
|
|
padding: 0.75rem;
|
|
background: color-mix(in srgb, var(--color-success) 10%, transparent);
|
|
border-left: 3px solid var(--color-success);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--color-success);
|
|
}
|
|
</style>
|