Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/vue/src/locales/en/account.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
"role": "Please select a role"
}
},
"messages": {
"created": "Invitation sent to {email}.",
"deleted": "Invitation deleted for {email}.",
"resend": "Invitation resent to {email}.",
"revoked": "Invitation revoked for {email}."
},
"modal": {
"title": "Invite an user"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/vue/src/locales/fr/account.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
"role": "Veuillez sélectionner un rôle"
}
},
"messages": {
"created": "Invitation envoyée à {email}.",
"deleted": "Invitation supprimée pour {email}.",
"resend": "Invitation renvoyée à {email}.",
"revoked": "Invitation révoquée pour {email}."
},
"modal": {
"title": "Inviter un utilisateur"
},
Expand Down
79 changes: 55 additions & 24 deletions packages/vue/src/views/Invitations/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,31 @@
v-if="showInvitationModal"
:show="showInvitationModal"
@close="showInvitationModal = false"
@created="handleInvitationCreated"
@invitation:created="handleInvitationCreated"
/>
</template>

<script setup lang="ts">
// Imports
import { useConfig } from "@dzangolab/vue3-config";
import { useI18n } from "@dzangolab/vue3-i18n";
import { Table } from "@dzangolab/vue3-tanstack-table";
import { BadgeComponent, ButtonElement } from "@dzangolab/vue3-ui";
import { ref, onMounted, h } from "vue";
import { ref, onMounted, h, inject } from "vue";
import { useRoute } from "vue-router";

import InvitationModal from "./_components/InvitationModal.vue";
import { useTranslations } from "../../index";
import useInvitationStore from "../../stores/accountInvitations";

import type { AccountInvitation } from "../../types/accountInvitation";
import type { SaasEventHandlers, EventMessage } from "../../types/plugin";
import type { AppConfig } from "@dzangolab/vue3-config";
import type { TableColumnDefinition } from "@dzangolab/vue3-tanstack-table";

defineProps({
isLoading: Boolean,
});

const emit = defineEmits([
"invitation:deleted",
"invitation:resent",
"invitation:revoked",
]);

const config = useConfig() as AppConfig;
const messages = useTranslations();
const { t } = useI18n({ messages });
Expand All @@ -61,7 +55,14 @@ const { deleteInvitation, getInvitations, resendInvitation, revokeInvitation } =
invitationStore;
const route = useRoute();

const eventHandlers = inject<SaasEventHandlers>(
Symbol.for("saas.eventHandlers"),
{ notification: undefined }
);

const accountId = route.params.id as string;
const invitations = ref<AccountInvitation[]>([]);
const showInvitationModal = ref(false);

const actionMenuData = [
{
Expand Down Expand Up @@ -100,16 +101,15 @@ const columns: TableColumnDefinition<AccountInvitation>[] = [
},
{
accessorKey: "role",
header: t("account.invitations.table.columns.role"),
cell: ({ row: { original } }) =>
h(BadgeComponent, {
label: original.role,
severity: original.role === "admin" ? "primary" : "success",
}),
header: t("account.invitations.table.columns.role"),
},
{
accessorKey: "status",
header: t("account.invitations.table.columns.status"),
cell: ({ row: { original } }) =>
h(BadgeComponent, {
label: original.acceptedAt
Expand All @@ -123,24 +123,22 @@ const columns: TableColumnDefinition<AccountInvitation>[] = [
? "danger"
: "warning",
}),
header: t("account.invitations.table.columns.status"),
},
{
accessorKey: "expiresAt",
header: t("account.invitations.table.columns.expiresAt"),
cell: ({ row: { original } }) =>
new Date(original.expiresAt).toLocaleDateString(),
header: t("account.invitations.table.columns.expiresAt"),
},
{
accessorKey: "createdAt",
header: t("account.invitations.table.columns.createdAt"),
cell: ({ row: { original } }) =>
new Date(original.createdAt).toLocaleDateString(),
header: t("account.invitations.table.columns.createdAt"),
},
];

const invitations = ref<AccountInvitation[]>([]);
const showInvitationModal = ref(false);

onMounted(async () => {
await fetchInvitations();
});
Expand All @@ -158,22 +156,47 @@ async function handleDelete(invitation: AccountInvitation) {
try {
await deleteInvitation(accountId, invitation.id, config.apiBaseUrl);
await fetchInvitations();
emit("invitation:deleted", invitation);

const message: EventMessage = {
message: t("account.invitations.messages.deleted", {
email: invitation.email,
}),
type: "success",
};

eventHandlers?.notification?.(message);
} catch (error) {
console.error("Failed to delete invitation:", error);
}
}

const handleInvitationCreated = async () => {
async function handleInvitationCreated(invitation: AccountInvitation) {
showInvitationModal.value = false;
await fetchInvitations();
};

const message: EventMessage = {
message: t("account.invitations.messages.created", {
email: invitation.email,
}),
type: "success",
};

eventHandlers?.notification?.(message);
}

async function handleResend(invitation: AccountInvitation) {
try {
await resendInvitation(accountId, invitation.id, config.apiBaseUrl);
await fetchInvitations();
emit("invitation:resent", invitation);

const message: EventMessage = {
message: t("account.invitations.messages.resend", {
email: invitation.email,
}),
type: "success",
};

eventHandlers?.notification?.(message);
} catch (error) {
console.error("Failed to resend invitation:", error);
}
Expand All @@ -183,23 +206,31 @@ async function handleRevoke(invitation: AccountInvitation) {
try {
await revokeInvitation(accountId, invitation.id, config.apiBaseUrl);
await fetchInvitations();
emit("invitation:revoked", invitation);

const message: EventMessage = {
message: t("account.invitations.messages.revoked", {
email: invitation.email,
}),
type: "success",
};

eventHandlers?.notification?.(message);
} catch (error) {
console.error("Failed to revoke invitation:", error);
}
}

function onActionSelect(rowData: { action: string; data: AccountInvitation }) {
switch (rowData.action) {
case "deleteInvitation":
handleDelete(rowData.data);
break;
case "resendInvitation":
handleResend(rowData.data);
break;
case "revokeInvitation":
handleRevoke(rowData.data);
break;
case "deleteInvitation":
handleDelete(rowData.data);
break;
}
}
</script>
20 changes: 2 additions & 18 deletions packages/vue/src/views/Invitations/_components/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@ import {

import type { AccountInvitationCreateInput } from "../../../types/accountInvitation";
import type { SaasConfig } from "../../../types/config";
import type { SaasEventHandlers, EventMessage } from "../../../types/plugin";

defineProps({
loading: Boolean,
});

defineEmits(["cancel"]);
const emit = defineEmits(["cancel", "invitation:created"]);

const config = useConfig();
const invitationStore = useInvitationStore();
Expand All @@ -82,25 +81,10 @@ const roles = computed(() => {
return saasConfig?.saasAccountRoles || SAAS_ACCOUNT_ROLES_DEFAULT;
});

const eventHandlers = inject<SaasEventHandlers>(
Symbol.for("saas.eventHandlers"),
{ notification: undefined }
);

async function onSubmit() {
try {
await addInvitation(accountId, formData.value, config.apiBaseUrl).then(
(response) => {
const message: EventMessage = {
type: "success",
message: t("account.invitations.messages.created"),
details: {
invitation: response,
},
};

eventHandlers?.notification?.(message);
}
(response) => emit("invitation:created", response)
);
} catch (error) {
console.error("Form submission error:", error);
Expand Down
14 changes: 12 additions & 2 deletions packages/vue/src/views/Invitations/_components/InvitationModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
:title="t('account.invitations.modal.title')"
@on:close="$emit('close')"
>
<InvitationForm :loading="loading" @cancel="$emit('close')" />
<InvitationForm
:loading="loading"
@cancel="$emit('close')"
@invitation:created="onInvitationCreated"
/>
</Modal>
</template>

Expand All @@ -15,15 +19,21 @@ import { Modal } from "@dzangolab/vue3-ui";
import InvitationForm from "./Form.vue";
import { useTranslations } from "../../../index";

import type { AccountInvitation } from "../../../types/accountInvitation";

defineProps({
show: Boolean,
loading: Boolean,
});

defineEmits(["close"]);
const emit = defineEmits(["close", "invitation:created"]);

const messages = useTranslations();
const { t } = useI18n({ messages, locale: "en" });

function onInvitationCreated(invitation: AccountInvitation) {
emit("invitation:created", invitation);
}
</script>

<style lang="css">
Expand Down