Skip to content

Commit

Permalink
Enterprise upgrade modal changes & fixes (#946)
Browse files Browse the repository at this point in the history
* limited text

* fix devicecard

* barebones enterprise upgrade toast

* translations

* update style

* defguard-ui bump
  • Loading branch information
t-aleksander authored Jan 16, 2025
1 parent 6d991e7 commit 98eadd5
Show file tree
Hide file tree
Showing 14 changed files with 338 additions and 42 deletions.
9 changes: 8 additions & 1 deletion web/src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const en: BaseTranslation = {
enterprise: {
title: 'Upgrade to Enterprise',
//md
subTitle: `This functionality is an **enterprise feature** and requires purchasing a license to enable it.`,
subTitle: `This functionality is an **enterprise feature** and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one.`,
},
limit: {
title: 'Upgrade',
Expand Down Expand Up @@ -182,6 +182,13 @@ Licensing information: [https://docs.defguard.net/enterprise/license](https://do
more: "See what's new",
},
},
enterpriseUpgradeToaster: {
title: `You've reached the enterprise functionality limit.`,
message: `You've exceeded the limit of your current Defguard plan and the enterprise
features will be disabled. Purchase an enterprise license or upgrade your
exsiting one to continue using these features.`,
link: 'See all enterprise plans',
},
updatesNotification: {
header: {
title: 'Update Available',
Expand Down
36 changes: 34 additions & 2 deletions web/src/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ type RootTranslation = {
*/
title: string
/**
* T​h​i​s​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​ ​i​s​ ​a​n​ ​*​*​e​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​*​*​ ​a​n​d​ ​r​e​q​u​i​r​e​s​ ​p​u​r​c​h​a​s​i​n​g​ ​a​ ​l​i​c​e​n​s​e​ ​t​o​ ​e​n​a​b​l​e​ ​i​t​.
* T​h​i​s​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​ ​i​s​ ​a​n​ ​*​*​e​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​*​*​ ​a​n​d​ ​y​o​u​'​v​e​ ​e​x​c​e​e​d​e​d​ ​t​h​e​ ​u​s​e​r​,​ ​d​e​v​i​c​e​ ​o​r​ ​n​e​t​w​o​r​k​ ​l​i​m​i​t​s​ ​t​o​ ​u​s​e​ ​i​t​.​ ​I​n​ ​o​r​d​e​r​ ​t​o​ ​u​s​e​ ​t​h​i​s​ ​f​e​a​t​u​r​e​,​ ​p​u​r​c​h​a​s​e​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​o​r​ ​u​p​g​r​a​d​e​ ​y​o​u​r​ ​e​x​i​s​t​i​n​g​ ​o​n​e​.
*/
subTitle: string
}
Expand Down Expand Up @@ -430,6 +430,22 @@ type RootTranslation = {
more: string
}
}
enterpriseUpgradeToaster: {
/**
* Y​o​u​'​v​e​ ​r​e​a​c​h​e​d​ ​t​h​e​ ​e​n​t​e​r​p​r​i​s​e​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​ ​l​i​m​i​t​.
*/
title: string
/**
* Y​o​u​'​v​e​ ​e​x​c​e​e​d​e​d​ ​t​h​e​ ​l​i​m​i​t​ ​o​f​ ​y​o​u​r​ ​c​u​r​r​e​n​t​ ​D​e​f​g​u​a​r​d​ ​p​l​a​n​ ​a​n​d​ ​t​h​e​ ​e​n​t​e​r​p​r​i​s​e​
​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​e​a​t​u​r​e​s​ ​w​i​l​l​ ​b​e​ ​d​i​s​a​b​l​e​d​.​ ​P​u​r​c​h​a​s​e​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​o​r​ ​u​p​g​r​a​d​e​ ​y​o​u​r​
​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​e​x​s​i​t​i​n​g​ ​o​n​e​ ​t​o​ ​c​o​n​t​i​n​u​e​ ​u​s​i​n​g​ ​t​h​e​s​e​ ​f​e​a​t​u​r​e​s​.
*/
message: string
/**
* S​e​e​ ​a​l​l​ ​e​n​t​e​r​p​r​i​s​e​ ​p​l​a​n​s
*/
link: string
}
updatesNotification: {
header: {
/**
Expand Down Expand Up @@ -4969,7 +4985,7 @@ export type TranslationFunctions = {
*/
title: () => LocalizedString
/**
* This functionality is an **enterprise feature** and requires purchasing a license to enable it.
* This functionality is an **enterprise feature** and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one.
*/
subTitle: () => LocalizedString
}
Expand Down Expand Up @@ -5236,6 +5252,22 @@ export type TranslationFunctions = {
more: () => LocalizedString
}
}
enterpriseUpgradeToaster: {
/**
* You've reached the enterprise functionality limit.
*/
title: () => LocalizedString
/**
* You've exceeded the limit of your current Defguard plan and the enterprise
features will be disabled. Purchase an enterprise license or upgrade your
exsiting one to continue using these features.
*/
message: () => LocalizedString
/**
* See all enterprise plans
*/
link: () => LocalizedString
}
updatesNotification: {
header: {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import { useNavigate } from 'react-router';
import { shallow } from 'zustand/shallow';

import { useI18nContext } from '../../../../i18n/i18n-react';
import { useUpgradeLicenseModal } from '../../../../shared/components/Layout/UpgradeLicenseModal/store';
import { UpgradeLicenseModalVariant } from '../../../../shared/components/Layout/UpgradeLicenseModal/types';
import { DeviceConfigsCard } from '../../../../shared/components/network/DeviceConfigsCard/DeviceConfigsCard';
import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card';
import { Input } from '../../../../shared/defguard-ui/components/Layout/Input/Input';
import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox';
import { MessageBoxType } from '../../../../shared/defguard-ui/components/Layout/MessageBox/types';
import { useAppStore } from '../../../../shared/hooks/store/useAppStore';
import { useAuthStore } from '../../../../shared/hooks/store/useAuthStore';
import { useEnterpriseUpgradeStore } from '../../../../shared/hooks/store/useEnterpriseUpgradeStore';
import useApi from '../../../../shared/hooks/useApi';
import { useAddDevicePageStore } from '../../hooks/useAddDevicePageStore';

Expand All @@ -31,7 +30,7 @@ export const AddDeviceConfigStep = () => {
const { getAppInfo } = useApi();
const isAdmin = useAuthStore((s) => s.user?.is_admin);
const setAppStore = useAppStore((s) => s.setState);
const openUpgradeLicenseModal = useUpgradeLicenseModal((s) => s.open, shallow);
const showUpgradeToast = useEnterpriseUpgradeStore((s) => s.show);

const [userData, device, publicKey, privateKey, networks] = useAddDevicePageStore(
(state) => [
Expand Down Expand Up @@ -63,9 +62,7 @@ export const AddDeviceConfigStep = () => {
void getAppInfo().then((response) => {
setAppStore({ appInfo: response });
if (response.license_info.any_limit_exceeded) {
openUpgradeLicenseModal({
modalVariant: UpgradeLicenseModalVariant.LICENSE_LIMIT,
});
showUpgradeToast();
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { useNavigate } from 'react-router';
import { shallow } from 'zustand/shallow';

import { useI18nContext } from '../../../../i18n/i18n-react';
import { useUpgradeLicenseModal } from '../../../../shared/components/Layout/UpgradeLicenseModal/store';
import { UpgradeLicenseModalVariant } from '../../../../shared/components/Layout/UpgradeLicenseModal/types';
import { ActionButton } from '../../../../shared/defguard-ui/components/Layout/ActionButton/ActionButton';
import { ActionButtonVariant } from '../../../../shared/defguard-ui/components/Layout/ActionButton/types';
import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card';
Expand All @@ -28,7 +26,6 @@ export const AddDeviceTokenStep = () => {
const navigate = useNavigate();
const { getAppInfo } = useApi();
const setAppStore = useAppStore((s) => s.setState, shallow);
const openUpgradeLicenseModal = useUpgradeLicenseModal((s) => s.open, shallow);
const isAdmin = useAuthStore((s) => s.user?.is_admin);

const userData = useAddDevicePageStore((state) => state.userData);
Expand Down Expand Up @@ -83,11 +80,6 @@ export const AddDeviceTokenStep = () => {
setAppStore({
appInfo: response,
});
if (response.license_info.any_limit_exceeded) {
openUpgradeLicenseModal({
modalVariant: UpgradeLicenseModalVariant.LICENSE_LIMIT,
});
}
});
}
setTimeout(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { EditButton } from '../../../../../shared/defguard-ui/components/Layout/
import { EditButtonOption } from '../../../../../shared/defguard-ui/components/Layout/EditButton/EditButtonOption';
import { EditButtonOptionStyleVariant } from '../../../../../shared/defguard-ui/components/Layout/EditButton/types';
import { Label } from '../../../../../shared/defguard-ui/components/Layout/Label/Label';
import { LimitedText } from '../../../../../shared/defguard-ui/components/Layout/LimitedText/LimitedText';
import { NoData } from '../../../../../shared/defguard-ui/components/Layout/NoData/NoData';
import { useAppStore } from '../../../../../shared/hooks/store/useAppStore';
import { useUserProfileStore } from '../../../../../shared/hooks/store/useUserProfileStore';
Expand Down Expand Up @@ -107,21 +108,22 @@ export const DeviceCard = ({ device, modifiable }: Props) => {
<h3 data-testid="device-name">{device.name}</h3>
</header>
<div className="section-content">
<div>
<div className="limited">
<Label>{LL.userPage.devices.card.labels.publicIP()}</Label>
{latestLocation?.last_connected_ip && (
<p data-testid="device-last-connected-from">
{latestLocation.last_connected_ip}
</p>
<LimitedText
text={latestLocation.last_connected_ip}
testId="device-last-connected-from"
/>
)}
{!latestLocation?.last_connected_ip && (
<NoData customMessage={LL.userPage.devices.card.labels.noData()} />
)}
</div>
<div>
<div className="limited">
<Label>{LL.userPage.devices.card.labels.connectedThrough()}</Label>
{latestLocation && latestLocation.last_connected_at && (
<p>{latestLocation?.network_name}</p>
<LimitedText text={latestLocation?.network_name} />
)}
{!latestLocation?.last_connected_at && (
<NoData customMessage={LL.userPage.devices.card.labels.noData()} />
Expand Down Expand Up @@ -220,10 +222,10 @@ const DeviceLocation = ({
</div>
</header>
<div className="section-content">
<div>
<div className="limited">
<Label>{LL.userPage.devices.card.labels.lastLocation()}</Label>
{last_connected_ip && (
<p data-testid="device-last-connected-from">{last_connected_ip}</p>
<LimitedText text={last_connected_ip} testId="device-last-connected-from" />
)}
{!last_connected_ip && (
<NoData customMessage={LL.userPage.devices.card.labels.noData()} />
Expand All @@ -238,9 +240,9 @@ const DeviceLocation = ({
<NoData customMessage={LL.userPage.devices.card.labels.noData()} />
)}
</div>
<div>
<div className="limited">
<Label>{LL.userPage.devices.card.labels.assignedIp()}</Label>
<p data-testid="device-assigned-ip">{device_wireguard_ip}</p>
<LimitedText text={device_wireguard_ip} testId="device-assigned-ip" />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
max-width: 100%;
}

.limited {
max-width: 120px;
}

.main-info {
& > header {
grid-template-rows: 40px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { z } from 'zod';
import { shallow } from 'zustand/shallow';

import { useI18nContext } from '../../../../../../../i18n/i18n-react';
import { useUpgradeLicenseModal } from '../../../../../../../shared/components/Layout/UpgradeLicenseModal/store';
import { UpgradeLicenseModalVariant } from '../../../../../../../shared/components/Layout/UpgradeLicenseModal/types';
import { FormCheckBox } from '../../../../../../../shared/defguard-ui/components/Form/FormCheckBox/FormCheckBox';
import { FormInput } from '../../../../../../../shared/defguard-ui/components/Form/FormInput/FormInput';
import { Button } from '../../../../../../../shared/defguard-ui/components/Layout/Button/Button';
Expand All @@ -20,6 +18,7 @@ import {
ButtonStyleVariant,
} from '../../../../../../../shared/defguard-ui/components/Layout/Button/types';
import { useAppStore } from '../../../../../../../shared/hooks/store/useAppStore';
import { useEnterpriseUpgradeStore } from '../../../../../../../shared/hooks/store/useEnterpriseUpgradeStore';
import useApi from '../../../../../../../shared/hooks/useApi';
import { useToaster } from '../../../../../../../shared/hooks/useToaster';
import {
Expand Down Expand Up @@ -140,7 +139,7 @@ export const AddUserForm = () => {

const setAppStore = useAppStore((s) => s.setState, shallow);

const openUpgradeLicenseModal = useUpgradeLicenseModal((s) => s.open, shallow);
const showUpgradeToast = useEnterpriseUpgradeStore((s) => s.show);

const toaster = useToaster();

Expand All @@ -158,11 +157,7 @@ export const AddUserForm = () => {
appInfo: response,
});
if (response.license_info.any_limit_exceeded) {
openUpgradeLicenseModal({
modalVariant: response.license_info.enterprise
? UpgradeLicenseModalVariant.LICENSE_LIMIT
: UpgradeLicenseModalVariant.ENTERPRISE_NOTICE,
});
showUpgradeToast();
}
});

Expand Down
9 changes: 3 additions & 6 deletions web/src/pages/wizard/components/WizardNav/WizardNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import { useNavigate } from 'react-router';
import { shallow } from 'zustand/shallow';

import { useI18nContext } from '../../../../i18n/i18n-react';
import { useUpgradeLicenseModal } from '../../../../shared/components/Layout/UpgradeLicenseModal/store';
import { UpgradeLicenseModalVariant } from '../../../../shared/components/Layout/UpgradeLicenseModal/types';
import DefguardNoIcon from '../../../../shared/components/svg/DefguardNoIcon';
import SvgIconArrowGrayLeft from '../../../../shared/components/svg/IconArrowGrayLeft';
import SvgIconArrowGrayRight from '../../../../shared/components/svg/IconArrowGrayRight';
Expand All @@ -19,6 +17,7 @@ import {
import { Divider } from '../../../../shared/defguard-ui/components/Layout/Divider/Divider';
import { DividerDirection } from '../../../../shared/defguard-ui/components/Layout/Divider/types';
import { useAppStore } from '../../../../shared/hooks/store/useAppStore';
import { useEnterpriseUpgradeStore } from '../../../../shared/hooks/store/useEnterpriseUpgradeStore';
import useApi from '../../../../shared/hooks/useApi';
import { useToaster } from '../../../../shared/hooks/useToaster';
import { QueryKeys } from '../../../../shared/queries';
Expand All @@ -33,7 +32,6 @@ interface Props {

export const WizardNav = ({ title, lastStep, backDisabled = false }: Props) => {
const { getAppInfo } = useApi();
const openUpgradeLicenseModal = useUpgradeLicenseModal((s) => s.open, shallow);
const setAppState = useAppStore((s) => s.setState, shallow);
const queryClient = useQueryClient();
const { LL } = useI18nContext();
Expand All @@ -54,6 +52,7 @@ export const WizardNav = ({ title, lastStep, backDisabled = false }: Props) => {
],
shallow,
);
const showUpgradeToast = useEnterpriseUpgradeStore((s) => s.show);

useEffect(() => {
const sub = nextSubject.subscribe(() => {
Expand All @@ -64,9 +63,7 @@ export const WizardNav = ({ title, lastStep, backDisabled = false }: Props) => {
void getAppInfo().then((response) => {
setAppState({ appInfo: response });
if (response.license_info.any_limit_exceeded) {
openUpgradeLicenseModal({
modalVariant: UpgradeLicenseModalVariant.LICENSE_LIMIT,
});
showUpgradeToast();
}
});
navigate('/admin/overview', { replace: true });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import './style.scss';

import { useCallback } from 'react';

import { useI18nContext } from '../../../../i18n/i18n-react';
import { Badge } from '../../../defguard-ui/components/Layout/Badge/Badge';
import { BadgeStyleVariant } from '../../../defguard-ui/components/Layout/Badge/types';
import { ToastOptions } from '../../../defguard-ui/components/Layout/ToastManager/Toast/types';
import { useToastsStore } from '../../../defguard-ui/hooks/toasts/useToastStore';
import SvgIconX from '../../svg/IconX';

export const EnterpriseUpgradeToast = ({ id }: ToastOptions) => {
const removeToast = useToastsStore((s) => s.removeToast);
const { LL } = useI18nContext();

const closeToast = useCallback(() => {
removeToast(id);
}, [id, removeToast]);

const handleDismiss = () => {
closeToast();
};

return (
<div className="enterprise-upgrade-toaster">
<div className="top">
<div className="heading">
<Badge
styleVariant={BadgeStyleVariant.PRIMARY}
icon={
<svg
width="8"
height="10"
viewBox="0 0 8 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.75294 0.891119C4.75294 0.553623 4.47935 0.280029 4.14185 0.280029C3.80436 0.280029 3.53076 0.553623 3.53076 0.891119V9.38893C3.53076 9.72642 3.80436 10 4.14185 10C4.47935 10 4.75294 9.72642 4.75294 9.38893V0.891119Z"
fill="white"
/>
<path
d="M4.54343 1.29638C4.78208 1.05773 4.78208 0.670812 4.54343 0.432167C4.30479 0.193521 3.91787 0.193521 3.67922 0.432167L1.51869 2.59269C1.28005 2.83134 1.28005 3.21826 1.5187 3.45691C1.75734 3.69555 2.14426 3.69555 2.38291 3.45691L4.54343 1.29638Z"
fill="white"
/>
<path
d="M4.5739 0.432152C4.33526 0.193507 3.94834 0.193507 3.70969 0.432152C3.47105 0.670798 3.47105 1.05772 3.70969 1.29636L5.87022 3.45689C6.10887 3.69554 6.49579 3.69554 6.73443 3.45689C6.97308 3.21825 6.97308 2.83132 6.73443 2.59268L4.5739 0.432152Z"
fill="white"
/>
</svg>
}
className="toaster-badge"
/>
<p>{LL.modals.enterpriseUpgradeToaster.title()}</p>
</div>
<button className="dismiss" onClick={handleDismiss}>
<SvgIconX width={14} height={14} />
</button>
</div>
<div className="bottom">
<p>{LL.modals.enterpriseUpgradeToaster.message()}</p>
<div className="upgrade-link-container">
<a
href="https://defguard.net/pricing/"
target="_blank"
rel="noreferrer noopener"
className="upgrade-link"
>
{LL.modals.enterpriseUpgradeToaster.link()}
</a>
</div>
</div>
</div>
);
};
Loading

0 comments on commit 98eadd5

Please sign in to comment.