New design for subscription page (#568)

Co-authored-by: Daniel Valdivia <hola@danielvaldivia.com>
This commit is contained in:
Lenin Alevski
2021-02-05 14:49:31 -06:00
committed by GitHub
parent 3262212bd0
commit 7174892231
4 changed files with 494 additions and 248 deletions

BIN
portal-ui/public/agpl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -17,10 +17,9 @@
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { LinearProgress } from "@material-ui/core";
import { LinearProgress, TextField } from "@material-ui/core";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import TextField from "@material-ui/core/TextField";
import Button from "@material-ui/core/Button";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import { SubscriptionActivateRequest } from "../Buckets/types";
@@ -28,6 +27,8 @@ import { setModalErrorSnackMessage } from "../../../actions";
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import api from "../../../common/api";
import PersonOutlineOutlinedIcon from "@material-ui/icons/PersonOutlineOutlined";
import LockOutlinedIcon from "@material-ui/icons/LockOutlined";
const styles = (theme: Theme) =>
createStyles({
@@ -37,16 +38,32 @@ const styles = (theme: Theme) =>
subnetLicenseKey: {
padding: "10px 10px 10px 0px",
borderRight: "1px solid rgba(0, 0, 0, 0.12)",
opacity: 0.5,
"&:hover": { opacity: 1 },
},
subnetLoginForm: {
padding: "10px 0px 10px 10px",
opacity: 0.5,
"&:hover": { opacity: 1 },
},
licenseKeyField: {
marginBottom: 20,
},
licenseKeyField: {},
pageTitle: {
marginBottom: 20,
},
button: {
textTransform: "none",
fontSize: 15,
fontWeight: 700,
background:
"transparent linear-gradient(90deg, #073052 0%, #081c42 100%) 0% 0% no-repeat padding-box",
color: "#fff",
},
buttonSignup: {
textTransform: "none",
fontSize: 15,
fontWeight: 700,
marginLeft: 15,
},
...containerForHeader(theme.spacing(4)),
});
@@ -110,28 +127,32 @@ const ActivationModal = ({
aria-describedby="alert-dialog-description"
>
<Grid container alignItems="center" item xs={12}>
<Grid item xs={12}>
<Typography component="h2" variant="h6" className={classes.pageTitle}>
Activate SUBNET License
</Typography>
</Grid>
<Grid item className={classes.subnetLicenseKey} xs={6}>
<Grid item xs={12}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
License Key
<Typography variant="caption" display="block" gutterBottom>
Enter your license key here
</Typography>
</Grid>
<TextField
id="license-key"
placeholder="Enter your license key here"
placeholder=""
multiline
rows={6}
rows={3}
value={license}
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
setLicense(event.target.value)
}
fullWidth
className={classes.licenseKeyField}
variant="outlined"
/>
<br />
<br />
<Button
variant="contained"
color="primary"
@@ -142,43 +163,38 @@ const ActivationModal = ({
</Button>
</Grid>
<Grid item className={classes.subnetLoginForm} xs={6}>
<Grid item xs={12}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Subscription Network (SUBNET)
</Typography>
</Grid>
<Grid container>
<Grid item xs={12}>
<InputBoxWrapper
overlayIcon={<PersonOutlineOutlinedIcon />}
id="subnet-email"
name="subnet-email"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSubnetEmail(event.target.value);
}}
label="Email"
placeholder="email"
label=""
type="text"
value={subnetEmail}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
overlayIcon={<LockOutlinedIcon />}
id="subnet-password"
name="subnet-password"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setSubnetPassword(event.target.value);
}}
label="Password"
placeholder="password"
label=""
type="password"
value={subnetPassword}
/>
</Grid>
<Grid item xs={12}>
<Button
variant="contained"
className={classes.button}
color="primary"
onClick={() => activateProduct()}
disabled={
@@ -186,8 +202,23 @@ const ActivationModal = ({
subnetEmail.trim().length === 0 ||
subnetPassword.trim().length === 0
}
variant="contained"
>
Login
Activate
</Button>
<Button
className={classes.buttonSignup}
color="primary"
target="_blank"
rel="noopener noreferrer"
href="#"
onClick={(e) => {
e.preventDefault();
window.open("https://min.io/pricing", "_blank");
}}
variant="outlined"
>
Sign Up
</Button>
</Grid>
</Grid>

View File

@@ -47,12 +47,32 @@ const styles = (theme: Theme) =>
paper: {
padding: "20px 52px 20px 28px",
},
licenseContainer: {
padding: "20px 52px 0px 28px",
background:
"transparent linear-gradient(180deg, #ffffff 0%, #d6e1e8 100%) 0% 0% no-repeat padding-box",
boxShadow: "0px 3px 7px #00000014",
"& h2": {
color: "#000",
marginBottom: "50px",
},
"& a": {
textDecoration: "none",
},
"& h3": {
color: "#000",
marginBottom: "30px",
fontWeight: "bold",
},
"& h6": {
color: "#000 !important",
},
},
tableContainer: {
marginLeft: 28,
},
detailsContainer: {
textAlign: "center",
paddingTop: 18,
paddingBottom: 12,
borderRadius: "3px 3px 0 0",
marginLeft: 8,
@@ -70,6 +90,14 @@ const styles = (theme: Theme) =>
fontSize: 17,
fontWeight: 700,
marginBottom: 26,
paddingTop: 18,
},
currentPlan: {
fontWeight: 700,
background:
"transparent linear-gradient(90deg, #073052 0%, #081C42 100%) 0% 0% no-repeat padding-box",
boxShadow: "0px 3px 7px #00000014",
color: "#fff",
},
detailsPrice: {
fontSize: 12,
@@ -141,7 +169,7 @@ const styles = (theme: Theme) =>
border: 0,
},
buttonContainerHighlighted: {
border: "1px solid #9a93ad",
border: "1px solid #000",
borderTop: 0,
},
button: {
@@ -154,6 +182,49 @@ const styles = (theme: Theme) =>
marginTop: 25,
marginRight: 25,
},
openSourcePolicy: {
color: "#1C5A8D",
fontWeight: "bold",
},
activateLink: {
color: "#1C5A8D",
fontWeight: "bold",
clear: "both",
background: "none",
border: "none",
textDecoration: "underline",
cursor: "pointer",
},
fullWidth: {
width: "100%",
height: "100%",
},
midWidth: {
width: "70%",
float: "left",
height: "100%",
},
smallWidth: {
width: "30%",
float: "right",
height: "100%",
borderRadius: "0px 3px 0px 0px !important",
},
licenseInfo: { color: "#000" },
licenseInfoTitle: {
textTransform: "none",
color: "#000",
},
licenseInfoValue: {
textTransform: "none",
fontSize: 17,
},
licenseDescription: {
background: "#fff",
padding: "30px 30px",
border: "1px solid #e2e5e4",
borderRadius: "5px 5px 0px 0px",
},
...containerForHeader(theme.spacing(4)),
});
@@ -200,262 +271,403 @@ const License = ({ classes, operatorMode }: ILicenseProps) => {
}
return (
<React.Fragment>
{licenseInfo ? (
<React.Fragment>
<PageHeader label="License" />
<Grid item xs={12} className={classes.container}>
<Paper className={classes.paper}>
<Grid container>
<Grid item xs={12}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Subscription Information
</Typography>
Account ID: {licenseInfo.account_id}
<br />
<br />
Email: {licenseInfo.email}
<br />
<br />
Plan: {licenseInfo.plan}
<br />
<br />
Organization: {licenseInfo.organization}
<br />
<br />
Storage Capacity: {licenseInfo.storage_capacity}
<br />
<br />
Expiration: {licenseInfo.expires_at}
</Grid>
</Grid>
</Paper>
</Grid>
</React.Fragment>
) : (
<React.Fragment>
<PageHeader label="License" />
{operatorMode ? (
<Button
className={classes.licenseButton}
variant="contained"
color="primary"
onClick={() => setActivateProductModal(true)}
<React.Fragment>
<PageHeader label="License" />
<Grid container>
<Grid item xs={operatorMode ? 12 : 6} className={classes.container}>
<Paper
className={`${classes.licenseContainer} ${
operatorMode ? classes.midWidth : classes.fullWidth
}`}
>
Activate Product
</Button>
) : null}
<Grid container>
<Grid item xs={12} className={classes.container}>
<Paper className={classes.paper}>
<Grid container>
{operatorMode ? (
<ActivationModal
open={activateProductModal}
closeModal={() => closeModalAndFetchLicenseInfo()}
/>
) : null}
<Grid item xs={12}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Upgrade to commercial license
</Typography>
</Grid>
<Grid
container
item
xs={12}
className={classes.tableContainer}
>
<Grid container item xs={12}>
<Grid item xs={3} className={classes.detailsContainer} />
{planDetails.map((details: any) => {
return (
<Grid
key={details.id}
container
item
xs={3}
className={clsx(
classes.detailsContainer,
classes.detailsContainerBorder,
{
[classes.detailsContainerBorderHighlighted]:
details.title !== "Community",
}
)}
>
<Grid item xs={12} className={classes.detailsTitle}>
{details.title}
</Grid>
<Grid item xs={12} className={classes.detailsPrice}>
{details.price}
</Grid>
<Grid
item
xs={12}
className={classes.detailsCapacityMax}
>
{details.capacityMax || ""}
</Grid>
<Grid
item
xs={12}
className={classes.detailsCapacityMin}
>
{details.capacityMin}
</Grid>
</Grid>
);
})}
{licenseInfo ? (
<React.Fragment>
<Grid container className={classes.licenseInfo}>
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
License
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
Commercial License
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Organization
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{licenseInfo.organization}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Registered Capacity
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{licenseInfo.storage_capacity}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Expiry Date
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{licenseInfo.expires_at}
</Typography>
</Grid>
{planItems.map((item: any) => {
<Grid item xs={6}>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Subscription Plan
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{licenseInfo.plan}
</Typography>
<Typography
variant="button"
display="block"
gutterBottom
className={classes.licenseInfoTitle}
>
Requester
</Typography>
<Typography
variant="overline"
display="block"
gutterBottom
className={classes.licenseInfoValue}
>
{licenseInfo.email}
</Typography>
</Grid>
</Grid>
</React.Fragment>
) : (
<React.Fragment>
<img src="agpl.png" height={40} alt="agpl" />
<Typography component="h2" variant="h6">
GNU Affero General Public License
</Typography>
<a
href={"https://www.gnu.org/licenses/agpl-3.0.html"}
target="_blank"
rel="nofollow noopener noreferrer"
>
<div className={classes.licenseDescription}>
<Typography component="h3">Version 3</Typography>
<Typography component="h6">
The GNU Affero General Public License is a free,
copyleft license for software and other kinds of works,
specifically designed to ensure cooperation with the
Community in the case of network server software.
</Typography>
</div>
</a>
</React.Fragment>
)}
</Paper>
{operatorMode && !licenseInfo && (
<Paper className={`${classes.paper} ${classes.smallWidth}`}>
<Typography
component="h2"
variant="h6"
className={classes.pageTitle}
>
Choosing between GNU AGPL v3 and Commercial License
</Typography>
<Typography component="h6">
If you are building proprietary applications, you may want to
choose the commercial license included as part of the Standard
and Enterprise subscription plans. Applications must otherwise
comply with all GNU AGPLv3 obligations and requirements. Click
the link below to learn more about Open Source license
compliance.
</Typography>
<br />
<a
href="https://min.io/compliance"
className={classes.openSourcePolicy}
target="_blank"
rel="nofollow noopener noreferrer"
>
Open Source Policy Compliance
</a>
</Paper>
)}
</Grid>
<Grid
item
xs={12}
className={classes.container}
style={{ padding: "0px 33px" }}
>
<Paper
className={classes.paper}
style={{ borderRadius: "0px 0px 3px 3px" }}
>
<Grid container>
{operatorMode ? (
<ActivationModal
open={activateProductModal}
closeModal={() => closeModalAndFetchLicenseInfo()}
/>
) : null}
<Grid container item xs={12} className={classes.tableContainer}>
<Grid container item xs={12}>
<Grid item xs={3} className={classes.detailsContainer} />
{planDetails.map((details: any) => {
return (
<Grid
key={item.id}
key={details.id}
container
item
xs={12}
xs={3}
className={clsx(
classes.itemContainer,
item.communityDetail && classes.itemContainerDetail
classes.detailsContainer,
classes.detailsContainerBorder,
{
[classes.detailsContainerBorderHighlighted]:
details.title !== "Community",
}
)}
>
<Grid
item
xs={3}
className={clsx(
classes.item,
classes.field,
classes.itemFirst
)}
>
{item.field}
</Grid>
<Grid container item xs={3} className={classes.item}>
<Grid item xs={12}>
{item.community === "N/A" ? (
""
) : item.community === "Yes" ? (
<CheckCircleIcon
className={classes.checkIcon}
/>
) : (
item.community
)}
{(!licenseInfo && details.title === "Community") ||
(licenseInfo &&
licenseInfo.plan.toLowerCase() ===
details.title.toLowerCase()) ? (
<Grid item xs={12} className={classes.currentPlan}>
Current Plan
</Grid>
{item.communityDetail !== undefined && (
<Grid item xs={12}>
{item.communityDetail}
</Grid>
)}
) : null}
<Grid item xs={12} className={classes.detailsTitle}>
{details.title}
</Grid>
<Grid item xs={12} className={classes.detailsPrice}>
{details.price}
</Grid>
<Grid
container
item
xs={3}
className={clsx(
classes.item,
classes.itemHighlighted
)}
xs={12}
className={classes.detailsCapacityMax}
>
<Grid item xs={12}>
{item.standard === "N/A" ? (
""
) : item.standard === "Yes" ? (
<CheckCircleIcon
className={classes.checkIcon}
/>
) : (
item.standard
)}
</Grid>
{item.standardDetail !== undefined && (
<Grid item xs={12}>
{item.standardDetail}
</Grid>
)}
{details.capacityMax || ""}
</Grid>
<Grid
container
item
xs={3}
className={clsx(
classes.item,
classes.itemHighlighted
)}
xs={12}
className={classes.detailsCapacityMin}
>
<Grid item xs={12}>
{item.enterprise === "N/A" ? (
""
) : item.enterprise === "Yes" ? (
<CheckCircleIcon
className={classes.checkIcon}
/>
) : (
item.enterprise
)}
</Grid>
{item.enterpriseDetail !== undefined && (
<Grid item xs={12}>
{item.enterpriseDetail}
</Grid>
)}
{details.capacityMin}
</Grid>
</Grid>
);
})}
<Grid container item xs={12}>
</Grid>
{planItems.map((item: any) => {
return (
<Grid
key={item.id}
container
item
xs={3}
xs={12}
className={clsx(
classes.buttonContainer,
classes.buttonContainerBlank
classes.itemContainer,
item.communityDetail && classes.itemContainerDetail
)}
/>
{planButtons.map((button: any) => {
return (
<Grid
key={button.id}
container
item
xs={3}
className={clsx(classes.buttonContainer, {
[classes.buttonContainerHighlighted]:
button.text === "Subscribe",
})}
>
>
<Grid
item
xs={3}
className={clsx(
classes.item,
classes.field,
classes.itemFirst
)}
>
{item.field}
</Grid>
<Grid container item xs={3} className={classes.item}>
<Grid item xs={12}>
{item.community === "N/A" ? (
""
) : item.community === "Yes" ? (
<CheckCircleIcon className={classes.checkIcon} />
) : (
item.community
)}
</Grid>
{item.communityDetail !== undefined && (
<Grid item xs={12}>
{item.communityDetail}
</Grid>
)}
</Grid>
<Grid
container
item
xs={3}
className={clsx(
classes.item,
classes.itemHighlighted
)}
>
<Grid item xs={12}>
{item.standard === "N/A" ? (
""
) : item.standard === "Yes" ? (
<CheckCircleIcon className={classes.checkIcon} />
) : (
item.standard
)}
</Grid>
{item.standardDetail !== undefined && (
<Grid item xs={12}>
{item.standardDetail}
</Grid>
)}
</Grid>
<Grid
container
item
xs={3}
className={clsx(
classes.item,
classes.itemHighlighted
)}
>
<Grid item xs={12}>
{item.enterprise === "N/A" ? (
""
) : item.enterprise === "Yes" ? (
<CheckCircleIcon className={classes.checkIcon} />
) : (
item.enterprise
)}
</Grid>
{item.enterpriseDetail !== undefined && (
<Grid item xs={12}>
{item.enterpriseDetail}
</Grid>
)}
</Grid>
</Grid>
);
})}
<Grid container item xs={12}>
<Grid
item
xs={3}
className={clsx(
classes.buttonContainer,
classes.buttonContainerBlank
)}
/>
{planButtons.map((button: any) => {
return (
<Grid
key={button.id}
container
item
xs={3}
style={{ textAlign: "center" }}
className={clsx(classes.buttonContainer, {
[classes.buttonContainerHighlighted]:
button.text === "Subscribe",
})}
>
<Grid item xs={12}>
<Button
variant={
button.text === "Subscribe"
? "contained"
: "outlined"
}
variant="contained"
color="primary"
className={classes.button}
target="_blank"
href={button.link}
rel="noopener noreferrer"
href="#"
disabled={
licenseInfo &&
licenseInfo.plan.toLowerCase() ===
button.plan.toLowerCase()
}
onClick={(e) => {
e.preventDefault();
window.open(button.link, "_blank");
}}
>
{button.text}
</Button>
</Grid>
);
})}
</Grid>
{operatorMode &&
button.text === "Subscribe" &&
!(
licenseInfo &&
licenseInfo.plan.toLowerCase() ===
button.plan.toLowerCase()
) && (
<Grid item xs={12} style={{ marginTop: "10px" }}>
<button
className={classes.activateLink}
onClick={(e) => {
e.preventDefault();
setActivateProductModal(true);
}}
>
Activate
</button>
</Grid>
)}
</Grid>
);
})}
</Grid>
</Grid>
</Paper>
</Grid>
</Grid>
</Paper>
</Grid>
</React.Fragment>
)}
</Grid>
</React.Fragment>
</React.Fragment>
);
};

View File

@@ -119,17 +119,20 @@ export const planItems = [
export const planButtons = [
{
id: 0,
text: "Slack Community",
text: "Join Slack",
link: "https://slack.min.io",
plan: "community",
},
{
id: 1,
text: "Subscribe",
link: "https://min.io/pricing",
plan: "standard",
},
{
id: 2,
text: "Subscribe",
link: "https://min.io/pricing",
plan: "enterprise",
},
];