completed reset password and new credentials features

This commit is contained in:
Vitaliy Pavlov 2024-07-29 10:34:41 +07:00
parent 3fcd1a5a38
commit 88820ce430
15 changed files with 161 additions and 26 deletions

View File

@ -3,7 +3,11 @@
<v-main>
<router-view />
<!-- Confirmation modal -->
<confirm-modal />
<!-- Info modal -->
<info-modal />
</v-main>
</v-app>
</template>

View File

@ -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,
},

View File

@ -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<NewCredentials>?, 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];
}
},
};

View File

@ -1,23 +1,23 @@
<script setup lang="ts">
import { useConfirmationStore } from '@/stores/confirmation';
import { useLocale } from 'vuetify';
import { useInfoStore } from '@/stores/info';
const { t } = useLocale();
const $confirmation = useConfirmationStore();
const $info = useInfoStore();
</script>
<template>
<v-dialog persistent :model-value="$confirmation.show" width="450">
<v-dialog persistent :model-value="$info.show" width="450">
<v-card>
<template v-slot:title>
<v-card-title class="p-0 w-full flex items-center">
<span class="mso mr-1 text-2xl">warning</span>
<span>{{ $confirmation.title }}</span>
<span>{{ $info.title }}</span>
</v-card-title>
</template>
<v-card-text>{{ $confirmation.message }}</v-card-text>
<v-card-text class="whitespace-pre-line">{{ $info.message }}</v-card-text>
<!-- Actions -->
<v-card-actions class="flex justify-end p-5 gap-1">
@ -25,16 +25,8 @@ const $confirmation = useConfirmationStore();
<v-btn
variant="text"
class="px-4"
:text="t('$vuetify.actions.cancel')"
@click="$confirmation.doDecline()"
></v-btn>
<!-- Confirm -->
<v-btn
variant="flat"
color="primary"
class="px-4"
:text="t('$vuetify.actions.confirm')"
@click="$confirmation.doConfirm()"
:text="t('$vuetify.actions.hide')"
@click="$info.close()"
></v-btn>
</v-card-actions>
</v-card>

View File

@ -10,6 +10,7 @@
"unblock": "Unblock",
"apply": "Apply",
"confirm": "Confirm",
"cancel": "Cancel"
"cancel": "Cancel",
"hide": "Hide"
}
}

View File

@ -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"
}
}
}

View File

@ -10,6 +10,7 @@
"unblock": "Разблокировать",
"apply": "Применить",
"confirm": "Подтвердить",
"cancel": "Отменить"
"cancel": "Отменить",
"hide": "Скрыть"
}
}

View File

@ -45,6 +45,10 @@
"title": "Удаление пользователей",
"message": "Вы уверены, что хотите удалить {0} пользователей? Данное действие нельзя будет отменить"
}
},
"new-credentials": {
"title": "Данные для входа",
"message": "Адрес эл. почты: {0}\nПароль: {1}\n\nЗапомните или запишите эти данные, повторно узнать их будет невозможно"
}
}
}

View File

@ -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<boolean> = 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),
);
}
};
</script>

View File

@ -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<any[]> = 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) => {

View File

@ -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 };

29
src/stores/info.ts Normal file
View File

@ -0,0 +1,29 @@
import { defineStore } from 'pinia';
export const useInfoStore = defineStore('info', () => {
const show: Ref<boolean> = ref(false);
const title: Ref<string> = ref('');
const message: Ref<string> = 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,
};
});

View File

@ -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<NewCredentials | undefined> => {
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,
};

View File

@ -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<Partial<UserForm>> = ref({
realName: '',
groupID: -1,
});
const createUser = async () => {
const createUser = async (): Promise<NewCredentials | undefined> => {
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 = () => {

4
src/types/new-credentials.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
export interface NewCredentials {
email: string;
password: string;
}