UNPKG

stitch-ui

Version:

579 lines (551 loc) 19.5 kB
// 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&#39;t linked a MongoDB Atlas cluster yet. Let&#39;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&#39;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&#39;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 &quot;<b>{this.state.firstRunNS}</b>&quot; 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);