add password in admin
This commit is contained in:
@@ -109,6 +109,11 @@ export type UpdateUserPayload = {
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AdminSetUserPasswordPayload = {
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type AssignOwnerPayload = {
|
export type AssignOwnerPayload = {
|
||||||
mobile: string;
|
mobile: string;
|
||||||
fullName?: string | null;
|
fullName?: string | null;
|
||||||
|
|||||||
+8
-1
@@ -1,5 +1,5 @@
|
|||||||
import { api } from './client';
|
import { api } from './client';
|
||||||
import type { UpdateUserPayload, User } from './types';
|
import type { AdminSetUserPasswordPayload, UpdateUserPayload, User } from './types';
|
||||||
|
|
||||||
export function fetchUsers() {
|
export function fetchUsers() {
|
||||||
return api<User[]>('/users');
|
return api<User[]>('/users');
|
||||||
@@ -15,3 +15,10 @@ export function updateUser(id: string, payload: UpdateUserPayload) {
|
|||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setUserPassword(id: string, payload: AdminSetUserPasswordPayload) {
|
||||||
|
return api<User>(`/users/${id}/password`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -130,9 +130,18 @@ async function load() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parsePrice(value: string | number): number | null {
|
||||||
|
if (value === '' || value == null) return null;
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return Number.isFinite(value) && value >= 0 ? value : null;
|
||||||
|
}
|
||||||
|
const n = Number.parseFloat(String(value).replace(/,/g, '').trim());
|
||||||
|
return Number.isFinite(n) && n >= 0 ? n : null;
|
||||||
|
}
|
||||||
|
|
||||||
async function onAdd() {
|
async function onAdd() {
|
||||||
const price = Number.parseFloat(form.value.price.replace(/,/g, ''));
|
const price = parsePrice(form.value.price);
|
||||||
if (!form.value.serviceTemplateId || !Number.isFinite(price) || price < 0) {
|
if (!form.value.serviceTemplateId || price === null) {
|
||||||
error.value = 'نوع خدمت و قیمت معتبر را وارد کنید.';
|
error.value = 'نوع خدمت و قیمت معتبر را وارد کنید.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-2
@@ -37,6 +37,9 @@
|
|||||||
<AppBadge :tone="user.isActive ? 'success' : 'danger'">
|
<AppBadge :tone="user.isActive ? 'success' : 'danger'">
|
||||||
{{ activeLabel(user.isActive) }}
|
{{ activeLabel(user.isActive) }}
|
||||||
</AppBadge>
|
</AppBadge>
|
||||||
|
<AppBadge :tone="user.hasPassword ? 'default' : 'warning'">
|
||||||
|
{{ user.hasPassword ? 'رمز دارد' : 'بدون رمز' }}
|
||||||
|
</AppBadge>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AppButton variant="ghost" class="!w-auto shrink-0 px-3" @click="openEdit(user)">
|
<AppButton variant="ghost" class="!w-auto shrink-0 px-3" @click="openEdit(user)">
|
||||||
@@ -64,6 +67,31 @@
|
|||||||
حساب فعال است
|
حساب فعال است
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<section class="space-y-3 rounded-xl border border-border bg-surface-3 p-3">
|
||||||
|
<h3 class="text-sm font-semibold text-text">
|
||||||
|
{{ editing.hasPassword ? 'تغییر رمز عبور' : 'تنظیم رمز عبور' }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-xs text-muted">
|
||||||
|
اختیاری — در صورت پر کردن، رمز کاربر بدون نیاز به رمز قبلی تنظیم میشود.
|
||||||
|
</p>
|
||||||
|
<AppInput
|
||||||
|
id="userNewPassword"
|
||||||
|
v-model="form.password"
|
||||||
|
label="رمز جدید"
|
||||||
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
ltr
|
||||||
|
/>
|
||||||
|
<AppInput
|
||||||
|
id="userConfirmPassword"
|
||||||
|
v-model="form.confirmPassword"
|
||||||
|
label="تکرار رمز جدید"
|
||||||
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
ltr
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
|
||||||
<AppButton type="submit" :loading="saving">ذخیره</AppButton>
|
<AppButton type="submit" :loading="saving">ذخیره</AppButton>
|
||||||
</form>
|
</form>
|
||||||
</AppModal>
|
</AppModal>
|
||||||
@@ -72,7 +100,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { fetchUsers, updateUser } from '@/api/users';
|
import { fetchUsers, setUserPassword, updateUser } from '@/api/users';
|
||||||
import type { User, UserRole } from '@/api/types';
|
import type { User, UserRole } from '@/api/types';
|
||||||
import { ApiError } from '@/api/client';
|
import { ApiError } from '@/api/client';
|
||||||
import AppBadge from '@/components/ui/AppBadge.vue';
|
import AppBadge from '@/components/ui/AppBadge.vue';
|
||||||
@@ -106,6 +134,8 @@ const form = ref({
|
|||||||
fullName: '',
|
fullName: '',
|
||||||
role: 'customer' as UserRole,
|
role: 'customer' as UserRole,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const roleOptions = [
|
const roleOptions = [
|
||||||
@@ -132,6 +162,8 @@ function openEdit(user: User) {
|
|||||||
fullName: user.fullName ?? '',
|
fullName: user.fullName ?? '',
|
||||||
role: user.role,
|
role: user.role,
|
||||||
isActive: user.isActive,
|
isActive: user.isActive,
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,14 +173,35 @@ function closeEdit() {
|
|||||||
|
|
||||||
async function onSave() {
|
async function onSave() {
|
||||||
if (!editing.value) return;
|
if (!editing.value) return;
|
||||||
|
|
||||||
|
const wantsPassword = Boolean(
|
||||||
|
form.value.password.trim() || form.value.confirmPassword.trim(),
|
||||||
|
);
|
||||||
|
if (wantsPassword) {
|
||||||
|
if (form.value.password.length < 8) {
|
||||||
|
error.value = 'رمز باید حداقل ۸ کاراکتر باشد.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (form.value.password !== form.value.confirmPassword) {
|
||||||
|
error.value = 'تکرار رمز با رمز جدید یکسان نیست.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
error.value = '';
|
error.value = '';
|
||||||
try {
|
try {
|
||||||
const updated = await updateUser(editing.value.id, {
|
let updated = await updateUser(editing.value.id, {
|
||||||
fullName: form.value.fullName.trim() || null,
|
fullName: form.value.fullName.trim() || null,
|
||||||
role: form.value.role,
|
role: form.value.role,
|
||||||
isActive: form.value.isActive,
|
isActive: form.value.isActive,
|
||||||
});
|
});
|
||||||
|
if (wantsPassword) {
|
||||||
|
updated = await setUserPassword(editing.value.id, {
|
||||||
|
password: form.value.password,
|
||||||
|
confirmPassword: form.value.confirmPassword,
|
||||||
|
});
|
||||||
|
}
|
||||||
const idx = users.value.findIndex((u) => u.id === updated.id);
|
const idx = users.value.findIndex((u) => u.id === updated.id);
|
||||||
if (idx >= 0) users.value[idx] = updated;
|
if (idx >= 0) users.value[idx] = updated;
|
||||||
closeEdit();
|
closeEdit();
|
||||||
|
|||||||
Reference in New Issue
Block a user