initial-rbac

This commit is contained in:
firdausfazil 2025-05-29 20:17:32 +08:00
parent 545f5c6c93
commit 8527b25ac4
3 changed files with 1552 additions and 0 deletions

View File

@ -25,6 +25,25 @@ export default [
],
"meta": {}
},
{
"header": "RBAC",
"description": "",
"child": [
{
"title": "Create User",
"path": "/create-user",
"icon": "",
"child": []
},
{
"title": "Permission",
"path": "/rbac-permission",
"icon": "",
"child": []
}
],
"meta": {}
},
{
"header": "Pentadbiran",
"description": "Urus aplikasi anda",

386
pages/create-user/index.vue Normal file
View File

@ -0,0 +1,386 @@
<script setup>
definePageMeta({
title: "Create User",
middleware: ["auth"],
requiresAuth: true,
});
import { ref, reactive } from 'vue'
// Form data
const formData = reactive({
fullName: '',
username: '',
email: '',
phone: '',
password: '',
confirmPassword: '',
status: 'active',
mfaEnabled: true,
mfaMethod: 'authenticator_app'
})
// Form validation
const errors = reactive({})
const isSubmitting = ref(false)
// Status options
const statusOptions = [
{ value: 'active', label: 'Active' },
{ value: 'inactive', label: 'Inactive' },
{ value: 'pending', label: 'Pending' }
]
// MFA Method options
const mfaMethodOptions = [
{ value: 'authenticator_app', label: 'Authenticator App' },
{ value: 'sms', label: 'SMS' },
{ value: 'email', label: 'Email' }
]
// Validation functions
const validateForm = () => {
const newErrors = {}
if (!formData.fullName.trim()) {
newErrors.fullName = 'Full name is required'
}
if (!formData.username.trim()) {
newErrors.username = 'Username is required'
} else if (formData.username.length < 3) {
newErrors.username = 'Username must be at least 3 characters'
}
if (!formData.email.trim()) {
newErrors.email = 'Email is required'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Please enter a valid email address'
}
if (!formData.phone.trim()) {
newErrors.phone = 'Phone number is required'
}
if (!formData.password) {
newErrors.password = 'Password is required'
} else if (formData.password.length < 8) {
newErrors.password = 'Password must be at least 8 characters'
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match'
}
Object.assign(errors, newErrors)
return Object.keys(newErrors).length === 0
}
// Clear specific error
const clearError = (field) => {
delete errors[field]
}
// Submit form
const submitForm = async () => {
if (!validateForm()) {
return
}
isSubmitting.value = true
try {
// Prepare data for submission (exclude confirmPassword)
const { confirmPassword, ...submitData } = formData
// Here you would typically make an API call
console.log('Submitting user data:', submitData)
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000))
// Success handling
alert('User created successfully!')
// Reset form or redirect
// resetForm()
} catch (error) {
console.error('Error creating user:', error)
alert('Error creating user. Please try again.')
} finally {
isSubmitting.value = false
}
}
// Reset form
const resetForm = () => {
Object.assign(formData, {
fullName: '',
username: '',
email: '',
phone: '',
password: '',
confirmPassword: '',
status: 'active',
mfaEnabled: true,
mfaMethod: 'authenticator_app'
})
Object.keys(errors).forEach(key => delete errors[key])
}
</script>
<template>
<div>
<LayoutsBreadcrumb />
<rs-card>
<template #header>
<div class="flex justify-between items-center">
<h2 class="text-xl font-semibold">Create User</h2>
</div>
</template>
<template #body>
<form @submit.prevent="submitForm" class="space-y-6">
<!-- Personal Information Section -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Full Name -->
<div>
<label for="fullName" class="block text-sm font-medium text-gray-700 mb-2">
Full Name <span class="text-red-500">*</span>
</label>
<input
id="fullName"
v-model="formData.fullName"
@input="clearError('fullName')"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:class="{ 'border-red-500': errors.fullName }"
placeholder="Enter full name"
/>
<p v-if="errors.fullName" class="text-red-500 text-sm mt-1">{{ errors.fullName }}</p>
</div>
<!-- Username -->
<div>
<label for="username" class="block text-sm font-medium text-gray-700 mb-2">
Username <span class="text-red-500">*</span>
</label>
<input
id="username"
v-model="formData.username"
@input="clearError('username')"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:class="{ 'border-red-500': errors.username }"
placeholder="Enter username"
/>
<p v-if="errors.username" class="text-red-500 text-sm mt-1">{{ errors.username }}</p>
</div>
<!-- Email -->
<div>
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">
Email Address <span class="text-red-500">*</span>
</label>
<input
id="email"
v-model="formData.email"
@input="clearError('email')"
type="email"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:class="{ 'border-red-500': errors.email }"
placeholder="Enter email address"
/>
<p v-if="errors.email" class="text-red-500 text-sm mt-1">{{ errors.email }}</p>
</div>
<!-- Phone -->
<div>
<label for="phone" class="block text-sm font-medium text-gray-700 mb-2">
Phone Number <span class="text-red-500">*</span>
</label>
<input
id="phone"
v-model="formData.phone"
@input="clearError('phone')"
type="tel"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:class="{ 'border-red-500': errors.phone }"
placeholder="+60123456789"
/>
<p v-if="errors.phone" class="text-red-500 text-sm mt-1">{{ errors.phone }}</p>
</div>
</div>
<!-- Password Section -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Password -->
<div>
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">
Password <span class="text-red-500">*</span>
</label>
<input
id="password"
v-model="formData.password"
@input="clearError('password')"
type="password"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:class="{ 'border-red-500': errors.password }"
placeholder="Enter password"
/>
<p v-if="errors.password" class="text-red-500 text-sm mt-1">{{ errors.password }}</p>
</div>
<!-- Confirm Password -->
<div>
<label for="confirmPassword" class="block text-sm font-medium text-gray-700 mb-2">
Confirm Password <span class="text-red-500">*</span>
</label>
<input
id="confirmPassword"
v-model="formData.confirmPassword"
@input="clearError('confirmPassword')"
type="password"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
:class="{ 'border-red-500': errors.confirmPassword }"
placeholder="Confirm password"
/>
<p v-if="errors.confirmPassword" class="text-red-500 text-sm mt-1">{{ errors.confirmPassword }}</p>
</div>
</div>
<!-- Role and Status Section -->
<!-- MFA Section -->
<div class="border-t pt-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Multi-Factor Authentication (MFA)</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- MFA Enabled -->
<div>
<label class="flex items-center">
<input
v-model="formData.mfaEnabled"
type="checkbox"
class="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
<span class="ml-2 text-sm font-medium text-gray-700">Enable MFA</span>
</label>
<p class="text-sm text-gray-500 mt-1">Enhance account security with multi-factor authentication</p>
</div>
<!-- MFA Method -->
<div v-if="formData.mfaEnabled">
<label for="mfaMethod" class="block text-sm font-medium text-gray-700 mb-2">
MFA Method
</label>
<div class="relative">
<select
id="mfaMethod"
v-model="formData.mfaMethod"
class="appearance-none w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white pr-10 cursor-pointer hover:border-gray-400 transition-colors duration-200"
>
<option v-for="method in mfaMethodOptions" :key="method.value" :value="method.value">
{{ method.label }}
</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
<svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="flex justify-end space-x-4 pt-6 border-t">
<button
type="button"
@click="resetForm"
class="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
:disabled="isSubmitting"
>
Reset
</button>
<button
type="submit"
class="px-4 py-2 bg-blue-600 border border-transparent rounded-md shadow-sm text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
:disabled="isSubmitting"
>
<span v-if="isSubmitting">Creating...</span>
<span v-else>Create User</span>
</button>
</div>
</form>
</template>
</rs-card>
</div>
</template>
<style scoped>
/* Custom styles for better form appearance */
.grid {
display: grid;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
@media (min-width: 768px) {
.md\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
.gap-6 {
gap: 1.5rem;
}
.space-y-6 > * + * {
margin-top: 1.5rem;
}
.border-t {
border-top: 1px solid #e5e7eb;
}
.pt-6 {
padding-top: 1.5rem;
}
/* Focus styles for better accessibility */
input:focus,
select:focus {
outline: none;
ring: 2px;
ring-color: #3b82f6;
border-color: transparent;
}
/* Error state styles */
.border-red-500 {
border-color: #ef4444;
}
.text-red-500 {
color: #ef4444;
}
/* Button styles */
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Checkbox styles */
input[type="checkbox"] {
width: 1rem;
height: 1rem;
}
</style>

File diff suppressed because it is too large Load Diff