From 0a760340830f51ee474d96b883d7722d1ecb5f5e Mon Sep 17 00:00:00 2001 From: dhaval055 Date: Wed, 25 Jan 2023 18:45:02 +0530 Subject: [PATCH] more fixes in 1.2.0 --- frontend/src/components/Billing.jsx | 317 ++++ .../components/{Dropzone.js => Dropzone.jsx} | 0 frontend/src/components/Newsletter.jsx | 102 ++ frontend/src/components/OrgHeaderexpanded.jsx | 702 ++++++++ frontend/src/views/AngularWorkflow.jsx | 2 +- frontend/src/views/Faq.jsx | 258 +++ frontend/src/views/HandlePaymentNew.jsx | 1548 +++++++++++++++++ frontend/src/views/Services.jsx | 203 +++ 8 files changed, 3131 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/Billing.jsx rename frontend/src/components/{Dropzone.js => Dropzone.jsx} (100%) create mode 100644 frontend/src/components/Newsletter.jsx create mode 100644 frontend/src/components/OrgHeaderexpanded.jsx create mode 100644 frontend/src/views/Faq.jsx create mode 100644 frontend/src/views/HandlePaymentNew.jsx create mode 100644 frontend/src/views/Services.jsx diff --git a/frontend/src/components/Billing.jsx b/frontend/src/components/Billing.jsx new file mode 100644 index 00000000..0394adad --- /dev/null +++ b/frontend/src/components/Billing.jsx @@ -0,0 +1,317 @@ +import React, { useState, useEffect } from "react"; +import ReactGA from 'react-ga'; + +import { useTheme } from "@material-ui/core/styles"; +import { + Paper, + Typography, + Divider, + Button, + Grid, + Card, +} from "@material-ui/core"; + +import { useAlert } from "react-alert"; +import { typecost, typecost_single, } from "../views/HandlePaymentNew.jsx"; + +const Billing = (props) => { + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, } = props; + console.log("Billing: ", billingInfo); + const theme = useTheme(); + const alert = useAlert(); + + const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : "" + console.log("Stripe: ", stripe) + + const paperStyle = { + padding: 20, + height: "100%", + width: "100%", + backgroundColor: theme.palette.surfaceColor, + border: "1px solid rgba(255,255,255,0.3)", + marginRight: 10, + } + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + billingInfo.subscription = { + "active": true, + "name": "Pay as you go", + "price": typecost_single, + "currency": "USD", + "currency_text": "$", + "interval": "app run / month", + "description": "Pay as you go", + "features": [ + "Includes 10.000 app run/month for free. ", + "Pay for what you use with no minimum commitment and cancel anytime.", + ], + "limit": 10000, + } + + + const handleStripeRedirect = () => { + //var priceItem = "price_1MRNF1DzMUgUjxHSfFTUb2Xh" + if (stripe == "") { + console.log("Stripe not loaded") + return + } + + var priceItem = "price_1MROFrDzMUgUjxHShcSxgHO1" + + const successUrl = `${window.location.origin}/admin?admin_tab=billing&payment=success` + const failUrl = `${window.location.origin}/admin?admin_tab=billing&payment=failure` + var checkoutObject = { + lineItems: [ + { + price: priceItem, + quantity: 1 + }, + ], + mode: "subscription", + billingAddressCollection: "auto", + successUrl: successUrl, + cancelUrl: failUrl, + clientReferenceId: props.userdata.active_org.id, + } + //submitType: "donate", + + stripe.redirectToCheckout(checkoutObject) + .then(function (result) { + console.log("SUCCESS STRIPE?: ", result) + + ReactGA.event({ + category: "pricing", + action: "add_card_success", + label: "", + }) + }) + .catch(function(error) { + console.error("STRIPE ERROR: ", error) + + ReactGA.event({ + category: "pricing", + action: "add_card_error", + label: "", + }) + }); + } + + const cancelSubscriptions = (subscription_id) => { + const orgId = selectedOrganization.id; + const data = { + subscription_id: subscription_id, + action: "cancel", + org_id: selectedOrganization.id, + }; + + const url = globalUrl + `/api/v1/orgs/${orgId}/cancel`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then(function (response) { + if (response.status !== 200) { + console.log("Error in response"); + } + + if (handleGetOrg != undefined) { + handleGetOrg(selectedOrganization.id); + } + + return response.json(); + }) + .then(function (responseJson) { + if (responseJson.success !== undefined && responseJson.success) { + alert.success("Successfully stopped subscription!"); + } else { + alert.error("Failed stopping subscription. Please contact us."); + } + }) + .catch(function (error) { + console.log("Error: ", error); + alert.error("Failed stopping subscription. Please contact us."); + }); + }; + + const SubscriptionObject = (props) => { + const { globalUrl, userdata, serverside, billingInfo, stripeKey, selectedOrganization, handleGetOrg, subscription, } = props; + + console.log("Sub: ", subscription) + var top_text = "Base Access" + if (subscription.limit === undefined && subscription.level !== undefined) { + + subscription.name = "Enterprise" + subscription.currency_text = "$" + subscription.price = subscription.level*180 + subscription.limit = subscription.level*100000 + subscription.interval = subscription.recurrence + subscription.features = [ + "Includes " + subscription.limit + " app runs/month. ", + "Multi-Tenancy and Region-Selection", + "And all other features from /pricing", + ] + } + + if (subscription.name === "Enterprise" && subscription.active === true) { + top_text = "Current Plan" + } + + return ( + +
+ + {top_text} + +
+ +
+ + {subscription.name} + +
+ + {subscription.currency_text}{subscription.price} + + + / {subscription.interval} + +
+ + Features + +
    + {subscription.features !== undefined && subscription.features !== null ? + subscription.features.map((feature, index) => { + return ( +
  • + + {feature} + +
  • + ) + }) + : null} +
+
+ {/*subscription.name === "Pay as you go" && subscription.limit <= 10000 ? + + + You are not subscribed to any plan and are using the free plan with max 10,000 apps per month. Activate billing to de-activate this limit. + + + + : null*/} +
+ ) + } + + + return ( +
+ + Billing + + + We use Stripe to manage subscriptions and do not store any of your billing information. You can manage your subscription and billing information below. + +
+ {billingInfo.subscription !== undefined && billingInfo.subscription !== null ? + + : null} + {isCloud && + selectedOrganization.subscriptions !== undefined && + selectedOrganization.subscriptions !== null && + selectedOrganization.subscriptions.length > 0 ? + + selectedOrganization.subscriptions + .reverse() + .map((sub, index) => { + return ( + + ) + }) + : null} + {/* + + + Quantity: {sub.level} +
+ Recurrence: {sub.recurrence} +
+ {sub.active ? ( +
+ Started:{" "} + {new Date(sub.startdate * 1000).toISOString()} +
+ +
+ ) : ( +
+ Cancelled:{" "} + {new Date( + sub.cancellationdate * 1000 + ).toISOString()} +
+ + Status: Deactivated + +
+ )} + + + */} +
+
+ ) +} + +export default Billing; \ No newline at end of file diff --git a/frontend/src/components/Dropzone.js b/frontend/src/components/Dropzone.jsx similarity index 100% rename from frontend/src/components/Dropzone.js rename to frontend/src/components/Dropzone.jsx diff --git a/frontend/src/components/Newsletter.jsx b/frontend/src/components/Newsletter.jsx new file mode 100644 index 00000000..d4d09dad --- /dev/null +++ b/frontend/src/components/Newsletter.jsx @@ -0,0 +1,102 @@ +import React, {useState} from 'react'; +import { useTheme } from '@material-ui/core/styles'; +import {isMobile} from "react-device-detect"; +import ReactGA from 'react-ga'; + +import {TextField, Typography, Button} from '@material-ui/core'; + +const Newsletter = (props) => { + const { globalUrl, } = props; + + const theme = useTheme(); + const [email, setEmail] = useState(""); + const [msg, setMsg] = useState(""); + const [buttonActive, setButtonActive] = useState(true); + const buttonStyle = {minWidth: 300, borderRadius: 30, height: 60, width: 140, margin: isMobile ? "15px auto 15px auto" : "20px 20px 20px 10px", fontSize: 18,} + + const newsletterSignup = (inemail) => { + if (inemail.length < 4) { + setMsg("Invalid email") + setButtonActive(true) + return + } + + setButtonActive(false) + const data = {"email": inemail} + const url = globalUrl+'/api/v1/functions/newsletter_signup' + fetch(url, { + method: 'POST', + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + setButtonActive(true) + setMsg(responseJson["reason"]) + if (responseJson["success"] === false) { + } else { + setEmail("") + } + }), + ) + .catch(error => { + setMsg("Something went wrong: ", error.toString()) + setButtonActive(true) + }); + } + + return ( +
+ + Security Automation Newsletter + + + Defensive security is 99% noise. Join us to sift through it. + +
+ { + setEmail(e.target.value) + }} + placeholder="Your email" + id="standard-required" + margin="normal" + variant="outlined" + /> +
+ +
+ {msg} +
+ ) +} + + +export default Newsletter; \ No newline at end of file diff --git a/frontend/src/components/OrgHeaderexpanded.jsx b/frontend/src/components/OrgHeaderexpanded.jsx new file mode 100644 index 00000000..f2691e54 --- /dev/null +++ b/frontend/src/components/OrgHeaderexpanded.jsx @@ -0,0 +1,702 @@ +import React, { useEffect } from "react"; + +import { makeStyles } from "@material-ui/styles"; +import { useTheme } from "@material-ui/core/styles"; +import { useAlert } from "react-alert"; + +import { + FormControl, + InputLabel, + Paper, + OutlinedInput, + Checkbox, + Card, + Tooltip, + FormControlLabel, + Typography, + Switch, + Select, + MenuItem, + Divider, + TextField, + Button, + Tabs, + Tab, + Grid, +} from "@material-ui/core"; + +import IconButton from "@material-ui/core/IconButton"; +import ExpandLessIcon from "@material-ui/icons/ExpandLess"; +import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; +import SaveIcon from "@material-ui/icons/Save"; + +const useStyles = makeStyles({ + notchedOutline: { + borderColor: "#f85a3e !important", + }, +}); + +const OrgHeaderexpanded = (props) => { + const { + userdata, + selectedOrganization, + setSelectedOrganization, + globalUrl, + isCloud, + adminTab, + } = props; + + const theme = useTheme(); + const alert = useAlert(); + const classes = useStyles(); + const defaultBranch = "master"; + + const [orgName, setOrgName] = React.useState(selectedOrganization.name); + const [orgDescription, setOrgDescription] = React.useState( + selectedOrganization.description + ); + + const [appDownloadUrl, setAppDownloadUrl] = React.useState( + selectedOrganization.defaults === undefined + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.app_download_repo === undefined || + selectedOrganization.defaults.app_download_repo.length === 0 + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.app_download_repo + ); + const [appDownloadBranch, setAppDownloadBranch] = React.useState( + selectedOrganization.defaults === undefined + ? defaultBranch + : selectedOrganization.defaults.app_download_branch === undefined || + selectedOrganization.defaults.app_download_branch.length === 0 + ? defaultBranch + : selectedOrganization.defaults.app_download_branch + ); + const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState( + selectedOrganization.defaults === undefined + ? "https://github.com/frikky/shuffle-apps" + : selectedOrganization.defaults.workflow_download_repo === undefined || + selectedOrganization.defaults.workflow_download_repo.length === 0 + ? "https://github.com/frikky/shuffle-workflows" + : selectedOrganization.defaults.workflow_download_repo + ); + const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState( + selectedOrganization.defaults === undefined + ? defaultBranch + : selectedOrganization.defaults.workflow_download_branch === undefined || + selectedOrganization.defaults.workflow_download_branch.length === 0 + ? defaultBranch + : selectedOrganization.defaults.workflow_download_branch + ); + const [ssoEntrypoint, setSsoEntrypoint] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.sso_entrypoint === undefined || + selectedOrganization.sso_config.sso_entrypoint.length === 0 + ? "" + : selectedOrganization.sso_config.sso_entrypoint + ); + const [ssoCertificate, setSsoCertificate] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.sso_certificate === undefined || + selectedOrganization.sso_config.sso_certificate.length === 0 + ? "" + : selectedOrganization.sso_config.sso_certificate + ); + const [notificationWorkflow, setNotificationWorkflow] = React.useState( + selectedOrganization.defaults === undefined + ? "" + : selectedOrganization.defaults.notification_workflow === undefined || + selectedOrganization.defaults.notification_workflow.length === 0 + ? "" + : selectedOrganization.defaults.notification_workflow + ); + + const [documentationReference, setDocumentationReference] = React.useState( + selectedOrganization.defaults === undefined + ? "" + : selectedOrganization.defaults.documentation_reference === undefined || + selectedOrganization.defaults.documentation_reference.length === 0 + ? "" + : selectedOrganization.defaults.documentation_reference + ); + const [openidClientId, setOpenidClientId] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.client_id === undefined || + selectedOrganization.sso_config.client_id.length === 0 + ? "" + : selectedOrganization.sso_config.client_id + ); + const [openidClientSecret, setOpenidClientSecret] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.client_secret === undefined || + selectedOrganization.sso_config.client_secret.length === 0 + ? "" + : selectedOrganization.sso_config.client_secret + ); + const [openidAuthorization, setOpenidAuthorization] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.openid_authorization === undefined || + selectedOrganization.sso_config.openid_authorization.length === 0 + ? "" + : selectedOrganization.sso_config.openid_authorization + ); + const [openidToken, setOpenidToken] = React.useState( + selectedOrganization.sso_config === undefined + ? "" + : selectedOrganization.sso_config.openid_token === undefined || + selectedOrganization.sso_config.openid_token.length === 0 + ? "" + : selectedOrganization.sso_config.openid_token + ) + + const handleEditOrg = ( + name, + description, + orgId, + image, + defaults, + sso_config + ) => { + + const data = { + name: name, + description: description, + org_id: orgId, + image: image, + defaults: defaults, + sso_config: sso_config, + }; + + const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; + fetch(url, { + mode: "cors", + method: "POST", + body: JSON.stringify(data), + credentials: "include", + crossDomain: true, + withCredentials: true, + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }) + .then((response) => + response.json().then((responseJson) => { + if (responseJson["success"] === false) { + alert.error("Failed updating org: ", responseJson.reason); + } else { + alert.success("Successfully edited org!"); + } + }) + ) + .catch((error) => { + alert.error("Err: " + error.toString()); + }); + }; + + const orgSaveButton = ( + + + + ); + + return ( +
+ + + + Notification Workflow ID + { + setNotificationWorkflow(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Org Documentation reference + { + setDocumentationReference(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + {isCloud ? null : + + OpenID connect + + + + Client ID + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client ID from the identity provider" + value={openidClientId} + onChange={(e) => { + setOpenidClientId(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Client Secret (optional) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The OpenID client secret - DONT use this if dealing with implicit auth / PKCE" + value={openidClientSecret} + onChange={(e) => { + setOpenidClientSecret(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + + + Authorization URL + { + setOpenidAuthorization(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + Token URL + { + setOpenidToken(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + } + {/*isCloud ? null : */} + + SAML SSO (v1.1) + + + + SSO Entrypoint (IdP) + 0 + } + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The entrypoint URL from your provider" + value={ssoEntrypoint} + onChange={(e) => { + setSsoEntrypoint(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + + SSO Certificate (X509) + { + setSsoCertificate(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + + {isCloud ? + + IdP URL for Shuffle: https://shuffler.io/api/v1/login_sso + + : null} + + {isCloud ? null : ( + + + App Download URL + { + setAppDownloadUrl(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + App Download Branch + { + setAppDownloadBranch(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + Workflow Download URL + { + setWorkflowDownloadUrl(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + {isCloud ? null : ( + + + Workflow Download Branch + { + setWorkflowDownloadBranch(e.target.value); + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style: { + color: "white", + }, + }} + /> + + + )} + +
+ {orgSaveButton} +
+ {/* + + {expanded ? + + : + + } + + */} +
+
+ ) +} + +export default OrgHeaderexpanded; \ No newline at end of file diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index d9448bf8..3e4097ab 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -134,7 +134,7 @@ import ParsedAction from "../components/ParsedAction.jsx"; import PaperComponent from "../components/PaperComponent.jsx" import ExtraApps from "../components/ExtraApps.jsx" import EditWorkflow from "../components/EditWorkflow.jsx" -import AppStats from "../components/AppStats.jsx"; +// import AppStats from "../components/AppStats.jsx"; const surfaceColor = "#27292D"; const inputColor = "#383B40"; diff --git a/frontend/src/views/Faq.jsx b/frontend/src/views/Faq.jsx new file mode 100644 index 00000000..42d24261 --- /dev/null +++ b/frontend/src/views/Faq.jsx @@ -0,0 +1,258 @@ +import React, {useState} from 'react'; +import {isMobile} from "react-device-detect"; +import {Link} from 'react-router-dom'; + +import {Divider, List, ListItem, ListItemText, Card, CardContent, Grid, Typography, Button, ButtonGroup, FormControl, Dialog, DialogTitle, DialogActions, DialogContent, Tooltip} from '@material-ui/core'; +import {ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon} from '@material-ui/icons'; + +const hrefStyle = { + textDecoration: "none", + color: "#f85a3e" +} + + +/* + * More questions: + * What happens with IPv6 vs IPv6? + * How long can contracts be? + * Any discount? 20% with 1 year+ + * How can we pay? Manual or not + * How is support handled? + * How big is the team EXACTLY? + * What are requirements for everything? + * What level of support does Fredrik/Shuffle provide to paying customers with enterprise license agreements? + * What’s their guaranteed response time? 2 hours, 4 hours, next business day? Support 365/24/7, or just weekdays? + * How can customers submit support requests? Email, phone, and/or web? + * Is there a support team, or is Fredrik the only support person right now? + * What’s the annual Shuffle release schedule / frequency? One major release once a year with minor releases quarterly? + * The ability to run our own, private instance of Shuffle in a public or private cloud, as well as on virtualized or bare metal, standalone/isolated servers is very important. + * We were wondering how the shuffle environment handles a playbook in production(workflow editing and testing phase) vs. in operations (playbook/workflow is operational in a SOC). + * Is Shuffle capable of pushing notifications/messages to REDPro if a playbook is Active, Inactive or in Error so it’s general status can be understood via the Playbook Library. + * Will cloud webhooks behave any differently from on premise webhooks if we are hosting our own cloud. + * If we are hosting on our own cloud and the cloud is not connected to the open internet, will there a be a work around for delivering app updates. + * What other maintenance and troubleshooting considerations should we be aware of in an isolated cloud environment + * Do you have any documentation for putting workflows into a github. + */ + + +export const pricingFaq = [ + { + "question": "What currency are your prices in?", + "answers": [ + "They are in US Dollars.", + ], + }, + { + "question": "Do you offer discounts or free trials?", + "answers": [ + "We offer free trials, and may offer discounts and features for testing in certain scenarios.", + ], + }, + { + "question": "What payment methods do you offer?", + "answers": [ + "We accept credit cards, Apple Pay, Google Pay and any other payment Stripe supports.", + ], + }, + { + "question": "How can I switch to annual billing?", + "answers": [ + "Contact us at Contact page!", + ], + }, + { + "question": "When does my membership get activated?", + "answers": [ + "As soon as the payment is finished, you should see more features available in the Admin view.", + ], + }, + { + "question": "How can I switch my plan?", + "answers": [ + "Contact us at Contact page!", + ], + }, + { + "question": "What happens after payment is finished?", + "answers": [ + "We will automatically and immediately apply all the featuers to your organization.", + ], + }, + { + "question": "How can I cancel my plan?", + "answers": [ + "As an Admin of your organization, you can manage it from the Admin page.", + ], + }, + { + "question": "What is your refund policy?", + "answers": [ + "For monthly and yearly subscriptions, you have 48 hours after the transaction to request a refund. Note that we reserve the right to decline requests if we detect high activity on your account within this time." , + ], + }, + { + "question": "Do you offer support?", + "answers": [ + "Yes! We offer priority support with an SLA to our enterprise customers, and will answer any questions directed our way on the Contact page otherwise." , + ], + }, + { + "question": "Can you help me automate my operations?", + "answers": [ + "Yes! We offer support with setup, configuration, automation and app creation. This can be bought as an addition withour needing a subscription.", + ], + } +] + + +export const faqData = [ + { + "question": "What is Niceable? What’s your mission?", + "answers": [ + "Check out our cool About page!", + ], + }, + { + "question": "How does it work?", + "answers": [ + "We've got you covered!", + ], + }, + { + "question": "When will winners be announced?", + "answers": [ + "When the prizedraw's 'ticket threshold' is reached, all contributors will receive an email notification about when the live announcement of the winners—the prize winner and the winning charity—will take place. In general, the live announcement happens within 48 hours of the email notification being sent." + ], + }, + { + "question": "How much goes to charity?", + "answers": [ + "All prizedraws are guaranteed to give the majority of user contributions—more than 50%—to the winning charity. Individual prizedraw hosts (ie, prize vendors) may choose to take a smaller amount for themselves and give a larger percentage to the winning charity. In any case, we are the only prizedraw hosting platform that guarantees that the majority goes to charity. It’s the right thing to do." + ], + }, + { + "question": "How are winning charities selected?", + "answers": [ + "Charities are selected through a voting process that happens separately for each prizedraw. The community of contributors for a given prizedraw use our voting system to determine the best destination for their crowdsourced contribution. The current vote distribution can be seen on each prizedraw page’s charity leaderboard.", + ], + }, + { + "question": "How are the charitable options chosen?", + "answers": [ + "All of the charities that users can vote for have been selected based on them receiving top ratings from the most respected “charity evaluator” organizations. These assessments focus on transparency and financial optimization as well as the nature of their mission and demonstrated impact of their activities. Ultimately, however, YOUR assessment matters most. So, discuss with our community and then decide for yourself!", + "If you’d like to recommend a charity or you are part of a charity that’s interested in being featured on our site, please let us know here (adam@niceable.co).", + "In the future, additional charities will be added as options with the least voted for charities being replaced. That way, all of our charitable options will be ones that have been top rated by charity evaluator organizations and top vote getters from our wise and beloved Niceable users.", + "We are also working on adding lots of information and statistics about each charity to our site, something that our charitable partners are helping us with.", + ], + }, + { + "question": "Can I “write-off” my contribution on my taxes?", + "answers": [ + "That depends on where you live. We do not claim to be tax experts and do not offer any advice on such matters. Basically, in some places, you can. In others, you can't. Check with a licensed tax expert in your area.", + ], + }, + { + "question": "Can I buy prizedraw prizes directly?", + "answers": [ + "We encourage users to check out prizedraw hosts, many of whom promote our prizedraws and charitable partnerships through social media. They offer prizes because they want to support great charities and offer products and experiences to people who may not always have the money to buy their products directly. Making super nice(able) things accessible to you and everyone else is a major part of our mission and they help us do that.", + "The current constraints of capitalism are BS and we're out to change that. Thanks for being a hero! Our prizedraw hosts are reaching out to you and--unlike almost all other organizations--trust YOU to choose the most-worthy charity to support. So, we certainly encourage you to check out their other offerings. They are helping all of you make the impact that YOU want to make and may offer something super nice(able) that's also a perfect fit for you.", + ], + }, + { + "question": "How do I enter a promocode?", + "answers": [ + "If its your first time visiting us, you can do it in a Raffle on the right hand side. If you are already logged in, click the 'My account' button in the upper right corner of the screen. Then click the 'enter a promotional code, before submitting the code you have.", + "You should now have received more entries!", + ], + }, + { + "question": "How do you select your vendors?", + "answers": [ + "Currently, our #1 priority is learning more about YOU. What do our users want? What prizes, charities, site features and technology, support, etc.? Therefore, we are currently trying to maximize the diversity of our prizes to see what YOU value most. It’s about you, not us or our vendors.", + "Do you most value products and experiences that are ethically-produced? Crazy expensive? Mid-priced? Rare or one-of-a-kind? Created by independent vendors like artists and craftspeople? By everyday people offering services customized for you and you alone? Luxury brands? Houses? Vacations? Cutting-edge technology? Whatever you want, we’ll work hard to offer it. We believe that EVERYONE should be able to have super nice(able) things!", + "We are, however, limiting the number of active prizedraws that we have to ensure that these prizedraws fill up quickly, allowing prize winners and winning charities to enjoy their winnings sooner. In the future, we plan to offer many more prizedraws at one time.", + ], + }, + { + "question": "Can I host a prizedraw so that I can make some money, support great charities, and reach new audiences?", + "answers": [ + "Contact us here (adam@niceable.co)", + ], + }, + { + "question": "Can I host a prizedraw and donate the prize (because I’m a super nice person)?", + "answers": [ + "Contact us here (adam@niceable.co)", + ], + } + ] + +const Faq = (props) => { + const { theme } = props; + + // Hahah, this is a hack fml + const HandleAnswer = (props) => { + const [answers, setAnswers] = useState(""); + const current = props.current + + const loadAnswers = () => { + if (answers === "") { + const data = current.answers.map(answer => { + return answer + }) + + setAnswers(data.join("
")) + } else { + setAnswers("") + } + } + + const icon = answers === "" ? : + + + return ( + loadAnswers()} style={{textAlign: "center"}}> +
{icon}
+ {current.question} secondary=/> +
+ ) + + } + + const width = isMobile ? "100%" : 1000 + const FAQ = +
+ + Frequently asked questions + + + {pricingFaq.map((current) => { + return ( + + + + + ) + })} + + {/* + + + Thanks for reading! Have a super nice(able) time entering prizedraws for amazing prizes, enjoying our awesome community, and making the impact that YOU want to make in the world! + + */} +
+ + const landingpageData = +
+ {FAQ} +
+ + return ( +
+ {landingpageData} +
+ ) +} + +export default Faq; \ No newline at end of file diff --git a/frontend/src/views/HandlePaymentNew.jsx b/frontend/src/views/HandlePaymentNew.jsx new file mode 100644 index 00000000..be41aa11 --- /dev/null +++ b/frontend/src/views/HandlePaymentNew.jsx @@ -0,0 +1,1548 @@ +import React, { useState, useEffect } from 'react'; + +import ReactGA from 'react-ga'; +import { useNavigate, Link } from "react-router-dom"; +import {isMobile} from "react-device-detect"; + +import { + Done as DoneIcon, + Clear as ClearIcon, + AddTask as AddTaskIcon, + } from '@mui/icons-material'; + +import { + Slider, + Divider, + List, + ListItem, + ListItemText, + Card, + CardContent, + Grid, + Typography, + Button, + ButtonGroup, + FormControl, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + Tooltip +} from '@material-ui/core'; +import FAQ from "./Faq.jsx"; +import Newsletter from "../components/Newsletter.jsx"; +import Services from "./Services.jsx"; + +export const typecost = 0.0018 +export const typecost_single = (typecost * 1.33).toFixed(4) + +// 1. Create 2-3 payment tiers (slider?) +// 2. Create a way to show them anywhere +// +// Site references: +// https://logz.io/pricing/ +// https://www.avanan.com/pricing +const PaymentField = (props) => { + const { maxFields, theme, removeAdditions, isLoggedIn } = props + + const isCloud = + window.location.host === "localhost:3002" || + window.location.host === "shuffler.io"; + + // Multiple unused variables here + let navigate = useNavigate(); + const parsedFields = maxFields === undefined ? 300 : maxFields + const [variant, setVariant] = useState(0) + const [shuffleVariant, setShuffleVariant] = useState(isCloud ? 0 : 1) + const [paymentType, setPaymentType] = useState(0) + const [modalOpen, setModalOpen] = useState(false) + const [showPricing, ] = useState(true) + const [currentPrice, setCurrentPrice] = useState(129) + const [isLoaded, setIsLoaded] = useState(false) + const [errorMessage, setErrorMessage] = useState("") + + // Cloud + const [calculatedApps, setCalculatedApps] = useState(600) + const [calculatedCost, setCalculatedCost] = useState("$600") + const [selectedValue, setSelectedValue] = useState(100) + + // Onprem + const [calculatedCores, setCalculatedCores] = useState(600) + const [onpremSelectedValue, setOnpremSelectedValue] = useState(8) + + useEffect(() => { + console.log("New variant: ", shuffleVariant) + + if (shuffleVariant === 1) { + setCalculatedCost("$600") + setSelectedValue(8) + } else { + setCalculatedCost("$180") + setSelectedValue(100) + } + }, [shuffleVariant]) + + if (typeof window === 'undefined' || window.location === undefined) { + return null + } + + + /* + const valuetext = (value, variant) => { + console.log("Valuetext: ", value, variant) + if (value === 32 || value === 1000) { + if (variant === 1) { + setCalculatedCores("Get A Quote") + setOnpremSelectedValue(value) + } else { + setCalculatedApps(`Get A Quote`) + setSelectedValue(value) + } + } else { + if (variant === 1) { + setCalculatedCores(`$${value*75}`) + setOnpremSelectedValue(value) + } else { + setCalculatedApps(`$${value*1000*0.0018}`) + setSelectedValue(value) + } + } + } + */ + + + const handleChange = (event, newValue) => { + console.log("Event, value: ", event.target, newValue) + + if (shuffleVariant === 1) { + setSelectedValue(newValue) + if (newValue === 32) { + setCalculatedCost(`Get A Quote`) + } else { + setCalculatedCost(`$${newValue*75}`) + } + } else { + setSelectedValue(newValue) + if (newValue === 1000) { + setCalculatedCost(`Get A Quote`) + } else { + setCalculatedCost(`$${newValue*1000*typecost}`) + } + } + } + + + // return `${value}`; + //} + + if (!isLoaded) { + setIsLoaded(true) + + const tmpsearch = typeof window === 'undefined' || window.location === undefined ? "" : window.location.search + const tmpVar = new URLSearchParams(tmpsearch).get("variant") + if (tmpVar !== undefined && tmpVar !== null && tmpVar < 3) { + setVariant(parseInt(tmpVar)) + } + + const tmpType = new URLSearchParams(tmpsearch).get("payment_type") + if (tmpType !== undefined && tmpType !== null && tmpType < 2) { + setPaymentType(parseInt(tmpType)) + } + + const modal = new URLSearchParams(tmpsearch).get("payment_modal") + if (modal !== undefined && modal !== null && modal === "open") { + setModalOpen(true) + } + + const tmpView = new URLSearchParams(tmpsearch).get("view") + if (tmpView !== undefined && tmpView !== null && tmpView === "failure") { + setErrorMessage("Something went wrong with your payment. Please try again.") + } + + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + const foundTab = params["tab"]; + if (foundTab !== null && foundTab !== undefined) { + if (foundTab === "onprem") { + //valuetext(8, 1) + setShuffleVariant(1) + //setCalculatedCores(`$${8*75}`) + } + } + } + + const billingInfo = Billed anually or monthly at 1.2x the cost + //const skipFreemode = window.location.pathname.startsWith("/admin") + const skipFreemode = false + const maxwidth = isMobile ? "91%" : skipFreemode ? 1100 : 1200 + const activeIcon = + const inActiveIcon = + + // All triggers + const features = [ + + { + "name": "Users", + "basic": "No limit", + "community": "No limit", + "pro": "No limit", + "enterprise": "", + "active": true, + }, + { + "name": "Apps", + "basic": "No limit", + "community": "No limit", + "pro": "No limit", + "enterprise": "", + "active": true, + }, + { + "name": "Workflows", + "basic": "No limit", + "community": "No limit", + "pro": "No limit", + "enterprise": "", + "active": true, + }, + { + "name": "Workflow App Runs", + "basic": "10.000 / month", + "community": "10.000 / month", + "pro": "Pay as you go", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "Workflow App Runs", + "basic": "No limit", + "community": "No limit", + "pro": "No limit", + "enterprise": "", + "active": true, + "cloud": false, + }, + { + "name": "Shuffle Datastore (cache)", + "basic": "Max 1GB", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "Shuffle Datastore (cache)", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "cloud": false, + }, + { + "name": "File Storage", + "basic": "Max 1GB", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "File Storage", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "cloud": false, + }, + { + "name": "Multi-Tenant", + "basic": "No", + "community": "Yes", + "pro": "Add-on", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "Multi-Tenant", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "cloud": false, + }, + { + "name": "Region Control", + "basic": "No", + "community": "Yes", + "pro": "Add-on", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "Per-CPU-core support", + "basic": "0 / month", + "community": "0 / month", + "pro": "Pay as you go", + "enterprise": "8 / month", + "active": true, + "cloud": false, + }, + { + "name": "Shuffle SMS alerting", + "basic": "30 / month", + "community": "Yes", + "pro": "300 / month", + "enterprise": "300 / month", + "active": true, + }, + { + "name": "Shuffle Email alerting", + "basic": "100 / month", + "community": "Yes", + "pro": "10.000 / month", + "enterprise": "", + "active": true, + }, + { + "name": "Priority Support", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": true, + "title": "Support & Success", + }, + { + "name": "Maintenance & Updates", + "basic": "No", + "community": "", + "pro": "Yes", + "enterprise": "", + "active": true, + "cloud": false, + }, + { + "name": "Documentation & Community Support", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Email & Chat Support", + "basic": "support@shuffler.io", + "community": "No", + "pro": "Prioritized + Critical issue SLA", + "enterprise": "", + "active": true, + }, + { + "name": "Personal onboarding", + "basic": "No", + "community": "", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Shuffle Academy", + "basic": "Yes", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Workflow editor", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "title": "Basic features", + }, + { + "name": "App editor", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Private Apps", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Default & Shared playbooks", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Organization control", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Autocomplete features", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Hybrid Webhook trigger", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Hybrid User Input trigger", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Hybrid Email trigger", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Hybrid Schedule", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Failure Notifications", + "basic": "Yes", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Hybrid Executions", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Use of Public Workflows", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Multiple Environments", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + + { + "name": "Shuffle creates integration", + "basic": "No", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Uptime SLA", + "basic": "No", + "community": "No", + "pro": "99.9%", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "Automated backups", + "basic": "No", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "MSSP org overview", + "basic": "No", + "community": "No", + "pro": "Add-on", + "enterprise": "", + "active": true, + }, + { + "name": "MSSP org control", + "basic": "No", + "community": "No", + "pro": "Add-on", + "enterprise": "", + "active": true, + }, + { + "name": "Automatic Platform updates", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "Audit logging", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "2-factor authentication", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "title": "Security & Development", + }, + { + "name": "SAML / SSO", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "API-key management", + "basic": "yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Role-based access control (RBAC)", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + + { + "name": "Workflow recommendations", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + "title": "Additional Features", + }, + { + "name": "Standardized app categories", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "App Framework", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Hybrid search engine", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + + { + "name": "Hybrid App syncronization", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": true, + }, + { + "name": "Execution Retention", + "basic": "1 Month", + "community": "Yes", + "pro": "1 Year default", + "enterprise": "", + "active": true, + "onprem": false, + }, + { + "name": "Detection Management", + "basic": "Yes", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Mitre Att&ck integrations", + "basic": "No", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Open Source account rollback", + "basic": "No", + "community": "Yes", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Data LOCATION control", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Data RETENTION control", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Shuffle IoC search", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Controllable Reporting", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Management dashboard", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Risk based overview", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "Compliance dashboard", + "basic": "No", + "community": "No", + "pro": "Yes", + "enterprise": "", + "active": false, + }, + { + "name": "App & Workflow Training", + "basic": "$4999 / 5 people", + "community": "No", + "pro": "$1499 / 5 people", + "enterprise": "", + "active": true, + "title": "Professional Services", + }, + { + "name": "Developer Training", + "basic": "$4999 / 5 people", + "community": "No", + "pro": "$1499 / 5 people", + "enterprise": "", + "active": true, + }, + { + "name": "Custom App Development", + "basic": "Contact us", + "community": "No", + "pro": "Contact us", + "enterprise": "", + "active": true, + }, + { + "name": "Custom Workflow Development", + "basic": "Contact us", + "community": "No", + "pro": "Contact us", + "enterprise": "", + "active": true, + }, + { + "name": "Shuffle Custom Modifications", + "basic": "Contact us", + "community": "No", + "pro": "Contact us", + "enterprise": "", + "active": true, + }, + + ] + + const defaultTaskIcon = + const stripe = typeof window === 'undefined' || window.location === undefined ? "" : props.stripeKey === undefined ? "" : window.Stripe ? window.Stripe(props.stripeKey) : "" + const handleStripeRedirect = (payment_type, recurrence) => { + console.log("REDIRECT: ", payment_type, recurrence) + + // FIXME: Proper redirect cycle here + if (props.userdata === undefined || props.userdata.username === undefined || props.userdata.active_org === undefined || props.userdata.active_org.id === undefined) { + console.log("User must sign in and have an organization first. Current: ", props.userdata) + // 1. Add query parameters: Yearly / monthly, Community / pro + navigate(`/register?view=pricing&variant=${variant}&payment_type=${paymentType}&payment_modal=open&message=You need to create a user to continue`) + return + } else { + console.log("Username is ", props.userdata.username) + } + + //payment_type = community(0), pro(1) + //recurrence = yearly(0), monthly(1) + var priceItem = "price_1Hh8ecDzMUgUjxHSPEdeueyu" + var text = "enterprise_yearly_pay_click" + // recurrence = 0 = yearly + // recurrence = 1 = monthly + // + if (payment_type === 0) { + console.log("Handling payment type 0: hybrid") + priceItem = recurrence === 0 ? isCloud ? "price_1HhAOgDzMUgUjxHSmesUZkNU" : "price_1HhAOgDzMUgUjxHSmesUZkNU" : isCloud ? "price_1HhAOgDzMUgUjxHSfU8XzQ84" : "price_1HhAOgDzMUgUjxHSfU8XzQ84" + + ReactGA.event({ + category: "pricing", + action: `hybrid_pay_click`, + label: "", + }) + } else if (payment_type === 1) { + console.log("Handling payment type 1: enterprise") + priceItem = recurrence === 0 ? isCloud ? "price_1HhAdrDzMUgUjxHSsIDOCYgm" : "price_1HhAdrDzMUgUjxHSsIDOCYgm" : isCloud ? "price_1HhAdrDzMUgUjxHS7Cu5vF95" : "price_1HhAdrDzMUgUjxHS7Cu5vF95" + + if (recurrence === 1) { + text = "enterprise_monthly_pay_click" + } + + ReactGA.event({ + category: "pricing", + action: text, + label: "", + }) + } else if (payment_type === 2) { + console.log("Handling payment type 2: basic") + priceItem = recurrence === 0 ? isCloud ? "price_1HnPmWDzMUgUjxHSzEHV5e6t" : "price_1HlvuPDzMUgUjxHS1pvtPONJ" : isCloud ? "price_1HnPmWDzMUgUjxHSGC3Yiact" : "price_1HlvuPDzMUgUjxHSrp7Ws8iu" + + ReactGA.event({ + category: "pricing", + action: `mssp_pay_click`, + label: "", + }) + } else { + console.log(`No handler for redirect ${payment_type} yet`) + return + } + + // Current URL + status = Success/fail + const successUrl = `${window.location.origin}/admin?payment=success` + const failUrl = `${window.location.origin}/pricing?view=failure&variant=${variant}&payment_type=${paymentType}` + + var checkoutObject = { + lineItems: [ + {price: priceItem, quantity: 1}, + ], + mode: "subscription", + billingAddressCollection: "auto", + successUrl: successUrl, + cancelUrl: failUrl, + submitType: "donate", + clientReferenceId: props.userdata.active_org.id, + } + + stripe.redirectToCheckout(checkoutObject) + .then(function (result) { + console.log("SUCCESS STRIPE?: ", result) + + text += "_success" + ReactGA.event({ + category: "pricing", + action: text, + label: "", + }) + }) + .catch(function(error) { + console.error("STRIPE ERROR: ", error) + text += "_fail" + ReactGA.event({ + category: "pricing", + action: text, + label: "", + }) + }); + + console.log("Done with payment!") + } + + const buttonBackground = "linear-gradient(to right, #f86a3e, #f34079)" + const level1Button = + + + const level2Button = + + + const level3Button = skipFreemode ? null : + + + + const cardStyle = { + height: "100%", + width: "100%", + textAlign: "center", + backgroundColor: theme.palette.surfaceColor, + color: "white", + } + + + var indexskip = 0 + const topRet = +
+ Pricing + {/*Find pricing, focused on shuffler.io and self-hosted*/} + {/*These prices are likely to change*/} +
+ + + + +
+ {errorMessage.length > 0 ? Error: {errorMessage} : null} +
+ + {skipFreemode ? null : + + + + Free + + {shuffleVariant === 0 ? + "shuffler.io / Cloud" + : + Open Source + } + + {paymentType === 0 ? "Free" : "Free"} + + {shuffleVariant === 0 ? + "Includes 10k App Executions. Refreshes every month." + : + "Unlimited use, self-hosted." + } + + + + + {defaultTaskIcon} Use any app + + + + {defaultTaskIcon} Unlimited users + + + + {defaultTaskIcon} Unlimited workflows + + + + + {defaultTaskIcon} { + if (window.drift !== undefined) { + window.drift.api.startInteraction({ interactionId: 340043, }) + } else { + console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) + } + }}>Free Support & Discord access + + +
+ {level3Button} + + + + } + + + + + {shuffleVariant === 1 ? "Scale" : "Enterprise"} + + {shuffleVariant === 0 ? + "SaaS / Cloud" + : + "Open Source + Scale License" + } + + + { + if (calculatedCores === "Get A Quote") { + console.log("Clicked on get a quote") + if (window.drift !== undefined) { + window.drift.api.startInteraction({ interactionId: 340785 }) + } + } + }}>{calculatedCost} + Per month for {shuffleVariant === 1 ? `${selectedValue} CPU cores` : `${selectedValue}k App Executions`}: +
+ + { + handleChange(event, newValue) + }} + marks + value={selectedValue} + step={shuffleVariant === 0 ? 100 : 4} + min={shuffleVariant === 0 ? 100 : 8} + max={shuffleVariant === 0 ? 1000 : 32} + valueLabelDisplay="auto" + /> +
+ + + + {defaultTaskIcon} Priority Support + + + + {defaultTaskIcon} {shuffleVariant === 0 ? + "Multi-Tenant" + : + "Scalable Orborus" + } + + + {shuffleVariant === 0 ? + + {defaultTaskIcon} Multi-Region Tenants + + : + + {defaultTaskIcon} High Availability + + } + + + {defaultTaskIcon} Help with Workflow and App development + + +
+ {/*billingInfo*/} + + + {level2Button} + {shuffleVariant === 0 ? + + : + null} + + {/* + + { + if (window.drift !== undefined) { + window.drift.api.startInteraction({ interactionId: 340785 }) + } else { + console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift) + } + }}> + Or get a quote + + + */} + + + + + {/*shuffleVariant === 1 ? null : + + + + MSSP + Cross-Customer automation + ${paymentType === 0 ? 1999 : 2399} + Per month{paymentType === 0 ? ", billed yearly" : null} + + - All previous tiers + + + - Extra Customer control + + + - Sub-organization access + + + - Build Shuffle into your product + +
+ + + + + */} + +
+ +
+ + {shuffleVariant === 0 ? + "- 100k Executions per month can handle about 500 Assets, and scales linearly." + : + "- 8 CPU-cores (default) can handle about 1500 Assets and scales linearly." + } +
+ - All prices are in USD and exclude VAT + + {/* + + Shuffle is an Open Source project. Gives access to support, development and features not otherwise available. This applies to both Open Source & Cloud/SaaS. After the transaction is finished, you will immediately have full access to our support team, and you organization will automatically get upgraded resources assigned. + + */} + + + Pay-as-you-go-pricing + Simple usage based pricing with no long-term commitments + + + Volume Discounts + Discounts trigger as your usage grows, so you always get a fair price. + + + Committed-use discounts + Get additional discounts for annual or multi-year commitments + + + {!showPricing ? + null : +
+ Features ({shuffleVariant === 0 ? "Cloud" : "Self-Hosted"}) + + + + {isMobile ? "F" : "Free"} + style={{ textAlign: "left", flex: 2}} + /> + {/*{isMobile ? "H" : "Hybrid"} + style={{ textAlign: "left", flex: 2}} + />*/} + {isMobile ? "E" : "Enterprise / Scale"} + style={{ textAlign: "left", flex: 2}} + /> + + {features.slice(0, parsedFields).map((data, index) => { + + //const activeData = data.active ? activeIcon : data.basic === "No" || data.basic === false ? inActiveIcon : data.basic + const basicData = data.basic === "Yes" || data.basic === true ? activeIcon : data.basic === "No" || data.basic === false ? inActiveIcon : data.basic + const communityData = data.community === "Yes" || data.community === true ? activeIcon : data.community === "No" || data.community === false ? inActiveIcon : data.community + const proData = data.pro === "Yes" || data.pro === true ? activeIcon : data.pro === "No" || data.pro === false ? inActiveIcon : data.pro + + if (shuffleVariant === 0 && data.cloud === false) { + indexskip += 1 + return null + } + + if (shuffleVariant === 1 && data.onprem === false) { + indexskip += 1 + return null + } + + const newindex = index-indexskip + + return ( + + {data.title !== undefined ? + + {data.title} + + : null} + + {data.active === true ? + + : + + + + } + + {/* + + */} + + + + ) + })} + {features.length > parsedFields ? + + + + : null + } + {/*isMobile ? null : +
+ {level3Button} + + {level1Button} + + {level2Button} +
+ */} + {/* + + + + + + */} + +
+
+ } +
+ Scalable models for MSSPs + + Need support and automation help for diverse and scalable environments? Our Enterprise and MSSP offerings can help you whether Onprem or in our cloud. + + + + + + Open Source + CPU-core-based + 8 cores included in sub + $75 /  + CPU-core + + Per month + + + + + + + + Cloud + App-Execution based + Pay-as-you-go + ${typecost_single} /  + app-runs + Per month + + + + + + Cloud, Hybrid & Onprem + + Our support model is built for both the Cloud and Onpremises version of Shuffle, and can be managed between both. Contact us for more info, or to get a quote from one of our verified resellers. + + Shuffle MSSP and Open Source + +
+ {removeAdditions === true ? null : + + + +
+ + Got other questions? + + + If you got more questions about our pricing and plans, please contact us so we can help + + +
+
+ } + +
+ + const setMonthlyCost = (variant, paymentType) => { + setErrorMessage("") + if (variant === 0 && paymentType === 0) { + setCurrentPrice(129) + } else if (variant === 0 && paymentType === 1) { + setCurrentPrice(155) + } else if (variant === 1 && paymentType === 0) { + setCurrentPrice(999) + } else if (variant === 1 && paymentType === 1) { + setCurrentPrice(1199) + } else if (variant === 2 && paymentType === 0) { + setCurrentPrice(15) + } else if (variant === 2 && paymentType === 1) { + setCurrentPrice(18) + } + } + + const modalView = modalOpen ? + { + setModalOpen(false) + ReactGA.event({ + category: "pricing", + action: `close_window_outside_click`, + label: "", + }) + }} + PaperProps={{ + style: { + backgroundColor: "#1f2023", + color: "white", + minWidth: isMobile ? "100%" :500, + padding: 30, + }, + }} + > + +
Shuffle payments
+ + + Choose recurrence + + + + + +
+ + + + Your plan: + + + Shuffle {variant === 0 ? "Community" : variant === 2 ? "Free" : "Pro"} Edition + + +
+ + Monthly subtotal: + + + ${currentPrice} + +
+
+ + Discount: + + + {paymentType === 0 ? "20%" : "0%"} + +
+
+ + Beta opt-in: + + + Extra features + +
+ +
+ + What you'll pay now: + + + ${paymentType === 0 ? currentPrice*12 : currentPrice} + +
+
+
+ + + + + + Your plan will renew each {paymentType === 0 ? "year" : "month"}. + + {/**/} + + + + + +
+ : null + + return ( +
+ {topRet} + {modalView} +
+ ) +} + +export default PaymentField; \ No newline at end of file diff --git a/frontend/src/views/Services.jsx b/frontend/src/views/Services.jsx new file mode 100644 index 00000000..c8f8e7cc --- /dev/null +++ b/frontend/src/views/Services.jsx @@ -0,0 +1,203 @@ +import React, {useState } from 'react'; + +import ReactGA from 'react-ga'; +import { useNavigate, Link, useParams } from "react-router-dom"; +import {isMobile} from "react-device-detect"; + +import {Done as DoneIcon, Clear as ClearIcon} from '@material-ui/icons'; +import {Paper, Divider, List, ListItem, ListItemText, Card, CardContent, Grid, Typography, Button, ButtonGroup, FormControl, Dialog, DialogTitle, DialogActions, DialogContent, Tooltip} from '@material-ui/core'; +import FAQ from "./Faq.jsx"; +import Newsletter from "../components/Newsletter.jsx"; + +// 1. Create 2-3 payment tiers (slider?) +// 2. Create a way to show them anywhere +// +// Site references: +// https://logz.io/pricing/ +// https://www.avanan.com/pricing +const PaymentField = (props) => { + const { maxFields, theme, removeAdditions } = props + + const parsedFields = maxFields === undefined ? 300 : maxFields + const [variant, setVariant] = useState(0) + const [paymentType, setPaymentType] = useState(0) + const [modalOpen, setModalOpen] = useState(false) + const [showPricing, ] = useState(true) + const [currentPrice, setCurrentPrice] = useState(129) + const [isLoaded, setIsLoaded] = useState(false) + const [errorMessage, setErrorMessage] = useState("") + let navigate = useNavigate(); + + if (typeof window === 'undefined' || window.location === undefined) { + return null + } + + if (!isLoaded) { + setIsLoaded(true) + + const tmpsearch = typeof window === 'undefined' || window.location === undefined ? "" : window.location.search + const tmpVar = new URLSearchParams(tmpsearch).get("variant") + if (tmpVar !== undefined && tmpVar !== null && tmpVar < 3) { + setVariant(parseInt(tmpVar)) + } + + const tmpType = new URLSearchParams(tmpsearch).get("payment_type") + if (tmpType !== undefined && tmpType !== null && tmpType < 2) { + setPaymentType(parseInt(tmpType)) + } + + const modal = new URLSearchParams(tmpsearch).get("payment_modal") + if (modal !== undefined && modal !== null && modal === "open") { + setModalOpen(true) + } + + const tmpView = new URLSearchParams(tmpsearch).get("view") + if (tmpView !== undefined && tmpView !== null && tmpView === "failure") { + setErrorMessage("Something went wrong with your payment. Please try again.") + } + } + + const billingInfo = Billed anually or monthly at 1.2x the cost + const maxwidth = isMobile ? "91%" : 1024 + const activeIcon = + const inActiveIcon = + + const cardStyle = { + height: "100%", + width: "100%", + textAlign: "center", + backgroundColor: theme.palette.surfaceColor, + color: "white", + } + + const maxWidth = isMobile ? "100%" : 1000 + const margin = isMobile ? 15 : 50 + const paperWidth = isMobile ? "100%" : maxWidth-margin*2 + const listItemStyle = isMobile ? {textAlign: "center"} : {} + const topRet = +
+
+ + Our team is here to help you + + + We solve any problem that arises in a security operations center using YOUR tools. Here's how. + + +
+ + {navigate("/usecases")}} style={listItemStyle}> + + + {}} style={listItemStyle}> + + + {"https://discord.gg/B2CBzUm"}} style={listItemStyle}> + + + +
+
+ + Accessibility first + + + Shuffle was built by and for security professionals. We aim to bring our unique toolbox to every operations center globally, enabling information sharing and collaboration at scale, whether in the cloud, or on-premises. + +
+
+ +
+ + Automation Services + + + Our team of security and automation experts create workflows to automated your SOC end-to-end. In the case that apps are missing, we will create them for you and share with the community to everyones benefit. + +
+
+ + {}} style={listItemStyle}> + + + {}} style={listItemStyle}> + + + {}} style={listItemStyle}> + + + +
+
+ +
+ + {}} style={listItemStyle}> + + + {}} style={listItemStyle}> + + + {}} style={listItemStyle}> + + + +
+
+ + Support and Maintenance + + + We recognize that support is a vital part of keeping operations stable and secure. That's why we're offering support of both the cloud platform and usage, as well as for the open source version of Shuffle. + +
+
+ +
+ + Training + + + We teach you how to become a poweruser of Shuffle. It involves how to maintain, create, share and use it Shuffle, whether in a small operations center or as an MSSP. + +
+
+ + {}} style={listItemStyle}> + + + {}} style={listItemStyle}> + + + {}} style={listItemStyle}> + + + +
+
+
+
+ + + return ( +
+ {topRet} + {removeAdditions === true ? null : + + +
+ + Got other questions? + + + If you got more questions about our pricing and plans, please contact us so we can help + + +
+
+ } +
+ ) +} + +export default PaymentField; \ No newline at end of file