feat: add profile, class unenroll, notify toggles, and keep sidebar width

Give admins a profile password page, class removal, and SMS/email/bot checkboxes, and stop the sidebar from shrinking on edit pages.
This commit is contained in:
2026-08-15 23:48:18 +03:30
parent 7b083d5dbc
commit 3f220dda52
13 changed files with 427 additions and 17 deletions
+1
View File
@@ -5,5 +5,6 @@ export const authApi = {
login: (data) => axiosInstance.post('/auth/login', data),
refresh: (refreshToken) => axiosInstance.post('/auth/refresh', { refreshToken }),
logout: (refreshToken) => axiosInstance.post('/auth/logout', { refreshToken }),
changePassword: (data) => axiosInstance.put('/auth/change-password', data),
getSelf: () => axiosInstance.get('/users/user/get-self')
};
+4 -1
View File
@@ -8,5 +8,8 @@ export const classApi = {
create: (data) => axiosInstance.post('/classes/admin/create', data),
update: (id, data) => axiosInstance.put(`/classes/admin/update/${id}`, data),
delete: (id) => axiosInstance.delete(`/classes/admin/delete/${id}`),
registerUsers: (classId, userIds) => axiosInstance.post(`/classes/admin/${classId}/register-users`, { userIds })
registerUsers: (classId, userIds, notify) =>
axiosInstance.post(`/classes/admin/${classId}/register-users`, { userIds, notify }),
removeUser: (classId, userId) =>
axiosInstance.delete(`/classes/admin/${classId}/students/${userId}`)
};
@@ -0,0 +1,44 @@
<!-- /src/components/common/NotifyChannelsField.vue -->
<template>
<fieldset class="notify-channels m-0 p-3 border-1 border-color border-round">
<legend class="font-semibold text-sm px-2">ارسال اطلاعرسانی</legend>
<div class="flex flex-column sm:flex-row flex-wrap gap-3">
<div class="flex align-items-center gap-2">
<Checkbox v-model="notify.sms" binary inputId="notify-sms" />
<label for="notify-sms" class="text-sm cursor-pointer">ارسال پیامک</label>
</div>
<div class="flex align-items-center gap-2">
<Checkbox v-model="notify.email" binary inputId="notify-email" />
<label for="notify-email" class="text-sm cursor-pointer">ارسال ایمیل</label>
</div>
<div class="flex align-items-center gap-2">
<Checkbox v-model="notify.bot" binary inputId="notify-bot" />
<label for="notify-bot" class="text-sm cursor-pointer">ارسال پیام ربات</label>
</div>
</div>
<p class="text-xs text-muted m-0 mt-2 line-height-3">
پیامک در صورت فعال بودن ارسال میشود. ارسال ایمیل و پیام ربات هنوز پیادهسازی نشده است.
</p>
</fieldset>
</template>
<script setup>
import Checkbox from 'primevue/checkbox';
defineProps({
notify: {
type: Object,
required: true
}
});
</script>
<style scoped>
.notify-channels {
background: var(--surface-ground, transparent);
}
.notify-channels legend {
color: var(--text-color);
}
</style>
+9 -1
View File
@@ -1,6 +1,6 @@
<!-- /src/components/layout/AppSidebar.vue -->
<template>
<div class="app-sidebar p-3 surface-card border-left-1 border-color flex flex-column h-full">
<div class="app-sidebar p-3 surface-card border-left-1 border-color flex flex-column h-full flex-shrink-0">
<div class="logo flex align-items-center gap-3 px-2 py-3 mb-3 border-bottom-1 border-color">
<i class="pi pi-graduation-cap text-primary text-3xl"></i>
<span class="font-bold text-xl text-color">گام نو</span>
@@ -62,11 +62,16 @@ function isMenuActive(to) {
<style lang="scss" scoped>
.app-sidebar {
width: 240px;
min-width: 240px;
max-width: 240px;
flex-shrink: 0;
min-height: 100vh;
}
.menu-item {
color: var(--text-secondary);
text-decoration: none;
white-space: nowrap;
flex-shrink: 0;
&:hover {
background-color: var(--surface-hover);
color: var(--text-primary);
@@ -76,5 +81,8 @@ function isMenuActive(to) {
color: var(--primary-color);
font-weight: 700;
}
span {
white-space: nowrap;
}
}
</style>
+1 -4
View File
@@ -122,10 +122,7 @@ const menuItems = ref([
label: t('app.profile'),
icon: 'pi pi-user',
command: () => {
const id = authStore.user?._id || authStore.user?.id;
if (id) {
router.push(`/users/edit/${id}`);
}
router.push('/profile');
}
},
{
+7 -2
View File
@@ -2,9 +2,9 @@
<template>
<div class="dashboard-layout min-h-screen flex flex-column">
<AppTopbar />
<div class="layout-main-container flex flex-grow-1">
<div class="layout-main-container flex flex-grow-1 min-w-0">
<AppSidebar />
<main class="layout-main flex-grow-1 p-4 overflow-x-hidden">
<main class="layout-main flex-grow-1 p-4 overflow-x-hidden min-w-0">
<AppBreadcrumb />
<router-view />
</main>
@@ -23,7 +23,12 @@ import AppBreadcrumb from '@/components/layout/AppBreadcrumb.vue';
background-color: var(--bg-primary);
}
.layout-main-container {
min-width: 0;
}
.layout-main {
min-width: 0;
min-height: calc(100vh - 60px);
}
</style>
+3 -2
View File
@@ -13,7 +13,7 @@
"updated": "تاریخ بروزرسانی",
"loading": "در حال بارگذاری...",
"noData": "اطلاعاتی یافت نشد",
"profile": "پروفایل",
"profile": "پروفایل من",
"logout": "خروج"
},
"auth": {
@@ -42,7 +42,8 @@
"contactInquiries": "درخواست‌های تماس",
"certificates": "گواهی نامه ها",
"logs": "گزارش فعالیت‌ها",
"settings": "تنظیمات"
"settings": "تنظیمات",
"profile": "پروفایل من"
},
"users": {
"title": "مدیریت کاربران",
+6
View File
@@ -25,6 +25,12 @@ export const routes = [
name: 'DashboardHome',
component: () => import('@/views/dashboard/DashboardHomeView.vue')
},
{
path: 'profile',
name: 'Profile',
component: () => import('@/views/profile/ProfileView.vue'),
meta: { title: 'پروفایل' }
},
// Users
{
+63 -2
View File
@@ -52,6 +52,22 @@
<Column header="موبایل">
<template #body="{ data }"><span dir="ltr">{{ data.phoneNumber || '—' }}</span></template>
</Column>
<Column v-if="canManageStudents" header="عملیات" style="width: 90px">
<template #body="{ data }">
<PermissionGate permission="classes:register_users">
<Button
icon="pi pi-user-minus"
text
rounded
size="small"
severity="danger"
v-tooltip.top="'حذف از کلاس'"
:loading="removingUserId === String(data._id || data.id)"
@click="confirmRemoveStudent(data)"
/>
</PermissionGate>
</template>
</Column>
</DataTable>
</div>
@@ -94,19 +110,30 @@
</Column>
</DataTable>
</div>
<ConfirmDeleteDialog
v-model="removeDialogVisible"
title="حذف از کلاس"
:message="removeDialogMessage"
:loading="!!removingUserId"
@confirm="handleRemoveStudent"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { classApi } from '@/api/classApi';
import { sessionApi } from '@/api/sessionApi';
import { usePersianDate } from '@/composables/usePersianDate';
import { useToast } from '@/composables/useToast';
import { usePermission } from '@/composables/usePermission';
import PageHeader from '@/components/common/PageHeader.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import TableSkeleton from '@/components/common/TableSkeleton.vue';
import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
import PermissionGate from '@/components/common/PermissionGate.vue';
import Button from 'primevue/button';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
@@ -114,11 +141,23 @@ import Column from 'primevue/column';
const route = useRoute();
const classId = route.params.id;
const { toPersianDigits, formatJalali } = usePersianDate();
const { showError } = useToast();
const { showError, showSuccess } = useToast();
const { hasPermission } = usePermission();
const canManageStudents = computed(() => hasPermission('classes:register_users'));
const classData = ref(null);
const sessions = ref([]);
const loadingSessions = ref(false);
const removeDialogVisible = ref(false);
const removingUserId = ref('');
const studentToRemove = ref(null);
const removeDialogMessage = computed(() => {
const name = studentToRemove.value?.name;
return name
? `آیا «${name}» از این کلاس حذف شود؟`
: 'آیا این دانشجو از کلاس حذف شود؟';
});
const fetchClass = async () => {
try {
@@ -159,4 +198,26 @@ onMounted(() => {
fetchClass();
fetchSessions();
});
const confirmRemoveStudent = (student) => {
studentToRemove.value = student;
removeDialogVisible.value = true;
};
const handleRemoveStudent = async () => {
const userId = studentToRemove.value?._id || studentToRemove.value?.id;
if (!userId) return;
removingUserId.value = String(userId);
try {
const res = await classApi.removeUser(classId, userId);
classData.value = res.data || res;
removeDialogVisible.value = false;
studentToRemove.value = null;
showSuccess('دانشجو از کلاس حذف شد');
} catch (err) {
showError(err);
} finally {
removingUserId.value = '';
}
};
</script>
+69 -1
View File
@@ -96,6 +96,10 @@
/>
</div>
<div class="mb-3">
<NotifyChannelsField :notify="registerNotify" />
</div>
<DataTable :value="students" class="p-datatable-sm text-sm" emptyMessage="هنوز دانشجویی ثبت‌نام نشده است">
<Column header="#" style="width: 50px">
<template #body="{ index }">{{ toPersianDigits(index + 1) }}</template>
@@ -108,6 +112,22 @@
<span dir="ltr">{{ data.phoneNumber || '—' }}</span>
</template>
</Column>
<Column v-if="canManageStudents" header="عملیات" style="width: 90px">
<template #body="{ data }">
<PermissionGate permission="classes:register_users">
<Button
icon="pi pi-user-minus"
text
rounded
size="small"
severity="danger"
v-tooltip.top="'حذف از کلاس'"
:loading="removingUserId === String(data._id || data.id)"
@click="confirmRemoveStudent(data)"
/>
</PermissionGate>
</template>
</Column>
</DataTable>
</div>
@@ -119,6 +139,14 @@
:default-session-count="defaultSessionCount"
/>
</template>
<ConfirmDeleteDialog
v-model="removeDialogVisible"
title="حذف از کلاس"
:message="removeDialogMessage"
:loading="!!removingUserId"
@confirm="handleRemoveStudent"
/>
</div>
</template>
@@ -132,9 +160,13 @@ import { professorApi } from '@/api/professorApi';
import { userApi } from '@/api/userApi';
import { usePersianDate } from '@/composables/usePersianDate';
import { useToast } from '@/composables/useToast';
import { usePermission } from '@/composables/usePermission';
import PageHeader from '@/components/common/PageHeader.vue';
import AdminNotesField from '@/components/common/AdminNotesField.vue';
import ClassSessionsSection from '@/components/classes/ClassSessionsSection.vue';
import NotifyChannelsField from '@/components/common/NotifyChannelsField.vue';
import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
import PermissionGate from '@/components/common/PermissionGate.vue';
import InputText from 'primevue/inputtext';
import InputNumber from 'primevue/inputnumber';
import Dropdown from 'primevue/select';
@@ -149,14 +181,20 @@ import DatePicker from 'vue3-persian-datetime-picker';
const route = useRoute();
const router = useRouter();
const { showSuccess, showError } = useToast();
const { hasPermission } = usePermission();
const { toPersianDigits, toLatinDigits } = usePersianDate();
const classId = route.params.id;
const queryCourseId = route.query.courseId ? String(route.query.courseId) : null;
const isEditMode = computed(() => !!classId);
const canManageStudents = computed(() => hasPermission('classes:register_users'));
const isSubmitting = ref(false);
const registering = ref(false);
const defaultSessionCount = ref(12);
const registerNotify = reactive({ sms: true, email: true, bot: true });
const removeDialogVisible = ref(false);
const removingUserId = ref('');
const studentToRemove = ref(null);
const courses = ref([]);
const professors = ref([]);
@@ -176,6 +214,13 @@ const form = reactive({
adminNotes: []
});
const removeDialogMessage = computed(() => {
const name = studentToRemove.value?.name;
return name
? `آیا «${name}» از این کلاس حذف شود؟`
: 'آیا این دانشجو از کلاس حذف شود؟';
});
const availableUsers = computed(() => {
const enrolled = new Set(students.value.map((s) => String(s._id)));
return allUsers.value
@@ -308,7 +353,7 @@ const registerSelected = async () => {
if (!selectedUserIds.value.length) return;
registering.value = true;
try {
const res = await classApi.registerUsers(classId, selectedUserIds.value);
const res = await classApi.registerUsers(classId, selectedUserIds.value, { ...registerNotify });
const data = res.data || res;
students.value = data.students || [];
selectedUserIds.value = [];
@@ -320,5 +365,28 @@ const registerSelected = async () => {
}
};
const confirmRemoveStudent = (student) => {
studentToRemove.value = student;
removeDialogVisible.value = true;
};
const handleRemoveStudent = async () => {
const userId = studentToRemove.value?._id || studentToRemove.value?.id;
if (!userId) return;
removingUserId.value = String(userId);
try {
const res = await classApi.removeUser(classId, userId);
const data = res.data || res;
students.value = data.students || [];
removeDialogVisible.value = false;
studentToRemove.value = null;
showSuccess('دانشجو از کلاس حذف شد');
} catch (err) {
showError(err);
} finally {
removingUserId.value = '';
}
};
onMounted(fetchData);
</script>
+8 -1
View File
@@ -120,6 +120,8 @@
<label class="font-semibold text-sm">تاریخ سررسید *</label>
<DatePicker v-model="createForm.dueDate" class="w-full text-sm" placeholder="1403/01/01" />
</div>
<NotifyChannelsField :notify="createNotify" />
</div>
<template #footer>
<Button label="انصراف" text severity="secondary" @click="showCreateModal = false" />
@@ -148,6 +150,7 @@ import DataTableWrapper from '@/components/common/DataTableWrapper.vue';
import ConfirmDeleteDialog from '@/components/common/ConfirmDeleteDialog.vue';
import PermissionGate from '@/components/common/PermissionGate.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import NotifyChannelsField from '@/components/common/NotifyChannelsField.vue';
import Button from 'primevue/button';
import Column from 'primevue/column';
import Tag from 'primevue/tag';
@@ -182,6 +185,7 @@ const createForm = reactive({
amount: 0,
dueDate: ''
});
const createNotify = reactive({ sms: true, email: true, bot: true });
const deleteDialogVisible = ref(false);
const selectedPayment = ref(null);
@@ -238,6 +242,9 @@ const resetCreateForm = () => {
createForm.classes = [];
createForm.amount = 0;
createForm.dueDate = '';
createNotify.sms = true;
createNotify.email = true;
createNotify.bot = true;
};
const openCreateModal = () => {
@@ -270,7 +277,7 @@ const handleCreatePayment = async () => {
isCreating.value = true;
try {
await paymentApi.create(createForm);
await paymentApi.create({ ...createForm, notify: { ...createNotify } });
showSuccess('صورتحساب جدید با موفقیت ایجاد شد');
showCreateModal.value = false;
loadData();
+203
View File
@@ -0,0 +1,203 @@
<!-- /src/views/profile/ProfileView.vue -->
<template>
<div class="profile-view w-full max-w-4xl mx-auto">
<PageHeader title="پروفایل من" subtitle="مشاهده اطلاعات حساب و تغییر رمز عبور" />
<div v-if="isLoading" class="surface-card p-5 border-round border-1 border-color shadow-sm">
<p class="text-muted text-sm m-0">در حال بارگذاری پروفایل</p>
</div>
<template v-else-if="profile">
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm mb-4">
<div class="flex align-items-center gap-3 mb-4 pb-3 border-bottom-1 border-color">
<Avatar icon="pi pi-user" size="large" shape="circle" class="bg-primary text-white" />
<div>
<h2 class="text-lg font-bold text-color m-0">{{ profile.name || profile.username }}</h2>
<p class="text-muted text-sm m-0 mt-1" dir="ltr">{{ profile.username }}</p>
</div>
</div>
<div class="grid">
<div class="col-12 sm:col-6 md:col-4 mb-3">
<span class="text-muted text-xs block mb-1">نام</span>
<span class="font-semibold text-color text-sm">{{ profile.name || '—' }}</span>
</div>
<div class="col-12 sm:col-6 md:col-4 mb-3">
<span class="text-muted text-xs block mb-1">نام کاربری</span>
<span class="font-semibold text-color text-sm" dir="ltr">{{ profile.username || '—' }}</span>
</div>
<div class="col-12 sm:col-6 md:col-4 mb-3">
<span class="text-muted text-xs block mb-1">نقش</span>
<Tag :value="roleLabel" severity="info" />
</div>
<div class="col-12 sm:col-6 md:col-4 mb-3">
<span class="text-muted text-xs block mb-1">شماره همراه</span>
<span class="font-semibold text-color text-sm" dir="ltr">{{ profile.phoneNumber || '—' }}</span>
</div>
<div class="col-12 sm:col-6 md:col-4 mb-3">
<span class="text-muted text-xs block mb-1">ایمیل</span>
<span class="font-semibold text-color text-sm" dir="ltr">{{ profile.email || '—' }}</span>
</div>
<div class="col-12 sm:col-6 md:col-4 mb-3">
<span class="text-muted text-xs block mb-1">وضعیت حساب</span>
<StatusTag :status="profile.isActive !== false" />
</div>
</div>
</div>
<div class="surface-card p-4 sm:p-5 border-round border-1 border-color shadow-sm">
<h2 class="text-lg font-bold text-color m-0 mb-1">تغییر رمز عبور</h2>
<p class="text-muted text-sm mt-1 mb-4">
برای تغییر رمز، ابتدا رمز فعلی را وارد کنید. پس از ذخیره، نشستهای دیگر از حساب خارج میشوند.
</p>
<form @submit.prevent="handleChangePassword" class="grid">
<div class="col-12 md:col-6 flex flex-column gap-2">
<label class="font-semibold text-sm" for="current-password">رمز عبور فعلی *</label>
<Password
inputId="current-password"
v-model="passwordForm.currentPassword"
toggleMask
:feedback="false"
class="w-full"
inputClass="w-full text-sm"
autocomplete="current-password"
/>
</div>
<div class="col-12 md:col-6"></div>
<div class="col-12 md:col-6 flex flex-column gap-2">
<label class="font-semibold text-sm" for="new-password">رمز عبور جدید *</label>
<Password
inputId="new-password"
v-model="passwordForm.newPassword"
toggleMask
:feedback="false"
class="w-full"
inputClass="w-full text-sm"
autocomplete="new-password"
/>
<span class="text-xs text-muted">حداقل ۶ نویسه</span>
</div>
<div class="col-12 md:col-6 flex flex-column gap-2">
<label class="font-semibold text-sm" for="confirm-password">تکرار رمز عبور جدید *</label>
<Password
inputId="confirm-password"
v-model="passwordForm.confirmPassword"
toggleMask
:feedback="false"
class="w-full"
inputClass="w-full text-sm"
autocomplete="new-password"
/>
</div>
<div class="col-12 flex justify-content-end gap-2 mt-3 pt-3 border-top-1 border-color">
<Button type="submit" label="تغییر رمز عبور" icon="pi pi-lock" :loading="isSubmitting" />
</div>
</form>
</div>
</template>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue';
import { authApi } from '@/api/authApi';
import { useAuthStore } from '@/stores/authStore';
import { useToast } from '@/composables/useToast';
import PageHeader from '@/components/common/PageHeader.vue';
import StatusTag from '@/components/common/StatusTag.vue';
import Avatar from 'primevue/avatar';
import Tag from 'primevue/tag';
import Password from 'primevue/password';
import Button from 'primevue/button';
const MIN_PASSWORD_LENGTH = 6;
const authStore = useAuthStore();
const { showSuccess, showError } = useToast();
const isLoading = ref(true);
const isSubmitting = ref(false);
const profile = ref(null);
const passwordForm = reactive({
currentPassword: '',
newPassword: '',
confirmPassword: ''
});
const roleLabel = computed(() => {
const role = profile.value?.role;
if (!role) return '—';
return typeof role === 'object' ? (role.name || '—') : String(role);
});
const resetPasswordForm = () => {
passwordForm.currentPassword = '';
passwordForm.newPassword = '';
passwordForm.confirmPassword = '';
};
const fetchProfile = async () => {
isLoading.value = true;
try {
const res = await authApi.getSelf();
profile.value = res.data || res;
if (profile.value) {
authStore.setUser({
...(authStore.user || {}),
...profile.value
});
}
} catch (err) {
showError(err);
} finally {
isLoading.value = false;
}
};
const handleChangePassword = async () => {
if (!passwordForm.currentPassword) {
showError('رمز عبور فعلی را وارد کنید');
return;
}
if (!passwordForm.newPassword || passwordForm.newPassword.length < MIN_PASSWORD_LENGTH) {
showError('رمز عبور جدید باید حداقل ۶ نویسه باشد');
return;
}
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
showError('رمز عبور جدید و تکرار آن یکسان نیستند');
return;
}
if (passwordForm.newPassword === passwordForm.currentPassword) {
showError('رمز عبور جدید باید با رمز فعلی متفاوت باشد');
return;
}
isSubmitting.value = true;
try {
await authApi.changePassword({
currentPassword: passwordForm.currentPassword,
newPassword: passwordForm.newPassword,
refreshToken: authStore.refreshToken
});
resetPasswordForm();
showSuccess('رمز عبور با موفقیت تغییر کرد');
} catch (err) {
showError(err);
} finally {
isSubmitting.value = false;
}
};
onMounted(fetchProfile);
</script>
<style scoped>
:deep(.p-password) {
width: 100%;
}
:deep(.p-password-input) {
width: 100%;
}
</style>
+9 -3
View File
@@ -3,7 +3,7 @@
<div class="user-form-view w-full max-w-4xl mx-auto">
<PageHeader
:title="isEditMode ? $t('users.editUser') : $t('users.addUser')"
:subtitle="isEditMode ? 'ویرایش اطلاعات حساب کاربر' : 'ایجاد حساب کاربری جدید — نام کاربری و رمز عبور به‌صورت خودکار ساخته و پیامک می‌شود'"
:subtitle="isEditMode ? 'ویرایش اطلاعات حساب کاربر' : 'ایجاد حساب کاربری جدید — نام کاربری و رمز عبور به‌صورت خودکار ساخته می‌شود'"
>
<Button label="انصراف" text severity="secondary" @click="$router.push('/users')" />
</PageHeader>
@@ -123,9 +123,13 @@
<InputText v-model="form.password" type="password" class="w-full text-sm" dir="ltr" placeholder="در صورت عدم تغییر خالی بگذارید" />
</div>
<div v-if="!isEditMode" class="col-12">
<NotifyChannelsField :notify="createNotify" />
</div>
<div v-if="!isEditMode" class="col-12">
<p class="text-sm text-color-secondary m-0 line-height-3">
نام کاربری و رمز عبور ساده بهصورت خودکار ساخته میشود و از طریق پیامک برای کاربر ارسال میگردد. بعداً میتوان رمز را تغییر داد.
نام کاربری و رمز عبور ساده بهصورت خودکار ساخته میشود. اگر ارسال پیامک فعال باشد، اطلاعات ورود برای کاربر پیامک میگردد. بعداً میتوان رمز را تغییر داد.
</p>
</div>
@@ -176,6 +180,7 @@ import { useToast } from '@/composables/useToast';
import PageHeader from '@/components/common/PageHeader.vue';
import AdminNotesField from '@/components/common/AdminNotesField.vue';
import UserFilesSection from '@/components/uploader/UserFilesSection.vue';
import NotifyChannelsField from '@/components/common/NotifyChannelsField.vue';
import InputText from 'primevue/inputtext';
import Dropdown from 'primevue/select';
import MultiSelect from 'primevue/multiselect';
@@ -193,6 +198,7 @@ const filesSection = ref(null);
const roles = ref([]);
const credentialsVisible = ref(false);
const createdCredentials = reactive({ username: '', password: '' });
const createNotify = reactive({ sms: true, email: true, bot: true });
const messengerOptions = [
{ label: 'پیامک (SMS)', value: 'SMS' },
@@ -342,7 +348,7 @@ const handleSubmit = async () => {
showSuccess('اطلاعات کاربر با موفقیت ویرایش شد');
router.push('/users');
} else {
const res = await userApi.create(payload);
const res = await userApi.create({ ...payload, notify: { ...createNotify } });
const data = res.data || res;
const creds = data.generatedCredentials || {};
createdCredentials.username = creds.username || data.username || '';