initial: bootstrap from BukidBountyApp base
This commit is contained in:
198
resources/js/Pages/CreateCoopUser.vue
Normal file
198
resources/js/Pages/CreateCoopUser.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user