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

mendersoftware / gui / 901187442

pending completion
901187442

Pull #3795

gitlab-ci

mzedel
feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

Ticket: None
Changelog: None
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3795: feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

4389 of 6365 branches covered (68.96%)

5 of 5 new or added lines in 1 file covered. (100.0%)

1729 existing lines in 165 files now uncovered.

8274 of 10019 relevant lines covered (82.58%)

144.86 hits per line

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

62.5
/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 => ({
6✔
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 }) => (
6✔
45
  <div className="flexbox centered muted">
9✔
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 }) => (
6✔
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:contact@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 = () => (
6✔
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{' '}
75
      <strong>
76
        <a href="mailto:support@mender.io">support@mender.io</a>
77
      </strong>
78
    </p>
79
  </Alert>
80
);
81

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

90
export const Billing = () => {
6✔
91
  const [cancelSubscription, setCancelSubscription] = useState(false);
4✔
92
  const [cancelSubscriptionConfirmation, setCancelSubscriptionConfirmation] = useState(false);
4✔
93
  const { isAdmin } = useSelector(getUserRoles);
4✔
94
  const { total: acceptedDevices = 0 } = useSelector(getAcceptedDevices);
4!
95
  const deviceLimit = useSelector(getDeviceLimit);
4✔
96
  const isEnterprise = useSelector(getIsEnterprise);
4✔
97
  const organization = useSelector(getOrganization);
4✔
98
  const { plan: currentPlan = 'os' } = organization;
4!
99
  const dispatch = useDispatch();
4✔
100
  const navigate = useNavigate();
4✔
101
  const { classes } = useStyles();
4✔
102

103
  const planName = PLANS[currentPlan].name;
4✔
104

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

119
  const cancelSubscriptionSubmit = async reason =>
4✔
UNCOV
120
    dispatch(cancelRequest(organization.id, reason)).then(() => {
×
UNCOV
121
      setCancelSubscription(false);
×
UNCOV
122
      setCancelSubscriptionConfirmation(true);
×
123
    });
124

125
  const handleCancelSubscription = e => {
4✔
UNCOV
126
    if (e !== undefined) {
×
UNCOV
127
      e.preventDefault();
×
128
    }
UNCOV
129
    setCancelSubscription(toggle);
×
130
  };
131

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

179
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