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

199 lines
7.7 KiB
Vue

<script setup>
import { ref, computed, onMounted } from 'vue';
import axios from 'axios';
import { useChapters } from '../composables/useChapters.js';
import { useNavigate } from '../composables/Core/useNavigate.js';
import { usePageTitle } from '../composables/Core/usePageTitle';
usePageTitle('Create Member');
const { fetchOfficerScope, loading } = useChapters();
const { navigate } = useNavigate();
const ownChapter = ref(null);
const cooperative = ref(null);
const form = ref({
name: '',
username: '',
mobile_number: '',
password: '',
});
const fieldErrors = ref({});
const errorMessage = ref('');
const submitting = ref(false);
const done = ref(false);
const mobileError = ref('');
const mobileTaken = ref(false);
const usernameTaken = ref(false);
const validateMobile = (val) => {
if (!val) { mobileError.value = 'Mobile number is required.'; return false; }
if (!/^(09|\+639)\d{9}$/.test(val)) {
mobileError.value = 'Must be a valid Philippine mobile number (e.g. 09XXXXXXXXX).';
return false;
}
mobileError.value = '';
return true;
};
const checkMobile = async () => {
if (!validateMobile(form.value.mobile_number)) return;
try {
const res = await axios.post('/admin/user/number/exists', { mobile_number: form.value.mobile_number });
mobileTaken.value = !!res.data?.exists;
} catch (e) { /* ignore */ }
};
const checkUsername = async () => {
if (!form.value.username) { usernameTaken.value = false; return; }
try {
const res = await axios.post('/admin/user/username/exists', { username: form.value.username });
usernameTaken.value = !!res.data?.exists;
} catch (e) { /* ignore */ }
};
const canSubmit = computed(() =>
form.value.name &&
form.value.username &&
form.value.mobile_number &&
form.value.password &&
!mobileError.value &&
!mobileTaken.value &&
!usernameTaken.value &&
ownChapter.value?.hashkey
);
const submit = async () => {
if (submitting.value || !canSubmit.value) return;
fieldErrors.value = {};
errorMessage.value = '';
submitting.value = true;
try {
const res = await axios.post('/api/public/chapter/register', {
chapter_hash: ownChapter.value.hashkey,
name: form.value.name,
username: form.value.username,
mobile_number: form.value.mobile_number,
password: form.value.password,
});
if (res.data.success) {
done.value = true;
} else {
errorMessage.value = res.data.message || 'Failed to create member.';
}
} catch (err) {
if (err.response?.data?.errors) fieldErrors.value = err.response.data.errors;
else errorMessage.value = err.response?.data?.message || 'An error occurred.';
} finally {
submitting.value = false;
}
};
const reset = () => {
form.value = { name: '', username: '', mobile_number: '', password: '' };
fieldErrors.value = {};
errorMessage.value = '';
mobileTaken.value = false;
usernameTaken.value = false;
done.value = false;
};
onMounted(async () => {
const scope = await fetchOfficerScope();
ownChapter.value = scope?.own_chapter ?? null;
cooperative.value = scope?.cooperative ?? null;
});
</script>
<template>
<div class="container py-4" style="max-width: 560px;">
<h5 class="fw-bold mb-3"><i class="fas fa-user-plus me-2"></i>Create Member</h5>
<div v-if="loading && !ownChapter" class="text-center py-5">
<div class="spinner-border text-primary" role="status"></div>
</div>
<div v-else-if="!ownChapter" class="text-center py-5 text-muted">
<i class="fas fa-exclamation-triangle fa-2x text-warning mb-2"></i>
<p>You are not assigned to a chapter, so you cannot create members.</p>
</div>
<div v-else-if="done" class="text-center py-5">
<i class="fas fa-check-circle fa-4x text-success mb-3"></i>
<h5 class="fw-bold">Member Created!</h5>
<p class="text-muted">The new member was added to <strong>{{ ownChapter.name }}</strong>.</p>
<button class="btn btn-outline-primary rounded-pill px-4 me-2" @click="reset">Add Another</button>
<button class="btn btn-primary rounded-pill px-4" @click="navigate({ page: 'Home' })">Done</button>
</div>
<div v-else class="info-card rounded-4 p-4">
<div class="assign-note rounded-3 p-2 mb-3 small">
<i class="fas fa-map-marker-alt me-1"></i>
Will be added to: <strong>{{ ownChapter.name }}</strong>
<span v-if="cooperative"> · {{ cooperative.name }}</span>
</div>
<div v-if="errorMessage" class="alert alert-danger rounded-3 small py-2">{{ errorMessage }}</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Full Name</label>
<input v-model="form.name" type="text" class="form-control rounded-pill"
:class="{ 'is-invalid': fieldErrors.name }" placeholder="Juan Dela Cruz" />
<div v-if="fieldErrors.name" class="invalid-feedback">{{ fieldErrors.name[0] }}</div>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Username</label>
<input v-model="form.username" type="text" class="form-control rounded-pill"
:class="{ 'is-invalid': usernameTaken || fieldErrors.username }"
placeholder="juandelacruz" autocomplete="off" @blur="checkUsername" />
<div v-if="usernameTaken" class="invalid-feedback d-block">Username already taken.</div>
<div v-else-if="fieldErrors.username" class="invalid-feedback d-block">{{ fieldErrors.username[0] }}</div>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold">Mobile Number</label>
<input v-model="form.mobile_number" type="tel" class="form-control rounded-pill"
:class="{ 'is-invalid': mobileError || mobileTaken || fieldErrors.mobile_number }"
placeholder="09XXXXXXXXX" @blur="checkMobile" />
<div v-if="mobileError" class="invalid-feedback d-block">{{ mobileError }}</div>
<div v-else-if="mobileTaken" class="invalid-feedback d-block">Mobile number already taken.</div>
<div v-else-if="fieldErrors.mobile_number" class="invalid-feedback d-block">{{ fieldErrors.mobile_number[0] }}</div>
</div>
<div class="mb-4">
<label class="form-label small fw-semibold">Password</label>
<input v-model="form.password" type="password" class="form-control rounded-pill"
:class="{ 'is-invalid': fieldErrors.password }" placeholder="Min. 6 characters" autocomplete="new-password" />
<div v-if="fieldErrors.password" class="invalid-feedback">{{ fieldErrors.password[0] }}</div>
</div>
<button class="btn btn-primary rounded-pill w-100 py-2 fw-semibold" :disabled="submitting || !canSubmit" @click="submit">
<span v-if="submitting" class="spinner-border spinner-border-sm me-2"></span>
<i v-else class="fas fa-user-plus me-2"></i>
{{ submitting ? 'Creating...' : 'Create Member' }}
</button>
</div>
</div>
</template>
<style scoped>
.info-card {
background: var(--bg-card);
color: var(--text-primary);
border: 1px solid rgba(0, 0, 0, 0.06);
}
.assign-note {
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid rgba(0, 0, 0, 0.08);
}
:global(.dark-mode) .info-card,
:global(.dark-mode) .assign-note {
border-color: rgba(255, 255, 255, 0.08);
}
</style>