Skip to content

Commit 88b701b

Browse files
authored
Added premium features notification (cvat-ai#5812)
<!-- Raise an issue to propose your change (https://github.com/opencv/cvat/issues). It helps to avoid duplication of efforts from multiple independent contributors. Discuss your ideas with maintainers to be sure that changes will be approved and merged. Read the [Contribution guide](https://opencv.github.io/cvat/docs/contributing/). --> <!-- Provide a general summary of your changes in the Title above --> ### Motivation and context <!-- Why is this change required? What problem does it solve? If it fixes an open issue, please link to the issue here. Describe your changes in detail, add screenshots. --> ### How has this been tested? <!-- Please describe in detail how you tested your changes. Include details of your testing environment, and the tests you ran to see how your change affects other areas of the code, etc. --> ### Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. If an item isn't applicable for some reason, then ~~explicitly strikethrough~~ the whole line. If you don't do that, GitHub will show incorrect progress for the pull request. If you're unsure about any of these, don't hesitate to ask. We're here to help! --> - [ ] I submit my changes into the `develop` branch - [ ] I have added a description of my changes into the [CHANGELOG](https://github.com/opencv/cvat/blob/develop/CHANGELOG.md) file - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes - [ ] I have linked related issues (see [GitHub docs]( https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)) - [ ] I have increased versions of npm packages if it is necessary ([cvat-canvas](https://github.com/opencv/cvat/tree/develop/cvat-canvas#versioning), [cvat-core](https://github.com/opencv/cvat/tree/develop/cvat-core#versioning), [cvat-data](https://github.com/opencv/cvat/tree/develop/cvat-data#versioning) and [cvat-ui](https://github.com/opencv/cvat/tree/develop/cvat-ui#versioning)) ### License - [ ] I submit _my code changes_ under the same [MIT License]( https://github.com/opencv/cvat/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern.
1 parent 985c9ee commit 88b701b

File tree

9 files changed

+191
-0
lines changed

9 files changed

+191
-0
lines changed

cvat-core/src/api-implementation.ts

+5
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ export default function implementAPI(cvat) {
8686
await serverProxy.server.logout();
8787
};
8888

89+
cvat.server.hasLimits.implementation = async (userId, orgId) => {
90+
const result = await serverProxy.server.hasLimits(userId, orgId);
91+
return result;
92+
};
93+
8994
cvat.server.socialAuthentication.implementation = async () => {
9095
const result: SocialAuthMethodsRawType = await serverProxy.server.socialAuthentication();
9196
return Object.entries(result).map(([provider, value]) => new SocialAuthMethod({ ...value, provider }));

cvat-core/src/api.ts

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ function build() {
7777
const result = await PluginRegistry.apiWrapper(cvat.server.logout);
7878
return result;
7979
},
80+
async hasLimits(userId, orgId) {
81+
const result = await PluginRegistry.apiWrapper(cvat.server.hasLimits, userId, orgId);
82+
return result;
83+
},
8084
async socialAuthentication() {
8185
const result = await PluginRegistry.apiWrapper(cvat.server.socialAuthentication);
8286
return result;

cvat-core/src/server-proxy.ts

+24
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ Axios.interceptors.request.use((reqConfig) => {
262262
return reqConfig;
263263
}
264264

265+
if (reqConfig.url.endsWith('/limits')) {
266+
return reqConfig;
267+
}
268+
265269
reqConfig.params = { ...organization, ...(reqConfig.params || {}) };
266270
return reqConfig;
267271
});
@@ -498,6 +502,25 @@ async function getSelf(): Promise<SerializedUser> {
498502
return response.data;
499503
}
500504

505+
async function hasLimits(userId: number, orgId: number): Promise<boolean> {
506+
const { backendAPI } = config;
507+
508+
try {
509+
const response = await Axios.get(`${backendAPI}/limits`, {
510+
params: {
511+
...(orgId ? { org_id: orgId } : { user_id: userId }),
512+
},
513+
});
514+
return response.data?.count !== 0;
515+
} catch (serverError) {
516+
if (serverError.code === 404) {
517+
return false;
518+
}
519+
520+
throw serverError;
521+
}
522+
}
523+
501524
async function authorized(): Promise<boolean> {
502525
try {
503526
// In CVAT app we use two types of authentication
@@ -2373,6 +2396,7 @@ export default Object.freeze({
23732396
installedApps,
23742397
loginWithSocialAccount,
23752398
selectSSOIdentityProvider,
2399+
hasLimits,
23762400
}),
23772401

23782402
projects: Object.freeze({

cvat-ui/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
import 'redux-thunk/extend-redux';
66

77
declare module '*.svg';
8+
declare module '*.png';
89
declare module 'cvat-core/src/api';
747 KB
Loading

cvat-ui/src/components/cvat-app.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import EmailConfirmationPage from './email-confirmation-pages/email-confirmed';
7575
import EmailVerificationSentPage from './email-confirmation-pages/email-verification-sent';
7676
import IncorrectEmailConfirmationPage from './email-confirmation-pages/incorrect-email-confirmation';
7777
import CreateModelPage from './create-model-page/create-model-page';
78+
import PremiumFeaturesModal from './premium-features-modal/premium-features-modal';
7879

7980
interface CVATAppProps {
8081
loadFormats: () => void;
@@ -398,6 +399,8 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
398399
isModelPluginActive,
399400
} = this.props;
400401

402+
const { CVAT_BILLING_URL } = appConfig;
403+
401404
const { healthIinitialized, backendIsHealthy } = this.state;
402405

403406
const notRegisteredUserInitialized = (userInitialized && (user == null || !user.isVerified));
@@ -493,6 +496,7 @@ class CVATApplication extends React.PureComponent<CVATAppProps & RouteComponentP
493496
<ExportBackupModal />
494497
<ImportDatasetModal />
495498
<ImportBackupModal />
499+
{ CVAT_BILLING_URL && <PremiumFeaturesModal /> }
496500
{/* eslint-disable-next-line */}
497501
<a id='downloadAnchor' target='_blank' style={{ display: 'none' }} download />
498502
</Layout.Content>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (C) 2023 CVAT.ai Corporation
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
import React, { useCallback, useEffect, useState } from 'react';
6+
import { useSelector } from 'react-redux';
7+
import { CombinedState } from 'reducers';
8+
import Typography from 'antd/es/typography';
9+
import Modal from 'antd/lib/modal';
10+
import { Col, Row } from 'antd/lib/grid';
11+
import Switch from 'antd/lib/switch';
12+
import Button from 'antd/lib/button';
13+
import Space from 'antd/lib/space';
14+
import { getCore } from 'cvat-core-wrapper';
15+
import PaidFeaturesPreviewImage from 'assets/paid-features-preview.png';
16+
import moment from 'moment';
17+
import config from 'config';
18+
import './styles.scss';
19+
20+
const core = getCore();
21+
const { CVAT_BILLING_URL } = config;
22+
23+
export default function PremiumFeaturesModal(): JSX.Element {
24+
const [isModalOpen, setIsModalOpen] = useState(false);
25+
const [dontShowAgainToday, setDontShowAgainToday] = useState(localStorage.getItem('paidNotificationShown') === moment().format('YYYY-MM-DD'));
26+
const organization = useSelector((state: CombinedState) => state.organizations.current);
27+
const user = useSelector((state: CombinedState) => state.auth.user);
28+
29+
const closeModal = useCallback((): void => {
30+
if (dontShowAgainToday) {
31+
const now = moment().format('YYYY-MM-DD');
32+
localStorage.setItem('paidNotificationShown', now);
33+
}
34+
setIsModalOpen(false);
35+
}, [dontShowAgainToday]);
36+
37+
const openBillingPage = useCallback((): void => {
38+
const params = organization?.id ? `/?type=organization&orgId=${organization.id}` : '/?type=personal';
39+
const billingURL = `${CVAT_BILLING_URL}${params}`;
40+
window.open(billingURL, '_self');
41+
}, []);
42+
43+
useEffect(() => {
44+
if (!dontShowAgainToday) {
45+
core.server.hasLimits(user.id, organization?.id).then((hasLimits: boolean) => {
46+
if (!hasLimits && !user.isSuperuser && !user.isStaff) {
47+
setIsModalOpen(true);
48+
}
49+
});
50+
}
51+
}, [user, organization]);
52+
53+
return (
54+
<>
55+
<Modal
56+
className='cvat-paid-features-notification'
57+
visible={isModalOpen}
58+
onCancel={closeModal}
59+
closable={false}
60+
width={725}
61+
footer={(
62+
<Row justify='space-between'>
63+
<Col className='cvat-paid-features-notification-dont-show'>
64+
<Switch onChange={(checked: boolean) => setDontShowAgainToday(checked)} />
65+
<Typography.Text>Don&apos;t show again today</Typography.Text>
66+
</Col>
67+
<Col>
68+
<Button onClick={closeModal}>
69+
Close
70+
</Button>
71+
<Button
72+
type='primary'
73+
onClick={openBillingPage}
74+
>
75+
Check premium features
76+
</Button>
77+
</Col>
78+
</Row>
79+
)}
80+
>
81+
<img src={PaidFeaturesPreviewImage} alt='paid-features-preview' />
82+
<Row className='cvat-paid-features-notification-description' justify='space-between'>
83+
<Col className='cvat-paid-features-notification-description-title' span={8}>
84+
<Typography.Title level={3}>Enhance your experience with premium features</Typography.Title>
85+
</Col>
86+
<Col className='cvat-paid-features-notification-description-details' span={14}>
87+
<Space direction='vertical' size='small'>
88+
<Typography.Text>
89+
We&apos;ve added premium features designed to enhance your experience and productivity.
90+
These features include advanced analytics and reporting, automated annotation,
91+
better user management, and increased storage capacity.
92+
</Typography.Text>
93+
<Typography.Text>
94+
In order to access these features, you need to subscribe to our paid plan.
95+
</Typography.Text>
96+
</Space>
97+
</Col>
98+
</Row>
99+
<Row />
100+
</Modal>
101+
</>
102+
);
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (C) 2023 CVAT.ai Corporation
2+
//
3+
// SPDX-License-Identifier: MIT
4+
5+
@import '../../base.scss';
6+
7+
.cvat-paid-features-notification {
8+
width: $grid-unit-size * 40;
9+
height: $grid-unit-size * 20;
10+
11+
img {
12+
max-width: 100%;
13+
max-height: 100%;
14+
}
15+
16+
.ant-modal-body {
17+
padding: 0;
18+
}
19+
20+
.ant-modal-footer {
21+
border: none;
22+
padding: $grid-unit-size * 4;
23+
24+
.cvat-paid-features-notification-dont-show {
25+
margin-top: $grid-unit-size;
26+
27+
.ant-switch {
28+
margin-right: $grid-unit-size * 2;
29+
}
30+
}
31+
}
32+
33+
.cvat-paid-features-notification-description {
34+
padding: $grid-unit-size * 6 $grid-unit-size * 4;
35+
}
36+
37+
.cvat-paid-features-notification-description-title {
38+
h3 {
39+
font-size: 26px;
40+
}
41+
}
42+
43+
.cvat-paid-features-notification-description-details {
44+
font-size: 13px;
45+
}
46+
}

cvat-ui/webpack.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ module.exports = (env) => {
121121
},
122122
],
123123
},
124+
{
125+
test: /\.(png|jpg|jpeg|gif)$/i,
126+
type: 'asset/resource',
127+
},
124128
{
125129
test: /3rdparty\/.*\.worker\.js$/,
126130
use: {

0 commit comments

Comments
 (0)