Skip to content

Commit

Permalink
add member merge frontend modal
Browse files Browse the repository at this point in the history
  • Loading branch information
Onatcer authored and korridor committed Mar 6, 2025
1 parent ab263e7 commit e5ec11a
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 16 deletions.
2 changes: 2 additions & 0 deletions app/Http/Controllers/Api/V1/MemberController.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ public function makePlaceholder(Organization $organization, Member $member, Memb
* @throws AuthorizationException
* @throws OnlyPlaceholdersCanBeMergedIntoAnotherMember
* @throws \Throwable
*
* @operationId mergeMember
*/
public function mergeInto(Organization $organization, Member $member, MemberMergeIntoRequest $request): JsonResponse
{
Expand Down
2 changes: 2 additions & 0 deletions app/Providers/JetstreamServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ protected function configurePermissions(): void
'members:invite-placeholder',
'members:change-ownership',
'members:make-placeholder',
'members:merge-into',
'members:update',
'members:delete',
'billing',
Expand Down Expand Up @@ -174,6 +175,7 @@ protected function configurePermissions(): void
'members:view',
'members:update',
'members:invite-placeholder',
'members:merge-into',
'reports:view',
'reports:create',
'reports:update',
Expand Down
109 changes: 109 additions & 0 deletions resources/js/Components/Common/Member/MemberMergeModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<script setup lang="ts">
import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue';
import DialogModal from '@/packages/ui/src/DialogModal.vue';
import { ref } from 'vue';
import {api, type Member} from '@/packages/api/src';
import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue';
import MemberCombobox from "@/Components/Common/Member/MemberCombobox.vue";
import {UserIcon, ArrowRightIcon} from "@heroicons/vue/24/solid";
import {Badge} from "@/packages/ui/src";
import { useMutation } from '@tanstack/vue-query';
import {getCurrentOrganizationId} from "@/utils/useUser";
import {useNotificationsStore} from "@/utils/notification";
const { handleApiRequestNotifications, addNotification } = useNotificationsStore();
const show = defineModel('show', { default: false });

Check warning on line 15 in resources/js/Components/Common/Member/MemberMergeModal.vue

View workflow job for this annotation

GitHub Actions / build

Prop "show" should define at least its type
const saving = ref(false);
const props = defineProps<{
member: Member;
}>();
const newMember = ref<string>('');
const mergeMember = useMutation({
mutationFn: async (newMemberId: string) => {
const organizationId = getCurrentOrganizationId();
if (organizationId === null) {
throw new Error('No current organization id - create report');
}
return await api.mergeMember({
memberId: newMemberId,
}, {
params: {
organization: organizationId,
member: props.member.id
},
});
},
});
async function submit() {
const newMemberId = newMember.value;
if(newMemberId !== ''){
saving.value = true;
await handleApiRequestNotifications(
() =>
mergeMember.mutateAsync(newMemberId),
'Members successfully merged!',
'There was an error merging the members.',
() => {
show.value = false;
}
);
}
else{
addNotification(
'error',
'Please select a member to merge into.',
);
}
}
</script>

<template>
<DialogModal closeable :show="show" @close="show = false">
<template #title>
<div class="flex space-x-2">
<span> Merge Member </span>
</div>
</template>

<template #content>
<p>Merging the user <strong>{{ member.name }} </strong> into another one will transfer all time entries to the new user. <strong>This cannot be reverted!</strong></p>
<div class="py-5 flex flex-col md:flex-row gap-6 items-center">
<div class="flex-1">
<Badge class="flex w-full text-base text-left space-x-3 px-3 text-text-secondary font-normal cursor py-1.5">
<UserIcon class="relative z-10 w-4 text-muted"></UserIcon>
<div class="flex-1 font-medium truncate">
{{ member.name }}
</div>
</Badge>
</div>
<div>
<ArrowRightIcon class="relative z-10 w-4 text-muted"></ArrowRightIcon>
</div>
<div class="flex-1">
<MemberCombobox
v-model="newMember"
></MemberCombobox>
</div>
</div>
</template>
<template #footer>
<SecondaryButton @click="show = false"> Cancel</SecondaryButton>

<PrimaryButton
class="ms-3"
:class="{ 'opacity-25': saving }"
:disabled="saving"
@click="submit()">
Merge Member
</PrimaryButton>
</template>
</DialogModal>
</template>

<style scoped></style>
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<script setup lang="ts">
import { TrashIcon, PencilSquareIcon } from '@heroicons/vue/20/solid';
import { TrashIcon, PencilSquareIcon, ArrowDownOnSquareStackIcon } from '@heroicons/vue/20/solid';
import type { Member } from '@/packages/api/src';
import { canDeleteMembers, canUpdateMembers } from '@/utils/permissions';
import {canDeleteMembers, canMergeMembers, canUpdateMembers} from '@/utils/permissions';
import MoreOptionsDropdown from '@/packages/ui/src/MoreOptionsDropdown.vue';
const emit = defineEmits<{
delete: [];
edit: [];
merge: [];
}>();
const props = defineProps<{
member: Member;
}>();
</script>

<template>
Expand All @@ -36,6 +38,15 @@ const props = defineProps<{
<TrashIcon class="w-5 text-icon-active"></TrashIcon>
<span>Delete</span>
</button>
<button
v-if="props.member.role === 'placeholder' && canMergeMembers()"
:aria-label="'Merge Member ' + props.member.name"
data-testid="member_merge"
class="flex items-center space-x-3 w-full px-3 py-2.5 text-start text-sm font-medium leading-5 text-white hover:bg-card-background-active focus:outline-none focus:bg-card-background-active transition duration-150 ease-in-out"
@click="emit('merge')">
<ArrowDownOnSquareStackIcon class="w-5 text-icon-active"></ArrowDownOnSquareStackIcon>
<span>Merge</span>
</button>
</div>
</MoreOptionsDropdown>
</template>
Expand Down
17 changes: 14 additions & 3 deletions resources/js/Components/Common/Member/MemberTableRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import { getCurrentOrganizationId } from '@/utils/useUser';
import { useNotificationsStore } from '@/utils/notification';
import { canInvitePlaceholderMembers } from '@/utils/permissions';
import { useMembersStore } from '@/utils/useMembers';
import { ref } from 'vue';
import {computed, ref} from 'vue';
import MemberEditModal from '@/Components/Common/Member/MemberEditModal.vue';
import { getOrganizationCurrencyString } from '@/utils/money';
import { formatCents } from '@/packages/ui/src/utils/money';
import MemberMergeModal from "@/Components/Common/Member/MemberMergeModal.vue";
const props = defineProps<{
member: Member;
}>();
const showEditMemberModal = ref(false);
const showMergeMemberModal = ref(false);
function removeMember() {
useMembersStore().removeMember(props.member.id);
Expand All @@ -45,6 +47,11 @@ async function invitePlaceholder(id: string) {
);
}
}
const userHasValidMailAddress = computed(() => {
return !props.member.email.endsWith('@solidtime-import.test');
})
</script>

<template>
Expand Down Expand Up @@ -87,7 +94,8 @@ async function invitePlaceholder(id: string) {
<SecondaryButton
v-if="
member.is_placeholder === true &&
canInvitePlaceholderMembers()
canInvitePlaceholderMembers() &&
userHasValidMailAddress
"
size="small"
@click="invitePlaceholder(member.id)"
Expand All @@ -96,11 +104,14 @@ async function invitePlaceholder(id: string) {
<MemberMoreOptionsDropdown
:member="member"
@edit="showEditMemberModal = true"
@delete="removeMember"></MemberMoreOptionsDropdown>
@delete="removeMember"
@merge="showMergeMemberModal = true"
></MemberMoreOptionsDropdown>
</div>
<MemberEditModal
v-model:show="showEditMemberModal"
:member="member"></MemberEditModal>
<MemberMergeModal v-model:show="showMergeMemberModal" :member="member"></MemberMergeModal>
</TableRow>
</template>

Expand Down
5 changes: 2 additions & 3 deletions resources/js/Pages/Profile/Partials/ApiTokensForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ async function createApiToken(){
(response) => {
createApiTokenForm.name = '';
displayingToken.value = true;
// @ts-expect-error temporary fix until openapi docs type is fixed
newToken.value = response.data.access_token;
}
);
Expand Down Expand Up @@ -117,7 +116,7 @@ const deleteApiTokenMutation = useMutation({
mutationFn: async (apiTokenId: string) => {
return await api.deleteApiToken(undefined, {
params: {
apiTokenId: apiTokenId,
apiToken: apiTokenId,
},
});
},
Expand All @@ -130,7 +129,7 @@ const revokeApiTokenMutation = useMutation({
mutationFn: async (apiTokenId: string) => {
return await api.revokeApiToken(undefined, {
params: {
apiTokenId: apiTokenId,
apiToken: apiTokenId,
},
});
},
Expand Down
Loading

0 comments on commit e5ec11a

Please sign in to comment.