Loads of more bugfixes and tests
This commit is contained in:
+316
-331
@@ -72,9 +72,11 @@ import {
|
||||
} from "@material-ui/icons";
|
||||
|
||||
import { useAlert } from "react-alert";
|
||||
import Dropzone from "../components/Dropzone";
|
||||
import HandlePaymentNew from "./HandlePaymentNew";
|
||||
import Dropzone from "../components/Dropzone.jsx";
|
||||
import HandlePaymentNew from "../views/HandlePaymentNew.jsx";
|
||||
import OrgHeader from "../components/OrgHeader.jsx";
|
||||
import OrgHeaderexpanded from "../components/OrgHeaderexpanded.jsx";
|
||||
import Billing from "../components/Billing.jsx";
|
||||
import { display, style } from "@mui/system";
|
||||
|
||||
const useStyles = makeStyles({
|
||||
@@ -188,8 +190,10 @@ const Admin = (props) => {
|
||||
const [dealDiscount, setDealDiscount] = React.useState("");
|
||||
const [dealerror, setDealerror] = React.useState("");
|
||||
const [dealList, setDealList] = React.useState([]);
|
||||
const [adminTab, setAdminTab] = React.useState(1);
|
||||
|
||||
const [fileContent, setFileContent] = React.useState("");
|
||||
const [billingInfo, setBillingInfo] = React.useState({});
|
||||
|
||||
useEffect(() => {
|
||||
if (isDropzone) {
|
||||
@@ -1562,6 +1566,14 @@ const Admin = (props) => {
|
||||
5: "environments",
|
||||
6: "suborgs",
|
||||
};
|
||||
|
||||
const admin_views = {
|
||||
0: "organization",
|
||||
1: "cloud_sync",
|
||||
2: "billing",
|
||||
3: "stats",
|
||||
};
|
||||
|
||||
const setConfig = (event, inputValue) => {
|
||||
const newValue = parseInt(inputValue);
|
||||
|
||||
@@ -1612,17 +1624,29 @@ const Admin = (props) => {
|
||||
) {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const params = Object.fromEntries(urlSearchParams.entries());
|
||||
const foundTab = params["tab"];
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
for (var key in Object.keys(views)) {
|
||||
const value = views[key];
|
||||
console.log(key, value);
|
||||
if (value === foundTab) {
|
||||
setConfig("", key);
|
||||
|
||||
const adminTab = params["admin_tab"];
|
||||
if (adminTab !== null && adminTab !== undefined) {
|
||||
for (var key in Object.keys(admin_views)) {
|
||||
const value = admin_views[key];
|
||||
if (value === adminTab) {
|
||||
setAdminTab(parseInt(key));
|
||||
setConfig("", 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const foundTab = params["tab"];
|
||||
if (foundTab !== null && foundTab !== undefined) {
|
||||
for (var key in Object.keys(views)) {
|
||||
const value = views[key];
|
||||
if (value === foundTab) {
|
||||
setConfig("", key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2612,48 +2636,7 @@ const Admin = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const cancelSubscriptions = (subscription_id) => {
|
||||
console.log(selectedOrganization);
|
||||
const orgId = selectedOrganization.id;
|
||||
const data = {
|
||||
subscription_id: subscription_id,
|
||||
action: "cancel",
|
||||
org_id: selectedOrganization.id,
|
||||
};
|
||||
|
||||
const url = globalUrl + `/api/v1/orgs/${orgId}`;
|
||||
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");
|
||||
}
|
||||
|
||||
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 organizationView =
|
||||
curTab === 0 && selectedOrganization.id !== undefined ? (
|
||||
<div style={{ position: "relative" }}>
|
||||
@@ -2758,6 +2741,7 @@ const Admin = (props) => {
|
||||
setSelectedOrganization={setSelectedOrganization}
|
||||
globalUrl={globalUrl}
|
||||
selectedOrganization={selectedOrganization}
|
||||
adminTab={adminTab}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@@ -2772,6 +2756,51 @@ const Admin = (props) => {
|
||||
<Typography>Loading Organization</Typography>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Tabs
|
||||
value={adminTab}
|
||||
indicatorColor="primary"
|
||||
textColor="secondary"
|
||||
style={{marginTop: 20, }}
|
||||
onChange={(event, inputValue) => {
|
||||
const newValue = parseInt(inputValue);
|
||||
setAdminTab(newValue);
|
||||
|
||||
//const setConfig = (event, inputValue) => {
|
||||
navigate(`/admin?admin_tab=${admin_views[newValue]}`);
|
||||
}}
|
||||
aria-label="disabled tabs example"
|
||||
>
|
||||
<Tab
|
||||
label=<span>
|
||||
Edit Details
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
label=<span>
|
||||
Cloud Synchronization
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
disabled={!isCloud}
|
||||
label=<span>
|
||||
Billing
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
disabled={true}
|
||||
label=<span>
|
||||
Usage
|
||||
</span>
|
||||
/>
|
||||
<Tab
|
||||
disabled={true}
|
||||
label=<span>
|
||||
Notifications
|
||||
</span>
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
@@ -2779,205 +2808,241 @@ const Admin = (props) => {
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
variant="h6"
|
||||
style={{ marginBottom: "10px", color: "white" }}
|
||||
>
|
||||
Cloud syncronization
|
||||
</Typography>
|
||||
What does{" "}
|
||||
<a
|
||||
href="https://shuffler.io/docs/organizations#cloud_sync"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
cloud sync
|
||||
</a>{" "}
|
||||
do? Cloud syncronization is a way of getting more out of Shuffle.
|
||||
Shuffle will <b>ALWAYS</b> make every option open source, but
|
||||
features relying on other users can't be done without a
|
||||
collaborative approach.
|
||||
{isCloud ? (
|
||||
<div style={{ marginTop: 15, display: "flex" }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Typography style={{}}>
|
||||
Currently syncronizing:{" "}
|
||||
{selectedOrganization.cloud_sync_active === true
|
||||
? "True"
|
||||
: "False"}
|
||||
</Typography>
|
||||
{selectedOrganization.cloud_sync_active ? (
|
||||
<Typography style={{}}>
|
||||
Syncronization interval:{" "}
|
||||
{selectedOrganization.sync_config.interval === 0
|
||||
? "60"
|
||||
: selectedOrganization.sync_config.interval}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Typography
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
marginTop: 25,
|
||||
marginRight: 10,
|
||||
}}
|
||||
>
|
||||
Your Apikey
|
||||
</Typography>
|
||||
<div style={{ display: "flex" }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
disabled={true}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
placeholder="Cloud Apikey"
|
||||
variant="outlined"
|
||||
defaultValue={userSettings.apikey}
|
||||
/>
|
||||
{selectedOrganization.cloud_sync_active ? (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
height: 50,
|
||||
marginLeft: 10,
|
||||
marginTop: 17,
|
||||
}}
|
||||
variant={
|
||||
selectedOrganization.cloud_sync_active === true
|
||||
? "outlined"
|
||||
: "contained"
|
||||
}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
handleStopOrgSync(selectedOrganization.id);
|
||||
}}
|
||||
>
|
||||
Stop Sync
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ display: "flex", marginBottom: 20 }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginRight: 10,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
disabled={selectedOrganization.cloud_sync}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
placeholder="Cloud Apikey"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setCloudSyncApikey(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
(!selectedOrganization.cloud_sync &&
|
||||
cloudSyncApikey.length === 0) ||
|
||||
loading
|
||||
}
|
||||
style={{ marginTop: 15, height: 50, width: 150 }}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
enableCloudSync(
|
||||
cloudSyncApikey,
|
||||
selectedOrganization,
|
||||
selectedOrganization.cloud_sync
|
||||
);
|
||||
}}
|
||||
color="primary"
|
||||
variant={
|
||||
selectedOrganization.cloud_sync === true
|
||||
? "outlined"
|
||||
: "contained"
|
||||
}
|
||||
>
|
||||
{selectedOrganization.cloud_sync
|
||||
? "Stop sync"
|
||||
: "Start sync"}
|
||||
</Button>
|
||||
</div>
|
||||
{orgSyncResponse.length > 0 ? (
|
||||
<Typography style={{ marginTop: 5, marginBottom: 10 }}>
|
||||
Message from Shuffle Cloud: <b>{orgSyncResponse}</b>
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
<Typography
|
||||
style={{ marginTop: 40, marginLeft: 10, marginBottom: 5 }}
|
||||
>
|
||||
Cloud sync features (monthly usage)
|
||||
</Typography>
|
||||
<Grid container style={{ width: "100%", marginBottom: 15 }}>
|
||||
{selectedOrganization.sync_features === undefined ||
|
||||
selectedOrganization.sync_features === null
|
||||
? null
|
||||
: Object.keys(selectedOrganization.sync_features).map(function (
|
||||
key,
|
||||
index
|
||||
) {
|
||||
// unnecessary parts
|
||||
if (key === "schedule" || key === "apps" || key === "updates") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = selectedOrganization.sync_features[key];
|
||||
if (item === null) {
|
||||
return null
|
||||
}
|
||||
{adminTab === 0 ? (
|
||||
<OrgHeaderexpanded
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
setSelectedOrganization={setSelectedOrganization}
|
||||
globalUrl={globalUrl}
|
||||
selectedOrganization={selectedOrganization}
|
||||
adminTab={adminTab}
|
||||
/>
|
||||
)
|
||||
: adminTab === 1 ? (
|
||||
<div>
|
||||
<Typography
|
||||
variant="h6"
|
||||
style={{ marginBottom: "10px", color: "white" }}
|
||||
>
|
||||
Cloud syncronization
|
||||
</Typography>
|
||||
What does{" "}
|
||||
<a
|
||||
href="https://shuffler.io/docs/organizations#cloud_sync"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
>
|
||||
cloud sync
|
||||
</a>{" "}
|
||||
do? Cloud syncronization is a way of getting more out of Shuffle.
|
||||
Shuffle will <b>ALWAYS</b> make every option open source, but
|
||||
features relying on other users can't be done without a
|
||||
collaborative approach.
|
||||
{isCloud ? (
|
||||
<div style={{ marginTop: 15, display: "flex" }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Typography style={{}}>
|
||||
Currently syncronizing:{" "}
|
||||
{selectedOrganization.cloud_sync_active === true
|
||||
? "True"
|
||||
: "False"}
|
||||
</Typography>
|
||||
{selectedOrganization.cloud_sync_active ? (
|
||||
<Typography style={{}}>
|
||||
Syncronization interval:{" "}
|
||||
{selectedOrganization.sync_config.interval === 0
|
||||
? "60"
|
||||
: selectedOrganization.sync_config.interval}
|
||||
</Typography>
|
||||
) : null}
|
||||
<Typography
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
marginTop: 25,
|
||||
marginRight: 10,
|
||||
}}
|
||||
>
|
||||
Your Apikey
|
||||
</Typography>
|
||||
<div style={{ display: "flex" }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{ backgroundColor: theme.palette.inputColor }}
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
disabled={true}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
placeholder="Cloud Apikey"
|
||||
variant="outlined"
|
||||
defaultValue={userSettings.apikey}
|
||||
/>
|
||||
{selectedOrganization.cloud_sync_active ? (
|
||||
<Button
|
||||
style={{
|
||||
width: 150,
|
||||
height: 50,
|
||||
marginLeft: 10,
|
||||
marginTop: 17,
|
||||
}}
|
||||
variant={
|
||||
selectedOrganization.cloud_sync_active === true
|
||||
? "outlined"
|
||||
: "contained"
|
||||
}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
handleStopOrgSync(selectedOrganization.id);
|
||||
}}
|
||||
>
|
||||
Stop Sync
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ display: "flex", marginBottom: 20 }}>
|
||||
<TextField
|
||||
color="primary"
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
marginRight: 10,
|
||||
}}
|
||||
InputProps={{
|
||||
style: {
|
||||
height: "50px",
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
},
|
||||
}}
|
||||
required
|
||||
fullWidth={true}
|
||||
disabled={selectedOrganization.cloud_sync}
|
||||
autoComplete="cloud apikey"
|
||||
id="apikey_field"
|
||||
margin="normal"
|
||||
placeholder="Cloud Apikey"
|
||||
variant="outlined"
|
||||
onChange={(event) => {
|
||||
setCloudSyncApikey(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
(!selectedOrganization.cloud_sync &&
|
||||
cloudSyncApikey.length === 0) ||
|
||||
loading
|
||||
}
|
||||
style={{ marginTop: 15, height: 50, width: 150 }}
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
enableCloudSync(
|
||||
cloudSyncApikey,
|
||||
selectedOrganization,
|
||||
selectedOrganization.cloud_sync
|
||||
);
|
||||
}}
|
||||
color="primary"
|
||||
variant={
|
||||
selectedOrganization.cloud_sync === true
|
||||
? "outlined"
|
||||
: "contained"
|
||||
}
|
||||
>
|
||||
{selectedOrganization.cloud_sync
|
||||
? "Stop sync"
|
||||
: "Start sync"}
|
||||
</Button>
|
||||
</div>
|
||||
{orgSyncResponse.length > 0 ? (
|
||||
<Typography style={{ marginTop: 5, marginBottom: 10 }}>
|
||||
Message from Shuffle Cloud: <b>{orgSyncResponse}</b>
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
<Typography
|
||||
style={{ marginTop: 40, marginLeft: 10, marginBottom: 5 }}
|
||||
>
|
||||
Cloud sync features (monthly usage)
|
||||
</Typography>
|
||||
<Grid container style={{ width: "100%", marginBottom: 15 }}>
|
||||
{selectedOrganization.sync_features === undefined ||
|
||||
selectedOrganization.sync_features === null
|
||||
? null
|
||||
: Object.keys(selectedOrganization.sync_features).map(function (
|
||||
key,
|
||||
index
|
||||
) {
|
||||
// unnecessary parts
|
||||
if (key === "schedule" || key === "apps" || key === "updates") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newkey = key.replaceAll("_", " ");
|
||||
const griditem = {
|
||||
primary: newkey,
|
||||
secondary:
|
||||
item.description === undefined ||
|
||||
item.description === null ||
|
||||
item.description.length === 0
|
||||
? "Not defined yet"
|
||||
: item.description,
|
||||
limit: item.limit,
|
||||
usage: item.usage === undefined ||
|
||||
item.usage === null ? 0 : item.usage,
|
||||
data_collection: "None",
|
||||
active: item.active,
|
||||
icon: <PolymerIcon style={{ color: itemColor }} />,
|
||||
};
|
||||
const item = selectedOrganization.sync_features[key];
|
||||
if (item === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Zoom key={index}>
|
||||
<GridItem data={griditem} />
|
||||
</Zoom>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
const newkey = key.replaceAll("_", " ");
|
||||
const griditem = {
|
||||
primary: newkey,
|
||||
secondary:
|
||||
item.description === undefined ||
|
||||
item.description === null ||
|
||||
item.description.length === 0
|
||||
? "Not defined yet"
|
||||
: item.description,
|
||||
limit: item.limit,
|
||||
usage: item.usage === undefined ||
|
||||
item.usage === null ? 0 : item.usage,
|
||||
data_collection: "None",
|
||||
active: item.active,
|
||||
icon: <PolymerIcon style={{ color: itemColor }} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<Zoom key={index}>
|
||||
<GridItem data={griditem} />
|
||||
</Zoom>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
: adminTab === 2 ?
|
||||
<Billing
|
||||
isCloud={isCloud}
|
||||
userdata={userdata}
|
||||
setSelectedOrganization={setSelectedOrganization}
|
||||
globalUrl={globalUrl}
|
||||
selectedOrganization={selectedOrganization}
|
||||
adminTab={adminTab}
|
||||
billingInfo={billingInfo}
|
||||
selectedOrganization={selectedOrganization}
|
||||
stripeKey={props.stripeKey}
|
||||
handleGetOrg={handleGetOrg}
|
||||
/>
|
||||
: adminTab === 3 ?
|
||||
<div>
|
||||
<Typography style={{ marginTop: 20, marginBottom: 10 }}>
|
||||
Stats
|
||||
</Typography>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
@@ -2986,8 +3051,8 @@ const Admin = (props) => {
|
||||
}}
|
||||
/>
|
||||
{isCloud &&
|
||||
selectedOrganization.partner_info !== undefined &&
|
||||
selectedOrganization.partner_info.reseller === true ? (
|
||||
selectedOrganization.partner_info !== undefined &&
|
||||
selectedOrganization.partner_info.reseller === true ? (
|
||||
<div style={{ marginTop: 30, marginBottom: 200 }}>
|
||||
<Typography
|
||||
style={{ marginTop: 40, marginLeft: 10, marginBottom: 5 }}
|
||||
@@ -3169,89 +3234,9 @@ const Admin = (props) => {
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
{isCloud &&
|
||||
selectedOrganization.subscriptions !== undefined &&
|
||||
selectedOrganization.subscriptions !== null &&
|
||||
selectedOrganization.subscriptions.length > 0 ? (
|
||||
<div style={{ marginTop: 30, marginBottom: 20 }}>
|
||||
<Typography
|
||||
style={{ marginTop: 40, marginLeft: 10, marginBottom: 5 }}
|
||||
>
|
||||
Your subscription
|
||||
{selectedOrganization.subscriptions.length > 1 ? "s" : ""}
|
||||
</Typography>
|
||||
<Grid container spacing={3} style={{ marginTop: 15 }}>
|
||||
{selectedOrganization.subscriptions
|
||||
.reverse()
|
||||
.map((sub, index) => {
|
||||
return (
|
||||
<Grid item key={index} xs={4}>
|
||||
<Card
|
||||
elevation={6}
|
||||
style={{
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
color: "white",
|
||||
padding: 25,
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
<b>Type</b>: {sub.level}
|
||||
<div />
|
||||
<b>Recurrence</b>: {sub.recurrence}
|
||||
<div />
|
||||
{sub.active ? (
|
||||
<div>
|
||||
<b>Started</b>:{" "}
|
||||
{new Date(sub.startdate * 1000).toISOString()}
|
||||
<div />
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
style={{ marginTop: 15 }}
|
||||
onClick={() => {
|
||||
cancelSubscriptions(sub.reference);
|
||||
}}
|
||||
>
|
||||
Cancel subscription
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<b>Cancelled</b>:{" "}
|
||||
{new Date(
|
||||
sub.cancellationdate * 1000
|
||||
).toISOString()}
|
||||
<div />
|
||||
<Typography color="textSecondary">
|
||||
<b>Status</b>: Deactivated
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
<Divider
|
||||
style={{
|
||||
marginTop: 20,
|
||||
backgroundColor: theme.palette.inputColor,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ backgroundColor: "#1f2023", paddingTop: 25 }}>
|
||||
<HandlePaymentNew
|
||||
theme={theme}
|
||||
stripeKey={props.stripeKey}
|
||||
userdata={userdata}
|
||||
globalUrl={globalUrl}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
|
||||
@@ -6144,7 +6144,6 @@ const AngularWorkflow = (defaultprops) => {
|
||||
const [hover, setHover] = React.useState(false);
|
||||
|
||||
if (app.id === "" || app.name === "") {
|
||||
console.log("Skipping app: ", app)
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
+306
-17
@@ -26,6 +26,12 @@ import {
|
||||
DialogContent,
|
||||
CircularProgress,
|
||||
Zoom,
|
||||
InputAdornment,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemText,
|
||||
Avatar,
|
||||
} from "@material-ui/core";
|
||||
|
||||
import {
|
||||
@@ -37,18 +43,24 @@ import {
|
||||
CloudDownload as CloudDownloadIcon,
|
||||
Edit as EditIcon,
|
||||
Delete as DeleteIcon,
|
||||
Search as SearchIcon,
|
||||
Folder as FolderIcon,
|
||||
LibraryBooks as LibraryBooksIcon,
|
||||
} from "@material-ui/icons";
|
||||
|
||||
import {
|
||||
ForkRight as ForkRightIcon,
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import aa from 'search-insights'
|
||||
import { useTheme } from "@material-ui/core/styles";
|
||||
import { InstantSearch, Configure, connectSearchBox, connectHits, Index } from 'react-instantsearch-dom';
|
||||
import algoliasearch from 'algoliasearch/lite';
|
||||
|
||||
import YAML from "yaml";
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
import { useAlert } from "react-alert";
|
||||
import Dropzone from "../components/Dropzone";
|
||||
import Dropzone from "../components/Dropzone.jsx";
|
||||
|
||||
const surfaceColor = "#27292D";
|
||||
const inputColor = "#383B40";
|
||||
@@ -254,6 +266,7 @@ export const GetParsedPaths = (inputdata, basekey) => {
|
||||
return parsedValues;
|
||||
};
|
||||
|
||||
const searchClient = algoliasearch("JNSS5CFDZZ", "db08e40265e2941b9a7d8f644b6e5240")
|
||||
const Apps = (props) => {
|
||||
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
|
||||
|
||||
@@ -1486,6 +1499,268 @@ const Apps = (props) => {
|
||||
}, [appValidation, isDropzone]);
|
||||
|
||||
var appDelay = -75
|
||||
|
||||
const leftBarSize = viewWidth
|
||||
const SearchBox = ({ currentRefinement, refine, isSearchStalled, }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (document !== undefined) {
|
||||
const appsearchValue = document.getElementById("app_search_field")
|
||||
if (appsearchValue !== undefined && appsearchValue !== null) {
|
||||
console.log("Value2: ", appsearchValue.value)
|
||||
if (appsearchValue.value !== undefined && appsearchValue.value !== null && appsearchValue.value.length > 0) {
|
||||
refine(appsearchValue.value)
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<form id="search_form" noValidate type="searchbox" action="" role="search" style={{ margin: 0, display: "none", }} onClick={() => {
|
||||
}}>
|
||||
<TextField
|
||||
fullWidth
|
||||
style={{ backgroundColor: theme.palette.inputColor, borderRadius: theme.palette.borderRadius, maxWidth: leftBarSize - 20, }}
|
||||
InputProps={{
|
||||
style: {
|
||||
color: "white",
|
||||
fontSize: "1em",
|
||||
height: 50,
|
||||
margin: 0,
|
||||
},
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon style={{ marginLeft: 5 }} />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
autoComplete='off'
|
||||
type="search"
|
||||
color="primary"
|
||||
placeholder="Find Public Apps, Workflows, Documentation and more"
|
||||
value={currentRefinement}
|
||||
id="shuffle_search_field"
|
||||
onClick={(event) => {
|
||||
console.log("Click!")
|
||||
}}
|
||||
onBlur={(event) => {
|
||||
//setSearchOpen(false)
|
||||
}}
|
||||
onChange={(event) => {
|
||||
//if (event.currentTarget.value.length > 0 && !searchOpen) {
|
||||
// setSearchOpen(true)
|
||||
//}
|
||||
|
||||
refine(event.currentTarget.value)
|
||||
}}
|
||||
limit={5}
|
||||
/>
|
||||
{/*isSearchStalled ? 'My search is stalled' : ''*/}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
const activateApp = (appid, refresh) => {
|
||||
fetch(globalUrl + "/api/v1/apps/" + appid + "/activate", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
console.log("Failed to activate")
|
||||
}
|
||||
|
||||
return response.json()
|
||||
})
|
||||
.then((responseJson) => {
|
||||
if (responseJson.success === false) {
|
||||
alert.error("Failed to activate the app")
|
||||
} else {
|
||||
alert.success("App activated for your organization! Refresh the page to use the app.")
|
||||
|
||||
if (refresh === true) {
|
||||
getApps()
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
//alert.error(error.toString())
|
||||
console.log("Activate app error: ", error.toString())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const AppHits = ({ hits }) => {
|
||||
const [mouseHoverIndex, setMouseHoverIndex] = React.useState(0)
|
||||
|
||||
//var tmp = searchOpen
|
||||
//if (!searchOpen) {
|
||||
// return null
|
||||
//}
|
||||
|
||||
const positionInfo = document.activeElement.getBoundingClientRect()
|
||||
const outerlistitemStyle = {
|
||||
width: "100%",
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden",
|
||||
borderBottom: "1px solid rgba(255,255,255,0.4)",
|
||||
}
|
||||
|
||||
if (hits.length > 4) {
|
||||
hits = hits.slice(0, 4)
|
||||
}
|
||||
|
||||
var type = "app"
|
||||
const baseImage = <LibraryBooksIcon />
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative", marginTop: 15, marginLeft: 0, marginRight: 10, position: "absolute", color: "white", zIndex: 1001, backgroundColor: theme.palette.inputColor, minWidth: leftBarSize - 10, maxWidth: leftBarSize - 10, boxShadows: "none", overflowX: "hidden", }}>
|
||||
<List style={{ backgroundColor: theme.palette.inputColor, }}>
|
||||
{hits.length === 0 ?
|
||||
<ListItem style={outerlistitemStyle}>
|
||||
<ListItemAvatar onClick={() => console.log(hits)}>
|
||||
<Avatar>
|
||||
<FolderIcon />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={"No public apps found."}
|
||||
secondary={"Try a broader search term"}
|
||||
/>
|
||||
</ListItem>
|
||||
:
|
||||
hits.map((hit, index) => {
|
||||
const innerlistitemStyle = {
|
||||
width: positionInfo.width + 35,
|
||||
overflowX: "hidden",
|
||||
overflowY: "hidden",
|
||||
borderBottom: "1px solid rgba(255,255,255,0.4)",
|
||||
backgroundColor: mouseHoverIndex === index ? "#1f2023" : "inherit",
|
||||
cursor: "pointer",
|
||||
marginLeft: 0,
|
||||
marginRight: 0,
|
||||
maxHeight: 75,
|
||||
minHeight: 75,
|
||||
maxWidth: 420,
|
||||
minWidth: "100%",
|
||||
}
|
||||
|
||||
const name = hit.name === undefined ?
|
||||
hit.filename.charAt(0).toUpperCase() + hit.filename.slice(1).replaceAll("_", " ") + " - " + hit.title :
|
||||
(hit.name.charAt(0).toUpperCase() + hit.name.slice(1)).replaceAll("_", " ")
|
||||
|
||||
var secondaryText = hit.data !== undefined ? hit.data.slice(0, 40) + "..." : ""
|
||||
const avatar = hit.image_url === undefined ?
|
||||
baseImage
|
||||
:
|
||||
<Avatar
|
||||
src={hit.image_url}
|
||||
variant="rounded"
|
||||
/>
|
||||
|
||||
//console.log(hit)
|
||||
if (hit.categories !== undefined && hit.categories !== null && hit.categories.length > 0) {
|
||||
secondaryText = hit.categories.slice(0, 3).map((data, index) => {
|
||||
if (index === 0) {
|
||||
return data
|
||||
}
|
||||
|
||||
return ", " + data
|
||||
|
||||
/*
|
||||
<Chip
|
||||
key={index}
|
||||
style={chipStyle}
|
||||
label={data}
|
||||
onClick={() => {
|
||||
//handleChipClick
|
||||
}}
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
/>
|
||||
*/
|
||||
})
|
||||
}
|
||||
|
||||
var parsedUrl = isCloud ? `/apps/${hit.objectID}` : `https://shuffler.io/apps/${hit.objectID}`
|
||||
parsedUrl += `?queryID=${hit.__queryID}`
|
||||
|
||||
return (
|
||||
<div style={{ textDecoration: "none", color: "white", }} onClick={(event) => {
|
||||
//if (!isCloud) {
|
||||
// alert.info("Since this is an on-prem instance. You will need to activate the app yourself. Opening link to download it in a new window.")
|
||||
// setTimeout(() => {
|
||||
// event.preventDefault()
|
||||
// window.open(parsedUrl, '_blank')
|
||||
// }, 2000)
|
||||
//} else {
|
||||
alert.info(`Activating ${name}`)
|
||||
//}
|
||||
|
||||
console.log("CLICK: ", hit)
|
||||
|
||||
const queryID = hit.__queryID
|
||||
console.log("QUERY: ", queryID)
|
||||
|
||||
if (queryID !== undefined && queryID !== null) {
|
||||
aa('init', {
|
||||
appId: "JNSS5CFDZZ",
|
||||
apiKey: "db08e40265e2941b9a7d8f644b6e5240",
|
||||
})
|
||||
|
||||
const timestamp = new Date().getTime()
|
||||
aa('sendEvents', [
|
||||
{
|
||||
eventType: 'conversion',
|
||||
eventName: 'Public App Activated',
|
||||
index: 'appsearch',
|
||||
objectIDs: [hit.objectID],
|
||||
timestamp: timestamp,
|
||||
queryID: queryID,
|
||||
userToken: userdata === undefined || userdata === null || userdata.id === undefined ? "unauthenticated" : userdata.id,
|
||||
}
|
||||
])
|
||||
} else {
|
||||
console.log("No query to handle when activating")
|
||||
}
|
||||
|
||||
activateApp(hit.objectID, true)
|
||||
}}>
|
||||
<ListItem key={hit.objectID} style={innerlistitemStyle} onMouseOver={() => {
|
||||
setMouseHoverIndex(index)
|
||||
}}>
|
||||
<ListItemAvatar>
|
||||
{avatar}
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={name}
|
||||
secondary={secondaryText}
|
||||
/>
|
||||
{/*
|
||||
<ListItemSecondaryAction>
|
||||
<IconButton edge="end" aria-label="delete">
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</ListItemSecondaryAction>
|
||||
*/}
|
||||
</ListItem>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const CustomSearchBox = connectSearchBox(SearchBox)
|
||||
const CustomAppHits = connectHits(AppHits)
|
||||
|
||||
const appView = isLoggedIn ? (
|
||||
<Dropzone
|
||||
@@ -1643,28 +1918,42 @@ const Apps = (props) => {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<Paper square style={uploadViewPaperStyle}>
|
||||
<Typography style={{ margin: 10 }}>
|
||||
<span>
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href={"https://shuffler.io/search"}
|
||||
style={{ textDecoration: "none", color: "#f85a3e" }}
|
||||
target="_blank"
|
||||
>
|
||||
Click here
|
||||
</a>{" "}
|
||||
to search ALL apps, not just your activated ones.
|
||||
</span>
|
||||
</Typography>
|
||||
<div />
|
||||
|
||||
<Paper square style={{
|
||||
minWidth: viewWidth,
|
||||
maxWidth: viewWidth,
|
||||
color: "white",
|
||||
borderRadius: 5,
|
||||
//display: "flex",
|
||||
marginBottom: 10,
|
||||
overflow: "hidden",
|
||||
backgroundColor: theme.palette.platformColor,
|
||||
border: null,
|
||||
}}>
|
||||
{appSearchLoading ? (
|
||||
<CircularProgress
|
||||
color="primary"
|
||||
style={{ margin: "auto" }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
style={{ textAlign: "center", width: leftBarSize, marginTop: 10 }}
|
||||
onLoad={() => {
|
||||
console.log("Should load in extra apps?")
|
||||
}}
|
||||
>
|
||||
<Typography variant="body1" color="textSecondary">
|
||||
Couldn't find the app you're looking for? Searching unactivated apps. Click one of the below apps to Activate it for your organization.
|
||||
</Typography>
|
||||
<InstantSearch searchClient={searchClient} indexName="appsearch" onClick={() => {
|
||||
console.log("CLICKED")
|
||||
}}>
|
||||
<CustomSearchBox />
|
||||
<Index indexName="appsearch">
|
||||
<CustomAppHits />
|
||||
</Index>
|
||||
</InstantSearch>
|
||||
</div>
|
||||
</Paper>
|
||||
)
|
||||
) : isLoading ? (
|
||||
|
||||
@@ -66,7 +66,7 @@ import { DataGrid, GridToolbar } from "@mui/x-data-grid";
|
||||
|
||||
//import JSONPretty from 'react-json-pretty';
|
||||
//import JSONPrettyMon from 'react-json-pretty/dist/monikai'
|
||||
import Dropzone from "../components/Dropzone";
|
||||
import Dropzone from "../components/Dropzone.jsx";
|
||||
|
||||
import { useNavigate, Link, useParams } from "react-router-dom";
|
||||
import { useAlert } from "react-alert";
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import theme from '../theme';
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import Drift from "react-driftjs";
|
||||
|
||||
const Welcome = (props) => {
|
||||
const { globalUrl, surfaceColor, newColor, mini, inputColor, userdata, isLoggedIn, isLoaded, serverside } = props;
|
||||
@@ -25,7 +26,7 @@ const Welcome = (props) => {
|
||||
const [inputUsecase, setInputUsecase] = useState({});
|
||||
const [frameworkData, setFrameworkData] = useState(undefined);
|
||||
const [discoveryWrapper, setDiscoveryWrapper] = useState(undefined);
|
||||
const [activeStep, setActiveStep] = React.useState(0);
|
||||
const [activeStep, setActiveStep] = React.useState(1);
|
||||
const [apps, setApps] = React.useState([]);
|
||||
const [defaultSearch, setDefaultSearch] = React.useState("")
|
||||
const [selectionOpen, setSelectionOpen] = React.useState(false)
|
||||
@@ -281,7 +282,8 @@ const Welcome = (props) => {
|
||||
|
||||
setActiveStep(foundTab-1)
|
||||
} else {
|
||||
navigate(`/welcome?tab=1`)
|
||||
//navigate(`/welcome?tab=1`)
|
||||
navigate(`/welcome?tab=2`)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
@@ -426,29 +428,31 @@ const Welcome = (props) => {
|
||||
Who do you identify with the most?
|
||||
</Typography>
|
||||
<div style={{display: "flex", marginTop: 70, width: 700, margin: "auto",}}>
|
||||
<Card style={paperObject} onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "welcome",
|
||||
action: "click_welcome_continue",
|
||||
label: "",
|
||||
})
|
||||
} else {
|
||||
//setActiveStep(1)
|
||||
}
|
||||
<div style={{border: "1px solid #49A928",}}>
|
||||
<Card style={paperObject} onClick={() => {
|
||||
if (isCloud) {
|
||||
ReactGA.event({
|
||||
category: "welcome",
|
||||
action: "click_welcome_continue",
|
||||
label: "",
|
||||
})
|
||||
} else {
|
||||
//setActiveStep(1)
|
||||
}
|
||||
|
||||
setShowWelcome(true)
|
||||
}}>
|
||||
<CardActionArea style={actionObject}>
|
||||
<Typography variant="h4" style={{color: "#49A928"}}>
|
||||
New to Shuffle
|
||||
</Typography>
|
||||
<img src="/images/welcome_cog.png" style={imageStyle} />
|
||||
<Typography variant="body1" style={{marginTop: 30, color: "rgba(255,255,255,0.8)"}}>
|
||||
Follow our short introduction and learn some tips and tricks
|
||||
</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
setShowWelcome(true)
|
||||
}}>
|
||||
<CardActionArea style={actionObject}>
|
||||
<Typography variant="h4" style={{color: "#49A928"}}>
|
||||
New to Shuffle
|
||||
</Typography>
|
||||
<img src="/images/welcome_cog.png" style={imageStyle} />
|
||||
<Typography variant="body1" style={{marginTop: 30, color: "rgba(255,255,255,0.8)"}}>
|
||||
Follow our short introduction and learn some tips and tricks
|
||||
</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</div>
|
||||
<div style={{marginLeft: 25, marginRight: 25, }}>
|
||||
<Typography style={{marginTop: 200, }}>
|
||||
OR
|
||||
@@ -476,6 +480,19 @@ const Welcome = (props) => {
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</div>
|
||||
{/*
|
||||
<div style={{margin: "auto", border: "1px solid rgba(255,255,255,0.5)", borderRadius: theme.palette.borderRadius, marginTop: 50, width: 200, overflow: "wrap", padding: 25, cursor: "pointer", }} onClick={() => {
|
||||
if (window.drift !== undefined) {
|
||||
window.drift.api.startInteraction({ interactionId: 341911 })
|
||||
} else {
|
||||
console.log("Couldn't find drift in window.drift and not .drift-open-chat with querySelector: ", window.drift)
|
||||
}
|
||||
}}>
|
||||
<Typography variant="body1" style={{margin: "auto", textAlign: "center"}}>
|
||||
Want a free Proof of Value with our support team?
|
||||
</Typography>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
</Fade>
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ import { DataGrid, GridToolbar } from "@mui/x-data-grid";
|
||||
|
||||
//import JSONPretty from 'react-json-pretty';
|
||||
//import JSONPrettyMon from 'react-json-pretty/dist/monikai'
|
||||
import Dropzone from "../components/Dropzone";
|
||||
import Dropzone from "../components/Dropzone.jsx";
|
||||
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import { useAlert } from "react-alert";
|
||||
@@ -1262,8 +1262,9 @@ const Workflows = (props) => {
|
||||
|
||||
const exportAllWorkflows = (allWorkflows) => {
|
||||
for (var i = 0; i < allWorkflows.length; i++) {
|
||||
console.log(allWorkflows[i])
|
||||
setTimeout(() => {
|
||||
console.log(allWorkflows[i].name)
|
||||
console.log(allWorkflows[i])
|
||||
exportWorkflow(allWorkflows[i], false)
|
||||
}, i * 200);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user