Files
BarangaySystem/resources/js/Pages/UserRegistration.vue
2026-06-06 18:43:00 +08:00

265 lines
8.1 KiB
Vue

<script setup>
import { usePageTitle } from '../composables/Core/usePageTitle';
usePageTitle('User Registration');
import { ref, computed } from 'vue'
import axios from 'axios'
import { useModal } from '../composables/Core/useModal'
import { h } from 'vue'
const modal = useModal()
// Form state
const mobileNumber = ref('')
const nickname = ref('')
const name = ref('')
const password = ref('')
const confirmPassword = ref('')
// Validation states
const isMobileValid = ref(false)
const isPasswordValid = ref(false)
const isConfirmPasswordValid = ref(false)
const isCheckingMobile = ref(false)
const isNameValid = computed(() => name.value.trim().length > 0)
const hasValidMobileNumberFormat = (number) => /^(09|\+639)\d{9}$/.test(number)
const missingFields = computed(() => {
const fields = []
if (!mobileNumber.value) {
fields.push('Mobile Number')
} else if (!isMobileValid.value) {
if (isCheckingMobile.value) {
fields.push('Checking Mobile Number...')
} else {
fields.push('Valid and Available Mobile Number')
}
}
if (!name.value.trim()) fields.push('Name')
if (password.value.length < 6) fields.push('Password (min 6 characters)')
if (password.value !== confirmPassword.value || !confirmPassword.value) {
fields.push('Passwords matching')
}
return fields
})
const isLoading = ref(false)
const debounce = (fn, delay) => {
let timeoutId
return (...args) => {
if (timeoutId) clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), delay)
}
}
const checkMobileExists = async () => {
const number = mobileNumber.value
if (!number || !hasValidMobileNumberFormat(number)) {
isMobileValid.value = false
return false
}
try {
isCheckingMobile.value = true
const res = await axios.post('/api/public/user/check-mobile', { mobile_number: number })
if (res.data?.exists === true) {
isMobileValid.value = false
return false
}
isMobileValid.value = true
validateAllInputs()
return true
} catch {
isMobileValid.value = false
return false
} finally {
isCheckingMobile.value = false
}
}
const debouncedCheckMobile = debounce(checkMobileExists, 600)
const handleMobileChange = () => { isMobileValid.value = false; debouncedCheckMobile(); }
const validatePassword = () => {
isPasswordValid.value = password.value.length >= 6
validateAllInputs()
return isPasswordValid.value
}
const validateConfirmPassword = () => {
isConfirmPasswordValid.value = password.value === confirmPassword.value && confirmPassword.value.length >= 6
validateAllInputs()
return isConfirmPasswordValid.value
}
const validateAllInputs = () => {}
const handleFormSubmit = () => {
if (missingFields.value.length > 0) {
modal.open({
title: 'Missing Requirements',
body: h('div', { class: 'p-3' }, [
h('div', { class: 'd-flex align-items-center mb-3 text-warning' }, [
h('i', { class: 'fas fa-exclamation-circle fa-2x me-3' }),
h('span', { class: 'fw_7 h5 mb-0' }, 'Required Fields')
]),
h('p', { class: 'text-muted mb-3' }, 'Please complete the following:'),
h('div', { class: 'd-flex flex-wrap gap-2' },
missingFields.value.map(field =>
h('span', { class: 'badge bg-light text-danger border border-danger-subtle rounded-pill px-3 py-2 fw_6' }, field)
)
)
]),
footer: h('button', {
class: 'btn btn-primary w-100 py-3 rounded-xl fw_7 shadow-sm',
onClick: () => modal.close()
}, 'I Understand')
})
return
}
registerUser()
}
const registerUser = async () => {
try {
isLoading.value = true
const payload = {
mobile_number: String(mobileNumber.value),
password: password.value,
nickname: nickname.value || '',
name: name.value,
}
const response = await axios.post('/api/public/user/register', payload)
if (response.data?.success) {
// Navigate to login page on success
setTimeout(() => {
window.location.href = '/login'
}, 1500)
}
} catch (error) {
const data = error.response?.data
const messages = []
if (data?.errors && typeof data.errors === 'object') {
for (const fieldMessages of Object.values(data.errors)) {
if (Array.isArray(fieldMessages)) messages.push(...fieldMessages)
else if (typeof fieldMessages === 'string') messages.push(fieldMessages)
}
}
if (data?.message) messages.push(data.message)
const msgText = messages.length ? messages.join('\n') : 'Registration failed. Please try again.'
modal.open({
title: 'Registration Failed',
body: h('div', { class: 'p-3 text-danger' }, msgText),
footer: null
})
} finally {
isLoading.value = false
}
}
const clearForm = () => {
mobileNumber.value = ''
nickname.value = ''
name.value = ''
password.value = ''
confirmPassword.value = ''
isMobileValid.value = false
isPasswordValid.value = false
isConfirmPasswordValid.value = false
}
const goToLogin = () => { window.location.href = '/login' }
</script>
<template>
<div class="user-registration-page pb-5">
<br><br>
<div class="tf-container">
<h2 class="fw_6 text-center mb-4">Create Your Account</h2>
<p class="text-muted text-center mb-4">Register to start exploring BukidBounty</p>
<div class="card shadow-sm">
<div class="card-body">
<div class="row g-3">
<!-- Mobile Number -->
<div class="col-12">
<div class="input-group">
<span class="input-group-text" :class="isMobileValid ? 'bg-success' : ''">
<i v-if="isCheckingMobile" class="fas fa-spinner fa-spin"></i>
<i v-else-if="isMobileValid" class="fas fa-check text-white"></i>
</span>
<input type="text" class="form-control" placeholder="Mobile Number (e.g., 09123456789)"
v-model="mobileNumber" @input="handleMobileChange">
</div>
</div>
<!-- Nickname -->
<div class="col-12">
<input type="text" class="form-control" placeholder="Nick Name (Optional)"
v-model.trim="nickname" :disabled="isLoading">
</div>
<!-- Name -->
<div class="col-12">
<input type="text" class="form-control" placeholder="Full Name (Required)"
v-model.trim="name" @input="validateAllInputs" :disabled="isLoading">
</div>
<!-- Password -->
<div class="col-12">
<input type="password" class="form-control" placeholder="Password (min 6 characters)"
v-model.trim="password" @input="validatePassword(); validateConfirmPassword()" :disabled="isLoading">
</div>
<!-- Confirm Password -->
<div class="col-12">
<div class="input-group">
<span class="input-group-text" :class="isConfirmPasswordValid ? 'bg-success' : ''"></span>
<input type="password" class="form-control" placeholder="Confirm Password"
v-model.trim="confirmPassword" @input="validateConfirmPassword()" :disabled="isLoading">
</div>
</div>
<div class="col-12 mt-3">
<AnimatedButton
id="RegisterNowButton"
btnClass="btn btn-primary w-100 py-3 shadow-lg rounded-xl fw_7"
@click="handleFormSubmit"
:loading="isLoading"
>
Register Account
</AnimatedButton>
</div>
</div>
</div>
</div>
<div class="text-center mt-4">
<p class="text-muted">
Already have an account?
<a href="/login" class="text-primary fw_6">Log in here</a>
</p>
</div>
</div>
</div>
</template>
<style scoped>
.card {
border-radius: 12px;
}
.rounded-xl {
border-radius: 15px;
}
.headline-gradient {
background: linear-gradient(135deg, #42b983 0%, #2c3e50 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>