Files
space-booking/frontend/src/views/Register.vue
Claude Agent 0b497157ad feat: landing refăcut + înveliș public unificat pentru login/register/book
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>
2026-07-13 08:56:23 +00:00

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>