stitch-ui
Version:
579 lines (551 loc) • 19.5 kB
JavaScript
// TODO proptypes
/* eslint-disable react/prop-types */
/* globals window */
import React, { Component } from "react"; // eslint-disable-line no-unused-vars
import Highlight from "react-highlight/lib/optimized";
import ClipboardButton from "react-clipboard.js";
import FontAwesome from "react-fontawesome";
import { connect } from "react-redux";
import { NavLink } from "react-router-dom";
import {
WEB_LINK,
NPM_PACKAGE,
LANG_WEB,
LANG_NODE,
codeSnippet,
findMongoDBService
} from "../snippet";
import { AlertContainer, addAlert } from "../../alert";
import AddServiceModal from "./AddServiceModal";
import * as actions from "../actions";
import * as svcActions from "../../services/actions";
import { Button, Collapsible, Spinner, Banner, Tabs, Tab } from "../../core";
import * as authActions from "../../auth/actions";
import {
validateCollectionName,
validateDBName
} from "../../services/mongodb/validation";
const FIRSTRUN_STORAGEKEY = "_stitch_firstrun_ns/";
// TODO use separate pills/tabs for choosing language or import type.
// Must be updated after a real NPM package has been published.
function StepLink({ number, header }) {
return (
<div className="collapsible">
<div className="collapsible-header">
<div className="collapsible-header-content">
<GettingStartedStep number={number} header={header} />
</div>
<div className="collapsible-header-toggle">
<FontAwesome name="chevron-right" />
</div>
</div>
</div>
);
}
function GettingStartedStep({ number, header }) {
return (
<div className="getting-started-steps-step-header">
<div className="getting-started-steps-step-num">
{number}
</div>
<div className="getting-started-steps-step-header-text">
{header}
</div>
</div>
);
}
class AppHome extends Component {
constructor(props) {
super(props);
this.toggleAnonAuth = this.toggleAnonAuth.bind(this);
this.addCollection = this.addCollection.bind(this);
this.resetFirstRunNS = this.resetFirstRunNS.bind(this);
this.closeNewServiceModal = this.closeNewServiceModal.bind(this);
this.openNewServiceModal = this.openNewServiceModal.bind(this);
if (!this.props.app.services) {
return;
}
this.state = {
modalOpen: false,
language: LANG_WEB,
savingNS: false,
ruleError: null,
openStep1: true,
openStep2: true,
openStep3: true,
openStep4: false,
openStep5: false,
openStep6: false,
openStep7: false
};
}
componentDidMount() {
this.props.loadProviders();
const firstRunNS = window.localStorage.getItem(
FIRSTRUN_STORAGEKEY + this.props.app._id
);
if (firstRunNS) {
const firstDot = firstRunNS.indexOf(".");
// eslint-disable-next-line react/no-did-mount-set-state
this.setState({
firstRunNS,
firstRunDB: firstRunNS.substring(0, firstDot),
firstRunCollection: firstRunNS.substring(firstDot + 1)
});
}
const mongoSvc = findMongoDBService(this.props.app.services);
this.setState({ mongoSvc, openStep4: !!mongoSvc }); // eslint-disable-line react/no-did-mount-set-state
}
setLanguage(language) {
this.setState({ language });
}
openNewServiceModal() {
this.setState({ modalOpen: true });
}
closeNewServiceModal() {
this.setState({ modalOpen: false });
}
addCollection() {
if (!this.state.mongoSvc) {
return Promise.resolve();
}
const dbError = validateDBName(this.props.dbName);
const collectionError = validateCollectionName(this.props.collectionName);
if (dbError) {
this.setState({ ruleError: dbError });
return Promise.resolve();
}
if (collectionError) {
this.setState({ ruleError: collectionError });
return Promise.resolve();
}
const newDB = this.props.dbName;
const newCollection = this.props.collectionName;
const newNS = `${newDB}.${newCollection}`;
this.setState({ savingNS: true });
return this.props
.addRule(this.state.mongoSvc)
.then(r =>
this.props.updateRule(this.state.mongoSvc, r._id, {
...r,
namespace: newNS
})
)
.then(() => {
window.localStorage.setItem(
FIRSTRUN_STORAGEKEY + this.props.app._id,
newNS
);
this.setState({
ruleError: null,
firstRunNS: newNS,
firstRunCollection: newCollection,
firstRunDB: newDB,
savingNS: false
});
})
.catch(err => {
this.setState({ ruleError: err.message, savingNS: false });
});
}
toggleAnonAuth(e) {
if (e.target.checked) {
return this.props
.enableProvider("anon", "user", {})
.then(this.props.loadProviders);
}
return this.props
.disableProvider("anon", "user")
.then(this.props.loadProviders);
}
resetFirstRunNS() {
window.localStorage.removeItem(FIRSTRUN_STORAGEKEY + this.props.app._id);
this.setState({
firstRunNS: null,
firstRunCollection: null,
firstRunDB: null,
savingNS: false
});
}
render() {
const {
app,
providers,
dbName,
settings,
collectionName,
setDB,
setCollection
} = this.props;
if (!app) {
return null;
}
const offset = this.state.mongoSvc ? 0 : 1;
const isAnonAuthEnabled = (providers || {})["anon/user"] || false;
const webSnippet = codeSnippet(
app.clientAppId,
this.state.mongoSvc,
this.state.firstRunDB || "<DATABASE>",
this.state.firstRunCollection || "<COLLECTION>",
LANG_WEB
);
const nodeSnippet = codeSnippet(
app.clientAppId,
this.state.mongoSvc,
this.state.firstRunDB || "<DATABASE>",
this.state.firstRunCollection || "<COLLECTION>",
LANG_NODE
);
const clustersURL = `${settings.cloudUIBaseUrl}/v2#clusters`;
return (
<div className="main-content app-home">
<AddServiceModal
open={this.state.modalOpen}
onClose={this.closeNewServiceModal}
app={this.props.app}
/>
<h1 className="getting-started-header">
Welcome to Stitch! Get started here.
</h1>
<div className="getting-started" />
<div className="getting-started-steps">
{!this.state.mongoSvc &&
<Collapsible
headerContent={
<GettingStartedStep
number={1}
header={"Link an Atlas Cluster"}
/>
}
open={this.state.openStep1}
toggle={() => this.setState({ openStep1: !this.state.openStep1 })}
>
<div className="getting-started-step-content">
Looks like you haven't linked a MongoDB Atlas cluster yet.
Let's head over to Atlas and set that up.
</div>
<Button onClick={() => (window.location = clustersURL)}>
View Clusters
</Button>
</Collapsible>}
<Collapsible
headerContent={
<GettingStartedStep
number={1 + offset}
header={"Turn on Authentication"}
/>
}
open={this.state.openStep2}
toggle={() => this.setState({ openStep2: !this.state.openStep2 })}
>
<div>
<div style={{ padding: "10px" }}>
Let's turn on an authentication method for your app, so that
users have a way to log in.
</div>
<div className="slide-toggle-round-container">
<h5 className="slide-toggle-round-label">
Anonymous Authentication
</h5>
<div className="switch">
<input
id="private"
type="checkbox"
name="private"
onChange={this.toggleAnonAuth}
checked={isAnonAuthEnabled}
className="slide-toggle-round"
/>
<label htmlFor="private" />
</div>
</div>
<div className="getting-started-spinner">
<Spinner
open={
this.props.togglingProvider || this.props.loadingProviders
}
/>
</div>
</div>
</Collapsible>
<Collapsible
headerContent={
<GettingStartedStep
number={2 + offset}
header={"Initialize a MongoDB Collection"}
/>
}
open={this.state.openStep3}
toggle={() => this.setState({ openStep3: !this.state.openStep3 })}
>
<div className="getting-started-steps-step-content">
<div className="getting-started-steps-step-content-heading">
Initialize a MongoDB Collection
</div>
<div>
Let's go ahead and add a named collection in your MongoDB
cluster for you, so you can get up and running with your app.
<div className="getting-started-spinner">
<Spinner open={this.state.savingNS} />
</div>
{this.state.firstRunNS &&
<div>
<div className="first-run-created-ns">
Collection "<b>{this.state.firstRunNS}</b>"
added
</div>
<Button small default onClick={this.resetFirstRunNS}>
reset
</Button>
</div>}
{!this.state.firstRunNS &&
<span>
<Banner message={this.state.ruleError} error />
<div className="first-run-ns-input">
<input
type="text"
disabled={this.state.savingNS}
className="text-input"
placeholder="Database Name"
value={dbName}
onChange={e => setDB(e.target.value)}
/>
<input
type="text"
className="text-input"
placeholder="Collection Name"
value={collectionName}
disabled={this.state.savingNS}
onChange={e => setCollection(e.target.value)}
/>
<Button
small
primary
onClick={this.addCollection}
disabled={this.state.savingNS}
>
Add Collection
</Button>
</div>
</span>}
</div>
</div>
</Collapsible>
<Collapsible
headerContent={
<GettingStartedStep
number={3 + offset}
header={"Execute a Test Request"}
/>
}
open={this.state.openStep4}
toggle={() => this.setState({ openStep4: !this.state.openStep4 })}
>
<div className="clients-section" style={{ paddingBottom: "0px" }}>
<h2 className="clients-title">App ID</h2>
<ClipboardButton
data-clipboard-text={app.clientAppId}
button-className="button button-is-small button-is-default"
onSuccess={() => this.props.addAlert("copied-appid", "Copied!")}
>
Copy App ID
</ClipboardButton>
<AlertContainer alertKey="copied-appid" />
<pre className="small">
<code className="hljs">
{app.clientAppId}
</code>
</pre>
</div>
<Tabs>
<Tab
onClick={() => this.setState({ language: LANG_WEB })}
active={this.state.language === LANG_WEB}
>
<a className="section-header-tab-link">JS (Browser)</a>
</Tab>
<Tab
onClick={() => this.setState({ language: LANG_NODE })}
active={this.state.language === LANG_NODE}
>
<a className="section-header-tab-link">JS (Node)</a>
</Tab>
</Tabs>
<div className="tabs-content">
{this.state.language === LANG_WEB &&
<div>
<div className="clients-section">
<h2 className="clients-title">Importing on a Webpage</h2>
<h4 className="clients-title">
Insert this line in your{" "}
<code>
<bold>HEAD</bold>
</code>{" "}
near the top in a webpage.
</h4>
<ClipboardButton
data-clipboard-text={`<script src="${WEB_LINK}"></script>`}
button-className="button button-is-small button-is-default"
onSuccess={() =>
this.props.addAlert("copied-web-import", "Copied!")}
>
Copy Line
</ClipboardButton>
<AlertContainer alertKey="copied-web-import" />
<pre>
<code className="hljs">
{`<script src="${WEB_LINK}"></script>`}
</code>
</pre>
</div>
<div className="clients-section">
<h2 className="clients-title">Example Snippet</h2>
<h4 className="clients-title">
Try including this snippet in an HTML file on your site.
Be sure to replace the placeholder variables.
</h4>
<div>
<ClipboardButton
data-clipboard-text={webSnippet}
button-className="button button-is-small button-is-default"
onSuccess={() =>
this.props.addAlert("copied-web", "Copied!")}
>
Copy Snippet
</ClipboardButton>
<AlertContainer alertKey="copied-web" />
<Highlight
className="javascript snippet-body"
languages={["javascript"]}
>
{webSnippet}
</Highlight>
</div>
</div>
</div>}
{this.state.language === LANG_NODE &&
<div>
<div
className="clients-section"
style={{ "padding-bottom": "0px" }}
>
<h2 className="clients-title">Node.js</h2>
<h4 className="clients-title">
Install the Stitch Node.js client with NPM
</h4>
<ClipboardButton
data-clipboard-text={`npm install --save ${NPM_PACKAGE}`}
button-className="button button-is-small button-is-default"
onSuccess={() =>
this.props.addAlert("copied-npm-install", "Copied!")}
>
Copy Command
</ClipboardButton>
<AlertContainer alertKey="copied-npm-install" />
<pre className="small">
<code className="hljs">
{`npm install --save ${NPM_PACKAGE}`}
</code>
</pre>
</div>
<div className="clients-section">
<h2 className="clients-title">Example Snippet</h2>
<h4 className="clients-title">
Try the following code in your Node.js shell.
</h4>
<ClipboardButton
data-clipboard-text={nodeSnippet}
button-className="button button-is-small button-is-default"
onSuccess={() =>
this.props.addAlert("copied-node", "Copied!")}
>
Copy Snippet
</ClipboardButton>
<AlertContainer alertKey="copied-node" />
<Highlight
className="javascript snippet-body"
languages={["javascript"]}
>
{nodeSnippet}
</Highlight>
</div>
</div>}
</div>
</Collapsible>
<span onClick={this.openNewServiceModal}>
<StepLink number={4 + offset} header={"Add a Service"} />
</span>
<NavLink
to={`/groups/${app.groupId}/apps/${app._id}/namedpipelines`}
className="getting-started-step-link"
>
<StepLink number={5 + offset} header={"Add a Named Pipeline"} />
</NavLink>
<NavLink
to={`/groups/${app.groupId}/apps/${app._id}/values`}
className="getting-started-step-link"
>
<StepLink number={6 + offset} header={"Add a Value"} />
</NavLink>
</div>
</div>
);
}
}
const mapStateToProps = state => {
const { settings } = state.base;
const { app, dbName, collectionName } = state.app.root;
const { providers, togglingProvider, loadingProviders } = state.auth.auth;
const { ruleSaveState } = state.service.base;
return {
app,
providers,
settings,
dbName,
collectionName,
togglingProvider,
loadingProviders,
ruleSaveState
};
};
const mapDispatchToProps = (dispatch, ownProps) => ({
addAlert: (key, message) => dispatch(addAlert(key, message)),
setDB: db => dispatch(actions.setDB({ db })),
setCollection: collection => dispatch(actions.setCollection({ collection })),
enableProvider: (type, name, data) =>
dispatch(
authActions.enableProvider(
ownProps.app.groupId,
ownProps.app._id,
type,
name,
data
)
),
disableProvider: (type, name) =>
dispatch(
authActions.disableProvider(
ownProps.app.groupId,
ownProps.app._id,
type,
name
)
),
loadProviders: () =>
dispatch(authActions.loadProviders(ownProps.app.groupId, ownProps.app._id)),
addRule: (svc, rule) =>
dispatch(
svcActions.addRule(ownProps.app.groupId, ownProps.app._id, svc, rule)
),
updateRule: (svc, ruleId, rule) =>
dispatch(
svcActions.updateRule(
ownProps.app.groupId,
ownProps.app._id,
svc,
ruleId,
rule
)
)
});
export default connect(mapStateToProps, mapDispatchToProps)(AppHome);