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

mendersoftware / gui / 897326496

pending completion
897326496

Pull #3752

gitlab-ci

mzedel
chore(e2e): made use of shared timeout & login checking values to remove code duplication

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3752: chore(e2e-tests): slightly simplified log in test + separated log out test

4395 of 6392 branches covered (68.76%)

8060 of 9780 relevant lines covered (82.41%)

126.17 hits per line

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

62.82
/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 { connect } 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 { getIsEnterprise, 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">
5✔
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 }}>
3✔
84
    <a href="" onClick={handleCancelSubscription}>
85
      {isTrial ? 'End trial' : 'Cancel subscription'} and deactivate account
3!
86
    </a>
87
  </p>
88
);
89

90
export const Billing = ({ acceptedDevices, cancelRequest, currentPlan, deviceLimit, isAdmin, isEnterprise, organization }) => {
6✔
91
  const [cancelSubscription, setCancelSubscription] = useState(false);
2✔
92
  const [cancelSubscriptionConfirmation, setCancelSubscriptionConfirmation] = useState(false);
2✔
93
  const navigate = useNavigate();
2✔
94
  const { classes } = useStyles();
2✔
95

96
  const planName = PLANS[currentPlan].name;
2✔
97

98
  const enabledAddOns =
99
    organization.addons?.reduce((accu, addon) => {
2!
100
      if (addon.enabled) {
4!
101
        const { title } = ADDONS[addon.name];
4✔
102
        let addonPrice = '';
4✔
103
        if (!organization.trial && !isEnterprise) {
4!
104
          const planAddon = ADDONS[addon.name][currentPlan] ? ADDONS[addon.name][currentPlan] : ADDONS[addon.name].os;
×
105
          addonPrice = ` - ${planAddon.price}`;
×
106
        }
107
        accu.push(`${title}${addonPrice}`);
4✔
108
      }
109
      return accu;
4✔
110
    }, []) || [];
111

112
  const cancelSubscriptionSubmit = async reason =>
2✔
113
    cancelRequest(organization.id, reason).then(() => {
×
114
      setCancelSubscription(false);
×
115
      setCancelSubscriptionConfirmation(true);
×
116
    });
117

118
  const handleCancelSubscription = e => {
2✔
119
    if (e !== undefined) {
×
120
      e.preventDefault();
×
121
    }
122
    setCancelSubscription(toggle);
×
123
  };
124

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

172
const actionCreators = { cancelRequest };
6✔
173

174
const mapStateToProps = state => {
6✔
175
  const currentPlan = state.organization.organization.plan || 'os';
2!
176
  const { isAdmin } = getUserRoles(state);
2✔
177
  return {
2✔
178
    acceptedDevices: state.devices.byStatus.accepted.total,
179
    currentPlan,
180
    deviceLimit: state.devices.limit,
181
    isAdmin,
182
    isEnterprise: getIsEnterprise(state),
183
    organization: state.organization.organization
184
  };
185
};
186
export default connect(mapStateToProps, actionCreators)(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