• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / gui / 1113439055

19 Dec 2023 09:01PM UTC coverage: 82.752% (-17.2%) from 99.964%
1113439055

Pull #4258

gitlab-ci

mender-test-bot
chore: Types update

Signed-off-by: Mender Test Bot <mender@northern.tech>
Pull Request #4258: chore: Types update

4326 of 6319 branches covered (0.0%)

8348 of 10088 relevant lines covered (82.75%)

189.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.19
/src/js/components/settings/organization/billing.js
1
// Copyright 2022 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14
import React, { useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16
import { Link, useNavigate } from 'react-router-dom';
17

18
// material ui
19
import { Error as ErrorIcon, OpenInNew as OpenInNewIcon } from '@mui/icons-material';
20
import { LinearProgress, List } from '@mui/material';
21
import { makeStyles } from 'tss-react/mui';
22

23
import moment from 'moment';
24

25
import { cancelRequest } from '../../../actions/organizationActions';
26
import { ADDONS, PLANS } from '../../../constants/appConstants';
27
import { toggle } from '../../../helpers';
28
import { getAcceptedDevices, getDeviceLimit, getIsEnterprise, getOrganization, getUserRoles } from '../../../selectors';
29
import Alert from '../../common/alert';
30
import CancelRequestDialog from '../dialogs/cancelrequest';
31
import OrganizationPaymentSettings from './organizationpaymentsettings';
32
import OrganizationSettingsItem, { maxWidth } from './organizationsettingsitem';
33

34
const useStyles = makeStyles()(theme => ({
5✔
35
  deviceLimitBar: { backgroundColor: theme.palette.grey[500], margin: '15px 0' },
36
  wrapper: {
37
    backgroundColor: theme.palette.background.lightgrey,
38
    marginTop: theme.spacing(6),
39
    padding: theme.spacing(2),
40
    '&>h5': { marginTop: 0, marginBottom: 0 }
41
  }
42
}));
43

44
export const TrialExpirationNote = ({ trial_expiration }) => (
5✔
45
  <div className="flexbox centered muted">
7✔
46
    <ErrorIcon fontSize="small" />
47
    <span className="margin-left-small">
48
      Your trial expires in {moment().from(moment(trial_expiration), true)}. <Link to="/settings/upgrade">Upgrade to a paid plan</Link>
49
    </span>
50
  </div>
51
);
52

53
export const DeviceLimitExpansionNotification = ({ isTrial }) => (
5✔
54
  <div className="flexbox centered">
1✔
55
    <ErrorIcon className="muted margin-right-small" fontSize="small" />
56
    <div className="muted" style={{ marginRight: 4 }}>
57
      To increase your device limit,{' '}
58
    </div>
59
    {isTrial ? (
1!
60
      <Link to="/settings/upgrade">upgrade to a paid plan</Link>
61
    ) : (
62
      <a href="mailto:support@mender.io" target="_blank" rel="noopener noreferrer">
63
        contact our sales team
64
      </a>
65
    )}
66
    <div className="muted">.</div>
67
  </div>
68
);
69

70
export const CancelSubscriptionAlert = () => (
5✔
71
  <Alert className="margin-top-large" severity="error" style={{ maxWidth }}>
1✔
72
    <p>We&#39;ve started the process to cancel your plan and deactivate your account.</p>
73
    <p>
74
      We&#39;ll send you an email confirming your deactivation. If you have any question at all, contact us at our{' '}
75
      <strong>
76
        <a href="https://support.northern.tech" target="_blank" rel="noopener noreferrer">
77
          support portal
78
        </a>
79
        .
80
      </strong>
81
    </p>
82
  </Alert>
83
);
84

85
export const CancelSubscriptionButton = ({ handleCancelSubscription, isTrial }) => (
5✔
86
  <p className="margin-left-small margin-right-small" style={{ maxWidth }}>
14✔
87
    <a href="" onClick={handleCancelSubscription}>
88
      {isTrial ? 'End trial' : 'Cancel subscription'} and deactivate account
14✔
89
    </a>
90
  </p>
91
);
92

93
export const Billing = () => {
5✔
94
  const [cancelSubscription, setCancelSubscription] = useState(false);
13✔
95
  const [cancelSubscriptionConfirmation, setCancelSubscriptionConfirmation] = useState(false);
13✔
96
  const { isAdmin } = useSelector(getUserRoles);
13✔
97
  const { total: acceptedDevices = 0 } = useSelector(getAcceptedDevices);
13!
98
  const deviceLimit = useSelector(getDeviceLimit);
13✔
99
  const isEnterprise = useSelector(getIsEnterprise);
13✔
100
  const organization = useSelector(getOrganization);
13✔
101
  const { plan: currentPlan = PLANS.os.id } = organization;
13!
102
  const dispatch = useDispatch();
13✔
103
  const navigate = useNavigate();
13✔
104
  const { classes } = useStyles();
13✔
105

106
  const planName = PLANS[currentPlan].name;
13✔
107

108
  const enabledAddOns =
109
    organization.addons?.reduce((accu, addon) => {
13!
110
      if (addon.enabled) {
6!
111
        const { title } = ADDONS[addon.name];
6✔
112
        let addonPrice = '';
6✔
113
        if (!organization.trial && !isEnterprise) {
6!
114
          const planAddon = ADDONS[addon.name][currentPlan] ? ADDONS[addon.name][currentPlan] : ADDONS[addon.name].os;
×
115
          addonPrice = ` - ${planAddon.price}`;
×
116
        }
117
        accu.push(`${title}${addonPrice}`);
6✔
118
      }
119
      return accu;
6✔
120
    }, []) || [];
121

122
  const cancelSubscriptionSubmit = async reason =>
13✔
123
    dispatch(cancelRequest(organization.id, reason)).then(() => {
×
124
      setCancelSubscription(false);
×
125
      setCancelSubscriptionConfirmation(true);
×
126
    });
127

128
  const handleCancelSubscription = e => {
13✔
129
    if (e !== undefined) {
×
130
      e.preventDefault();
×
131
    }
132
    setCancelSubscription(toggle);
×
133
  };
134

135
  return (
13✔
136
    <div className={classes.wrapper}>
137
      <h5>Billing</h5>
138
      <List>
139
        <OrganizationSettingsItem
140
          title="Current plan"
141
          content={{
142
            action: { title: 'Compare product plans', internal: false, target: 'https://mender.io/plans/pricing' },
143
            description: organization.trial ? 'Trial' : planName
13✔
144
          }}
145
          notification={organization.trial ? <TrialExpirationNote trial_expiration={organization.trial_expiration} /> : null}
13✔
146
        />
147
        {deviceLimit > 0 && (
13!
148
          <OrganizationSettingsItem
149
            title={`Device limit: ${acceptedDevices}/${deviceLimit}`}
150
            content={{}}
151
            secondary={<LinearProgress className={classes.deviceLimitBar} variant="determinate" value={(acceptedDevices * 100) / deviceLimit} />}
152
            notification={<DeviceLimitExpansionNotification isTrial={organization.trial} />}
153
          />
154
        )}
155
        <OrganizationSettingsItem
156
          title="Current add-ons"
157
          content={{
158
            action: { title: 'Purchase an add-on', internal: true, action: () => navigate('/settings/upgrade') },
×
159
            description: enabledAddOns.length ? enabledAddOns.join(', ') : `You currently don't have any add-ons`
13✔
160
          }}
161
          notification={organization.trial && <TrialExpirationNote trial_expiration={organization.trial_expiration} />}
16✔
162
          sideBarContent={
163
            <div className="margin-left-small margin-bottom">
164
              {/* eslint-disable-next-line react/jsx-no-target-blank */}
165
              <a className="flexbox center-aligned" href="https://mender.io/plans/pricing" target="_blank" rel="noopener">
166
                <div style={{ maxWidth: 200 }}>Compare plans and add-ons at mender.io</div>
167
                <OpenInNewIcon fontSize="small" />
168
              </a>
169
            </div>
170
          }
171
        />
172
        {!organization.trial && !isEnterprise && <OrganizationPaymentSettings />}
23!
173
      </List>
174
      {cancelSubscriptionConfirmation && <CancelSubscriptionAlert />}
13!
175
      {isAdmin && !cancelSubscriptionConfirmation && (
39✔
176
        <CancelSubscriptionButton handleCancelSubscription={handleCancelSubscription} isTrial={organization.trial} />
177
      )}
178
      {cancelSubscription && <CancelRequestDialog onCancel={() => setCancelSubscription(false)} onSubmit={cancelSubscriptionSubmit} />}
×
179
    </div>
180
  );
181
};
182

183
export default Billing;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc