Skip to content

Enterprise upgrade modal changes & fixes #946

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 16, 2025
Merged
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
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
Loading