diff --git a/frontend/.env.example b/frontend/.env.example index 720ea0f59..da8badd5a 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -5,3 +5,4 @@ VITE_SIGNOUT_REDIRECT_URI=your-signout-redirect-uri VITE_API_BASE_URL=your-api-base-url VITE_LOGTO_ENDPOINT=your-logto-endpoint VITE_LOGTO_APP_ID=your-logto-app-id +VITE_SHOP_BASE_URL=your-shop-base-url diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8b6cc611a..61d0d94cf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1642,7 +1642,7 @@ }, "node_modules/@nethesis/nethesis-light-svg-icons": { "version": "6.2.1", - "resolved": "git+ssh://git@github.com/nethesis/Font-Awesome.git#42a41a56dbee88805a15d3433672bdd952a1f505", + "resolved": "git+ssh://git@github.com/nethesis/Font-Awesome.git#b701b3a6018f3327e0cedcda9650a621eeb2cbb2", "hasInstallScript": true, "license": "UNLICENSED", "dependencies": { diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css index 3235614a1..76eb9be51 100644 --- a/frontend/src/assets/main.css +++ b/frontend/src/assets/main.css @@ -43,6 +43,13 @@ --breakpoint-6xl: 192rem; --breakpoint-7xl: 224rem; + /* motion durations, scaled by how far the animated element travels: state + feedback and small components are quick, while large panels get more time + to enter than to leave */ + --duration-small: 200ms; + --duration-large-enter: 300ms; + --duration-large-leave: 200ms; + /* animations */ --animate-spin-relaxed: spin 1.5s linear infinite; --animate-fade-in: fade-in 0.3s ease-in; @@ -83,7 +90,7 @@ /* fade transition */ .fade-enter-active, .fade-leave-active { - transition: all 0.3s ease; + transition: all var(--duration-small) ease; } .fade-enter-from, @@ -94,7 +101,7 @@ /* slide-down transition */ .slide-down-enter-active, .slide-down-leave-active { - transition: all 0.3s ease; + transition: all var(--duration-small) ease; } .slide-down-enter-from, diff --git a/frontend/src/components/account/ChangePasswordDrawer.vue b/frontend/src/components/account/ChangePasswordDrawer.vue index 41b88ab9c..4c575909a 100644 --- a/frontend/src/components/account/ChangePasswordDrawer.vue +++ b/frontend/src/components/account/ChangePasswordDrawer.vue @@ -225,7 +225,7 @@ async function validateAndChangePassword() {
diff --git a/frontend/src/components/account/ChangePictureDrawer.vue b/frontend/src/components/account/ChangePictureDrawer.vue index 549b518b4..f3563a1ee 100644 --- a/frontend/src/components/account/ChangePictureDrawer.vue +++ b/frontend/src/components/account/ChangePictureDrawer.vue @@ -130,7 +130,7 @@ function savePicture() { diff --git a/frontend/src/components/account/apiKeys/CreateApiKeyDrawer.vue b/frontend/src/components/account/apiKeys/CreateApiKeyDrawer.vue index 743af4357..315e1d1fd 100644 --- a/frontend/src/components/account/apiKeys/CreateApiKeyDrawer.vue +++ b/frontend/src/components/account/apiKeys/CreateApiKeyDrawer.vue @@ -197,7 +197,7 @@ function copyKeyAndCloseDrawer() { diff --git a/frontend/src/components/addons/AddonInUseModal.vue b/frontend/src/components/addons/AddonInUseModal.vue new file mode 100644 index 000000000..338fa30a0 --- /dev/null +++ b/frontend/src/components/addons/AddonInUseModal.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/frontend/src/components/addons/AddonLogo.vue b/frontend/src/components/addons/AddonLogo.vue new file mode 100644 index 000000000..3e930f793 --- /dev/null +++ b/frontend/src/components/addons/AddonLogo.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/frontend/src/components/addons/AddonsCatalogPanel.vue b/frontend/src/components/addons/AddonsCatalogPanel.vue new file mode 100644 index 000000000..792333c15 --- /dev/null +++ b/frontend/src/components/addons/AddonsCatalogPanel.vue @@ -0,0 +1,486 @@ + + + + + diff --git a/frontend/src/components/addons/AddonsReportPanel.vue b/frontend/src/components/addons/AddonsReportPanel.vue new file mode 100644 index 000000000..a3d6a14e7 --- /dev/null +++ b/frontend/src/components/addons/AddonsReportPanel.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/frontend/src/components/addons/CreateOrEditAddonDrawer.vue b/frontend/src/components/addons/CreateOrEditAddonDrawer.vue new file mode 100644 index 000000000..5a1284248 --- /dev/null +++ b/frontend/src/components/addons/CreateOrEditAddonDrawer.vue @@ -0,0 +1,419 @@ + + + + + diff --git a/frontend/src/components/addons/DeleteAddonModal.vue b/frontend/src/components/addons/DeleteAddonModal.vue new file mode 100644 index 000000000..93dc81cb0 --- /dev/null +++ b/frontend/src/components/addons/DeleteAddonModal.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/frontend/src/components/addons/ProductTypeSelector.vue b/frontend/src/components/addons/ProductTypeSelector.vue new file mode 100644 index 000000000..21679642e --- /dev/null +++ b/frontend/src/components/addons/ProductTypeSelector.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/frontend/src/components/addons/report/AddonActivationsCard.vue b/frontend/src/components/addons/report/AddonActivationsCard.vue new file mode 100644 index 000000000..c777f9bd3 --- /dev/null +++ b/frontend/src/components/addons/report/AddonActivationsCard.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/frontend/src/components/addons/report/AddonRenewalsCard.vue b/frontend/src/components/addons/report/AddonRenewalsCard.vue new file mode 100644 index 000000000..562db8efc --- /dev/null +++ b/frontend/src/components/addons/report/AddonRenewalsCard.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/frontend/src/components/addons/report/AddonsByAddonCard.vue b/frontend/src/components/addons/report/AddonsByAddonCard.vue new file mode 100644 index 000000000..996979b23 --- /dev/null +++ b/frontend/src/components/addons/report/AddonsByAddonCard.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/frontend/src/components/addons/report/AddonsByOrganizationCard.vue b/frontend/src/components/addons/report/AddonsByOrganizationCard.vue new file mode 100644 index 000000000..9c36681cc --- /dev/null +++ b/frontend/src/components/addons/report/AddonsByOrganizationCard.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/frontend/src/components/addons/report/AddonsByTierCard.vue b/frontend/src/components/addons/report/AddonsByTierCard.vue new file mode 100644 index 000000000..fa854b726 --- /dev/null +++ b/frontend/src/components/addons/report/AddonsByTierCard.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/frontend/src/components/addons/report/ReportCard.vue b/frontend/src/components/addons/report/ReportCard.vue new file mode 100644 index 000000000..2314aae39 --- /dev/null +++ b/frontend/src/components/addons/report/ReportCard.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/frontend/src/components/alerts/AddAlertNoteDrawer.vue b/frontend/src/components/alerts/AddAlertNoteDrawer.vue index dd0a06652..cbc5d6175 100644 --- a/frontend/src/components/alerts/AddAlertNoteDrawer.vue +++ b/frontend/src/components/alerts/AddAlertNoteDrawer.vue @@ -110,7 +110,7 @@ function handleSubmit() { diff --git a/frontend/src/components/alerts/AlertDetailsDrawer.vue b/frontend/src/components/alerts/AlertDetailsDrawer.vue index 3fe62f68d..de6a9f26e 100644 --- a/frontend/src/components/alerts/AlertDetailsDrawer.vue +++ b/frontend/src/components/alerts/AlertDetailsDrawer.vue @@ -81,7 +81,7 @@ function closeDrawer() {
diff --git a/frontend/src/components/alerts/AssignAlertDrawer.vue b/frontend/src/components/alerts/AssignAlertDrawer.vue index 604e3198c..68573d199 100644 --- a/frontend/src/components/alerts/AssignAlertDrawer.vue +++ b/frontend/src/components/alerts/AssignAlertDrawer.vue @@ -89,7 +89,7 @@ function handleSubmit() { diff --git a/frontend/src/components/alerts/EditEmailNotificationsDrawer.vue b/frontend/src/components/alerts/EditEmailNotificationsDrawer.vue index e0ed70893..af793f878 100644 --- a/frontend/src/components/alerts/EditEmailNotificationsDrawer.vue +++ b/frontend/src/components/alerts/EditEmailNotificationsDrawer.vue @@ -225,7 +225,7 @@ function closeDrawer() {
@@ -264,7 +264,7 @@ function closeDrawer() {
@@ -371,8 +371,8 @@ function closeDrawer() { .accordion-leave-active { overflow: hidden; transition: - max-height 0.25s ease, - opacity 0.2s ease; + max-height var(--duration-small) ease, + opacity var(--duration-small) ease; } .accordion-enter-from, diff --git a/frontend/src/components/alerts/EditTelegramNotificationsDrawer.vue b/frontend/src/components/alerts/EditTelegramNotificationsDrawer.vue index 37a600a03..6a9c79de2 100644 --- a/frontend/src/components/alerts/EditTelegramNotificationsDrawer.vue +++ b/frontend/src/components/alerts/EditTelegramNotificationsDrawer.vue @@ -233,7 +233,7 @@ function closeDrawer() {
@@ -272,7 +272,7 @@ function closeDrawer() {
@@ -380,8 +380,8 @@ function closeDrawer() { .accordion-leave-active { overflow: hidden; transition: - max-height 0.25s ease, - opacity 0.2s ease; + max-height var(--duration-small) ease, + opacity var(--duration-small) ease; } .accordion-enter-from, diff --git a/frontend/src/components/alerts/EditWebhookNotificationsDrawer.vue b/frontend/src/components/alerts/EditWebhookNotificationsDrawer.vue index 5c7149671..7e6bc5efd 100644 --- a/frontend/src/components/alerts/EditWebhookNotificationsDrawer.vue +++ b/frontend/src/components/alerts/EditWebhookNotificationsDrawer.vue @@ -215,7 +215,7 @@ function closeDrawer() {
@@ -254,7 +254,7 @@ function closeDrawer() {
@@ -349,8 +349,8 @@ function closeDrawer() { .accordion-leave-active { overflow: hidden; transition: - max-height 0.25s ease, - opacity 0.2s ease; + max-height var(--duration-small) ease, + opacity var(--duration-small) ease; } .accordion-enter-from, diff --git a/frontend/src/components/alerts/MuteAlertDrawer.vue b/frontend/src/components/alerts/MuteAlertDrawer.vue index 4682ea8d3..791f296df 100644 --- a/frontend/src/components/alerts/MuteAlertDrawer.vue +++ b/frontend/src/components/alerts/MuteAlertDrawer.vue @@ -152,7 +152,7 @@ onBeforeUnmount(() => { diff --git a/frontend/src/components/applications/AssignOrganizationDrawer.vue b/frontend/src/components/applications/AssignOrganizationDrawer.vue index aff98fb24..5e64cdbcd 100644 --- a/frontend/src/components/applications/AssignOrganizationDrawer.vue +++ b/frontend/src/components/applications/AssignOrganizationDrawer.vue @@ -111,7 +111,7 @@ async function saveApplication() { diff --git a/frontend/src/components/applications/SetNotesDrawer.vue b/frontend/src/components/applications/SetNotesDrawer.vue index 34fa1e726..62bf7eb52 100644 --- a/frontend/src/components/applications/SetNotesDrawer.vue +++ b/frontend/src/components/applications/SetNotesDrawer.vue @@ -109,7 +109,7 @@ async function saveApplication() { :title=" currentApplication?.notes ? $t('applications.edit_notes') : $t('applications.add_notes') " - :close-aria-label="$t('common.shell.close_side_drawer')" + :close-aria-label="$t('shell.close_side_drawer')" @show="onShow" @close="closeDrawer" > diff --git a/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue b/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue index 8ae717d8a..c49363762 100644 --- a/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue +++ b/frontend/src/components/customers/CreateOrEditCustomerDrawer.vue @@ -338,7 +338,7 @@ async function saveCustomer() { diff --git a/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue b/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue index 2697976bd..4c4d67abf 100644 --- a/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue +++ b/frontend/src/components/distributors/CreateOrEditDistributorDrawer.vue @@ -320,7 +320,7 @@ async function saveDistributor() { ? $t('distributors.edit_distributor') : $t('distributors.create_distributor') " - :close-aria-label="$t('common.shell.close_side_drawer')" + :close-aria-label="$t('shell.close_side_drawer')" @show="onShow" @close="closeDrawer" > diff --git a/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue b/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue index cb6eb49b3..98c6fe0b6 100644 --- a/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue +++ b/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue @@ -338,7 +338,7 @@ async function saveReseller() { diff --git a/frontend/src/components/shell/AppShell.vue b/frontend/src/components/shell/AppShell.vue index 4f66dabbf..55e6cd430 100644 --- a/frontend/src/components/shell/AppShell.vue +++ b/frontend/src/components/shell/AppShell.vue @@ -38,10 +38,10 @@ const logoFile = computed(() => { @@ -51,20 +51,20 @@ const logoFile = computed(() => {
diff --git a/frontend/src/components/shell/SideMenu.vue b/frontend/src/components/shell/SideMenu.vue index ccf71cacc..b72f47213 100644 --- a/frontend/src/components/shell/SideMenu.vue +++ b/frontend/src/components/shell/SideMenu.vue @@ -22,6 +22,7 @@ import { faUserGroup as fasUserGroup, faServer as fasServer, faTriangleExclamation, + faPuzzlePiece, } from '@fortawesome/free-solid-svg-icons' import { faGridOne as fasGridOne } from '@nethesis/nethesis-solid-svg-icons' import { @@ -33,6 +34,7 @@ import { faServer as falServer, faGrid2 as falGrid2, faTriangleExclamation as falTriangleExclamation, + faPuzzlePiece as falPuzzlePiece, } from '@nethesis/nethesis-light-svg-icons' import { canReadApplications, @@ -41,6 +43,7 @@ import { canReadResellers, canReadSystems, canReadUsers, + isAddonAdmin, } from '@/lib/permissions' import { isUserCustomer } from '@/lib/organizations/organizations' @@ -67,7 +70,7 @@ const menuExpanded: Ref> = ref({ resellers: false, }) -const systemsManagementRoutes = ['alerts', 'systems', 'applications'] +const systemsManagementRoutes = ['alerts', 'systems', 'applications', 'addons'] const companiesAndUsersRoutes = ['distributors', 'resellers', 'customers', 'users'] const navigation = computed(() => { @@ -102,6 +105,18 @@ const navigation = computed(() => { }) } + // Managing the add-on catalog is a licensing back-office duty, so the entry + // answers to owner-level authority rather than to read:entitlements, which + // every company holds to see its own add-ons. + if (isAddonAdmin()) { + menuItems.push({ + name: 'addons.title', + to: 'addons', + solidIcon: faPuzzlePiece, + lightIcon: falPuzzlePiece, + }) + } + if (canReadDistributors()) { menuItems.push({ name: 'distributors.title', @@ -214,7 +229,7 @@ function loadMenuItemsExpanded() { isCurrentRoute(dashboardItem.to) ? 'border-primary-700 dark:border-primary-500 bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-50' : 'text-tertiary-neutral dark:text-tertiary-neutral border-transparent hover:text-gray-900 dark:hover:text-gray-50', - 'group flex cursor-pointer items-center gap-x-3 rounded-md border-l-4 px-3 py-2 text-sm leading-6 font-semibold hover:bg-gray-100 dark:hover:bg-gray-800', + 'group flex cursor-pointer items-center gap-x-3 rounded-md border-l-4 px-3 py-2 text-sm leading-6 font-semibold transition-colors duration-(--duration-small) hover:bg-gray-100 dark:hover:bg-gray-800', ]" > @@ -289,7 +304,7 @@ function loadMenuItemsExpanded() { isCurrentRoute(child.to) ? 'border-primary-700 dark:border-primary-500 border-l-4 bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-50' : 'text-tertiary-neutral dark:text-tertiary-neutral hover:text-gray-900 dark:hover:text-gray-50', - 'group flex cursor-pointer items-center gap-x-3 rounded-md px-3 py-1 text-sm leading-6 font-semibold hover:bg-gray-100 dark:hover:bg-gray-800', + 'group flex cursor-pointer items-center gap-x-3 rounded-md px-3 py-1 text-sm leading-6 font-semibold transition-colors duration-(--duration-small) hover:bg-gray-100 dark:hover:bg-gray-800', ]" > {{ t(child.name) }} diff --git a/frontend/src/components/shell/TopBar.vue b/frontend/src/components/shell/TopBar.vue index 2cc0faeb3..80b66eb15 100644 --- a/frontend/src/components/shell/TopBar.vue +++ b/frontend/src/components/shell/TopBar.vue @@ -35,8 +35,8 @@ const loginStore = useLoginStore() const notificationsStore = useNotificationsStore() const { state: impersonationConsentState } = useImpersonationConsent() -const topBarButtonsColorClasses = - 'text-gray-700 dark:text-gray-200 hover:text-gray-900 dark:hover:text-gray-50 transition-colors duration-300' +const topBarButtonClasses = + 'flex rounded-full p-2.5 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-gray-50 transition-colors duration-(--duration-small)' const shakeNotificationsIcon = ref(false) const accountMenuOptions = computed(() => { @@ -84,7 +84,7 @@ function openNotificationsDrawer() { >