diff --git a/src/App.vue b/src/App.vue
index d297b13..1895828 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -3,7 +3,11 @@
+
+
+
+
diff --git a/src/api/groups.ts b/src/api/groups.ts
index a122f39..dcbac8f 100644
--- a/src/api/groups.ts
+++ b/src/api/groups.ts
@@ -71,7 +71,7 @@ export default {
},
deleteMany: async (IDs: number[]): Promise<[AxiosResponse?, string?]> => {
try {
- const data = await $axios.delete(`v1/groups`, {
+ const data = await $axios.delete('v1/groups', {
data: {
array: IDs,
},
diff --git a/src/api/users.ts b/src/api/users.ts
index b5b4e88..e6a1d8d 100644
--- a/src/api/users.ts
+++ b/src/api/users.ts
@@ -3,6 +3,7 @@ import { AxiosResponse } from 'axios';
import { OrderBy } from '@/types/order-by';
import { camelToSnake } from '@/mixins/case-converter';
import { setURIParams } from '@/mixins/query-params';
+import { NewCredentials } from '@/types/new-credentials';
export default {
getAll: async (
@@ -73,7 +74,7 @@ export default {
},
deleteMany: async (IDs: number[]): Promise<[AxiosResponse?, string?]> => {
try {
- const data = await $axios.delete(`v1/users`, {
+ const data = await $axios.delete('v1/users', {
data: {
array: IDs,
},
@@ -85,7 +86,7 @@ export default {
},
blockMany: async (IDs: number[]): Promise<[AxiosResponse?, string?]> => {
try {
- const data = await $axios.patch(`v1/users/block`, {
+ const data = await $axios.patch('v1/users/block', {
array: IDs,
});
return [data, undefined];
@@ -95,7 +96,7 @@ export default {
},
unblockMany: async (IDs: number[]): Promise<[AxiosResponse?, string?]> => {
try {
- const data = await $axios.patch(`v1/users/unblock`, {
+ const data = await $axios.patch('v1/users/unblock', {
array: IDs,
});
return [data, undefined];
@@ -103,4 +104,14 @@ export default {
return [undefined, e?.response?.data?.error ?? e.message];
}
},
+ resetPassword: async (
+ ID: number,
+ ): Promise<[AxiosResponse?, string?]> => {
+ try {
+ const data = await $axios.patch(`v1/users/password/${ID}`);
+ return [data, undefined];
+ } catch (e: any) {
+ return [undefined, e?.response?.data?.error ?? e.message];
+ }
+ },
};
diff --git a/src/components/InfoModal.vue b/src/components/InfoModal.vue
index 0935692..9e4bd8d 100644
--- a/src/components/InfoModal.vue
+++ b/src/components/InfoModal.vue
@@ -1,23 +1,23 @@
-
+
warning
- {{ $confirmation.title }}
+ {{ $info.title }}
- {{ $confirmation.message }}
+ {{ $info.message }}
@@ -25,16 +25,8 @@ const $confirmation = useConfirmationStore();
-
-
diff --git a/src/locales/en/actions.json b/src/locales/en/actions.json
index e61d40d..4c7934c 100644
--- a/src/locales/en/actions.json
+++ b/src/locales/en/actions.json
@@ -10,6 +10,7 @@
"unblock": "Unblock",
"apply": "Apply",
"confirm": "Confirm",
- "cancel": "Cancel"
+ "cancel": "Cancel",
+ "hide": "Hide"
}
}
\ No newline at end of file
diff --git a/src/locales/en/users.json b/src/locales/en/users.json
index 25b6640..f7224ad 100644
--- a/src/locales/en/users.json
+++ b/src/locales/en/users.json
@@ -45,6 +45,10 @@
"title": "Delete users",
"message": "Are you sure you want to delete {0} users? This action cannot be undone"
}
+ },
+ "new-credentials": {
+ "title": "Login details",
+ "message": "E-Mail address: {0}\nPassword: {1}\n\nRemember or write down this data, it will be impossible to find it out again"
}
}
}
\ No newline at end of file
diff --git a/src/locales/ru/actions.json b/src/locales/ru/actions.json
index 8438ef6..3331c94 100644
--- a/src/locales/ru/actions.json
+++ b/src/locales/ru/actions.json
@@ -10,6 +10,7 @@
"unblock": "Разблокировать",
"apply": "Применить",
"confirm": "Подтвердить",
- "cancel": "Отменить"
+ "cancel": "Отменить",
+ "hide": "Скрыть"
}
}
\ No newline at end of file
diff --git a/src/locales/ru/users.json b/src/locales/ru/users.json
index dbb432d..d026d7e 100644
--- a/src/locales/ru/users.json
+++ b/src/locales/ru/users.json
@@ -45,6 +45,10 @@
"title": "Удаление пользователей",
"message": "Вы уверены, что хотите удалить {0} пользователей? Данное действие нельзя будет отменить"
}
+ },
+ "new-credentials": {
+ "title": "Данные для входа",
+ "message": "Адрес эл. почты: {0}\nПароль: {1}\n\nЗапомните или запишите эти данные, повторно узнать их будет невозможно"
}
}
}
\ No newline at end of file
diff --git a/src/pages/users/components/AddUser.vue b/src/pages/users/components/AddUser.vue
index 77c388c..0b267a8 100644
--- a/src/pages/users/components/AddUser.vue
+++ b/src/pages/users/components/AddUser.vue
@@ -3,6 +3,7 @@ import { useLocale, useTheme } from 'vuetify';
import { useUsersStore } from '@/stores/users';
import { validEmail, validName } from '@/mixins/validator';
import { useGroupsStore } from '@/stores/groups';
+import { useInfoStore } from '@/stores/info';
defineProps<{
modelValue: boolean;
@@ -10,6 +11,7 @@ defineProps<{
const $users = useUsersStore();
const $groups = useGroupsStore();
+const $info = useInfoStore();
const { t } = useLocale();
const theme = useTheme();
@@ -39,8 +41,17 @@ const validForm: ComputedRef = computed(
),
);
-const onConfirm = () => {
- $users.isEditing ? $users.editUser() : $users.createUser();
+const onConfirm = async () => {
+ const res = await ($users.isEditing
+ ? $users.editUser()
+ : $users.createUser());
+ if (res) {
+ console.log(res);
+ $info.setInfoMessage(
+ t('$vuetify.users.new-credentials.title'),
+ t('$vuetify.users.new-credentials.message', res.email, res.password),
+ );
+ }
};
diff --git a/src/pages/users/components/UsersTab.vue b/src/pages/users/components/UsersTab.vue
index 23d503c..8d802b2 100644
--- a/src/pages/users/components/UsersTab.vue
+++ b/src/pages/users/components/UsersTab.vue
@@ -8,6 +8,7 @@ import { User } from '@/types/user';
import Moment from '@/plugins/moment';
import { isDeleted, isModifiedDate } from '@/mixins/item-validation';
import { useConfirmationStore } from '@/stores/confirmation';
+import { useInfoStore } from '@/stores/info';
defineProps<{
tableHeight: number;
@@ -17,6 +18,7 @@ const { t } = useLocale();
const $users = useUsersStore();
const $confirmation = useConfirmationStore();
+const $info = useInfoStore();
const headers: ComputedRef = computed(() => [
{ title: t('$vuetify.users.heads.id'), key: 'ID' },
@@ -46,16 +48,19 @@ const actions = computed(() => [
title: t('$vuetify.actions.block'),
value: UserActions.BLOCK,
blocked: false,
+ onClick: (u: User) => $users.blockMany([u.ID]),
},
{
title: t('$vuetify.actions.reset-password'),
value: UserActions.RESET_PASSWORD,
blocked: false,
+ onClick: (u: User) => resetPassword(u),
},
{
title: t('$vuetify.actions.unblock'),
value: UserActions.UNBLOCK,
blocked: true,
+ onClick: (u: User) => $users.unblockMany([u.ID]),
},
]);
// Actions
@@ -71,6 +76,16 @@ const deleteUser = (u: User) => {
);
$confirmation.setOnConfirm(() => $users.deleteUser(u.ID));
};
+const resetPassword = async (u: User) => {
+ const res = await $users.resetPassword(u.ID);
+ if (res) {
+ console.log(res);
+ $info.setInfoMessage(
+ t('$vuetify.users.new-credentials.title'),
+ t('$vuetify.users.new-credentials.message', res.email, res.password),
+ );
+ }
+};
// Status
const calculateUserStatus = (user: User) => {
diff --git a/src/stores/groups/actions.ts b/src/stores/groups/actions.ts
index c989c84..283b4c1 100644
--- a/src/stores/groups/actions.ts
+++ b/src/stores/groups/actions.ts
@@ -21,6 +21,10 @@ const deleteGroup = async (ID: number) => {
setLoadingActions([0]);
};
+
+//
+// MARK: Delete many groups
+//
const deleteMany = async (IDs: number[]) => {
if (!IDs.length) {
return;
@@ -37,6 +41,10 @@ const deleteMany = async (IDs: number[]) => {
setLoadingActions([0]);
};
+
+//
+// MARK: Misc
+//
const setLoadingActions = (IDs: number[]) => (loadingActionsID.value = IDs);
export { loadingActionsID, deleteGroup, deleteMany };
diff --git a/src/stores/info.ts b/src/stores/info.ts
new file mode 100644
index 0000000..0abca3d
--- /dev/null
+++ b/src/stores/info.ts
@@ -0,0 +1,29 @@
+import { defineStore } from 'pinia';
+
+export const useInfoStore = defineStore('info', () => {
+ const show: Ref = ref(false);
+ const title: Ref = ref('');
+ const message: Ref = ref('');
+
+ const setInfoMessage = (t: string, m: string) => {
+ title.value = t;
+ message.value = m;
+ show.value = true;
+ };
+
+ const close = () => {
+ show.value = false;
+ setTimeout(() => {
+ title.value = '';
+ message.value = '';
+ }, 250);
+ };
+
+ return {
+ show,
+ title,
+ message,
+ setInfoMessage,
+ close,
+ };
+});
diff --git a/src/stores/users/actions.ts b/src/stores/users/actions.ts
index 31790dd..297f072 100644
--- a/src/stores/users/actions.ts
+++ b/src/stores/users/actions.ts
@@ -1,5 +1,6 @@
import api from '@/api';
import { getAll } from './search';
+import { NewCredentials } from '@/types/new-credentials';
//
// MARK: Delete user
@@ -21,6 +22,10 @@ const deleteUser = async (ID: number) => {
setLoadingActions([0]);
};
+
+//
+// MARK: Delete many users
+//
const deleteMany = async (IDs: number[]) => {
if (!IDs.length) {
return;
@@ -37,6 +42,10 @@ const deleteMany = async (IDs: number[]) => {
setLoadingActions([0]);
};
+
+//
+// MARK: Block many users
+//
const blockMany = async (IDs: number[]) => {
if (!IDs.length) {
return;
@@ -53,6 +62,10 @@ const blockMany = async (IDs: number[]) => {
setLoadingActions([0]);
};
+
+//
+// MARK: Unblock many users
+//
const unblockMany = async (IDs: number[]) => {
if (!IDs.length) {
return;
@@ -69,6 +82,41 @@ const unblockMany = async (IDs: number[]) => {
setLoadingActions([0]);
};
+
+//
+// MARK: Reset user password
+//
+const resetPassword = async (
+ ID: number,
+): Promise => {
+ if (!ID) {
+ return;
+ }
+
+ setLoadingActions([ID]);
+
+ const [res, e] = await api.users.resetPassword(ID);
+ if (e && typeof e === 'string') {
+ // TODO notify error
+ } else {
+ getAll();
+ }
+
+ setLoadingActions([0]);
+
+ return res?.data;
+};
+
+//
+// MARK: Misc
+//
const setLoadingActions = (IDs: number[]) => (loadingActionsID.value = IDs);
-export { loadingActionsID, deleteUser, deleteMany, blockMany, unblockMany };
+export {
+ loadingActionsID,
+ deleteUser,
+ deleteMany,
+ blockMany,
+ unblockMany,
+ resetPassword,
+};
diff --git a/src/stores/users/modal.ts b/src/stores/users/modal.ts
index a741f83..bcdcf44 100644
--- a/src/stores/users/modal.ts
+++ b/src/stores/users/modal.ts
@@ -2,6 +2,7 @@ import api from '@/api';
import { validEmail, validName } from '@/mixins/validator';
import { UserForm, User } from '@/types/user';
import { getAll } from './search';
+import { NewCredentials } from '@/types/new-credentials';
//
// MARK: User creation
@@ -15,7 +16,7 @@ const form: Ref> = ref({
realName: '',
groupID: -1,
});
-const createUser = async () => {
+const createUser = async (): Promise => {
if (
loadingCreate.value ||
!form.value.email ||
@@ -30,7 +31,7 @@ const createUser = async () => {
resetErrorCreate();
setLoadingCreate(true);
- const [_, e] = await api.users.create(
+ const [res, e] = await api.users.create(
form.value.email,
form.value.realName,
form.value.groupID,
@@ -43,6 +44,8 @@ const createUser = async () => {
}
setLoadingCreate(false);
+
+ return res?.data;
};
const resetErrorCreate = () => (errorCreate.value = '');
const openModal = () => {
diff --git a/src/types/new-credentials.d.ts b/src/types/new-credentials.d.ts
new file mode 100644
index 0000000..e3e0b0a
--- /dev/null
+++ b/src/types/new-credentials.d.ts
@@ -0,0 +1,4 @@
+export interface NewCredentials {
+ email: string;
+ password: string;
+}