jusrhee 5 лет назад
Родитель
Сommit
750370dab6

+ 1 - 3
dashboard/src/components/Selector.tsx

@@ -45,9 +45,7 @@ export default class Selector extends Component<PropsType, StateType> {
   renderDropdownLabel = () => {
     if (this.props.dropdownLabel && this.props.dropdownLabel !== '') {
       return (
-        <DropdownLabel>
-          {this.props.dropdownLabel}
-        </DropdownLabel>
+        <DropdownLabel>{this.props.dropdownLabel}</DropdownLabel>
       );
     }
   }

+ 2 - 0
dashboard/src/components/values-form/InputRow.tsx

@@ -84,6 +84,8 @@ const Input = styled.input`
 const Label = styled.div`
   color: #ffffff;
   margin-bottom: 10px;
+  display: flex;
+  align-items: center;
   font-size: 13px;
   font-family: 'Work Sans', sans-serif;
 `;

+ 6 - 1
dashboard/src/main/Main.tsx

@@ -54,6 +54,11 @@ export default class Main extends Component<PropsType, StateType> {
     this.setState({ isLoggedIn: true, initialized: true });
   }
 
+  handleLogOut = () => {
+    this.context.clearContext();
+    this.setState({ isLoggedIn: false, initialized: true });
+  }
+
   renderMain = () => {
     if (this.state.loading) {
       return <Loading />
@@ -79,7 +84,7 @@ export default class Main extends Component<PropsType, StateType> {
 
         <Route path='/dashboard' render={() => {
           if (this.state.isLoggedIn && this.state.initialized) {
-            return <Home logOut={() => this.setState({ isLoggedIn: false, initialized: true })} />
+            return <Home logOut={this.handleLogOut} />
           } else {
             return <Redirect to='/' />
           }

+ 49 - 28
dashboard/src/main/home/Home.tsx

@@ -4,6 +4,7 @@ import ReactModal from 'react-modal';
 
 import { Context } from '../../shared/Context';
 import api from '../../shared/api';
+import { ProjectType } from '../../shared/types';
 
 import Sidebar from './sidebar/Sidebar';
 import Dashboard from './dashboard/Dashboard';
@@ -16,6 +17,8 @@ import ClusterInstructionsModal from './modals/ClusterInstructionsModal';
 import IntegrationsModal from './modals/IntegrationsModal';
 import IntegrationsInstructionsModal from './modals/IntegrationsInstructionsModal';
 import NewProject from './new-project/NewProject';
+import Navbar from './navbar/Navbar';
+import Provisioner from './new-project/Provisioner';
 
 type PropsType = {
   logOut: () => void
@@ -27,7 +30,7 @@ type StateType = {
   currentView: string,
 
   // Track last project id for refreshing clusters on project change
-  prevProjectId: number | null
+  prevProjectId: number | null,
 };
 
 export default class Home extends Component<PropsType, StateType> {
@@ -38,6 +41,27 @@ export default class Home extends Component<PropsType, StateType> {
     prevProjectId: null as number | null,
   }
 
+  // Possibly consolidate into context (w/ ProjectSection + NewProject)
+  getProjects = () => {
+    let { user, currentProject, projects, setProjects } = this.context;
+    api.getProjects('<token>', {}, { id: user.userId }, (err: any, res: any) => {
+      if (err) {
+        console.log(err)
+      } else if (res.data) {
+        setProjects(res.data);
+        if (res.data.length > 0 && !currentProject) {
+          this.context.setCurrentProject(res.data[0]);
+        } else if (res.data.length === 0) {
+          this.setState({ currentView: 'new-project' });
+        }
+      }
+    });
+  }
+
+  componentDidMount() {
+    this.getProjects();
+  }
+
   componentDidUpdate(prevProps: PropsType) {
     if (prevProps !== this.props && this.context.currentProject) {
 
@@ -91,7 +115,7 @@ export default class Home extends Component<PropsType, StateType> {
     } else if (currentView === 'dashboard') {
       return (
         <DashboardWrapper>
-          <Dashboard />
+          <Dashboard setCurrentView={(x: string) => this.setState({ currentView: x })} />
         </DashboardWrapper>
       );
     } else if (currentView === 'integrations') {
@@ -100,6 +124,8 @@ export default class Home extends Component<PropsType, StateType> {
       return (
         <NewProject setCurrentView={(x: string) => this.setState({ currentView: x })} />
       );
+    } else if (currentView === 'provisioner') {
+      return <Provisioner />
     }
 
     return (
@@ -109,6 +135,25 @@ export default class Home extends Component<PropsType, StateType> {
     );
   }
 
+  renderSidebar = () => {
+    if (this.context.projects.length > 0) {
+
+      // Force sidebar closed on first provision 
+      if (this.state.currentView === 'provisioner' && this.state.forceSidebar) {
+        this.setState({ forceSidebar: false });
+      }
+      
+      return (
+        <Sidebar
+          forceSidebar={this.state.forceSidebar}
+          setWelcome={(x: boolean) => this.setState({ showWelcome: x })}
+          setCurrentView={(x: string) => this.setState({ currentView: x })}
+          currentView={this.state.currentView}
+        />
+      );
+    }
+  }
+
   render() {
     let { currentModal, setCurrentModal, currentProject } = this.context;
     return (
@@ -146,15 +191,10 @@ export default class Home extends Component<PropsType, StateType> {
           <IntegrationsInstructionsModal />
         </ReactModal>
 
-        <Sidebar
-          logOut={this.props.logOut}
-          forceSidebar={this.state.forceSidebar}
-          setWelcome={(x: boolean) => this.setState({ showWelcome: x })}
-          setCurrentView={(x: string) => this.setState({ currentView: x })}
-          currentView={this.state.currentView}
-        />
+        {this.renderSidebar()}
 
         <ViewWrapper>
+          <Navbar logOut={this.props.logOut} />
           {this.renderContents()}
         </ViewWrapper>
       </StyledHome>
@@ -164,25 +204,6 @@ export default class Home extends Component<PropsType, StateType> {
 
 Home.contextType = Context;
 
-const MediumModalStyles = {
-  overlay: {
-    backgroundColor: 'rgba(0,0,0,0.6)',
-    zIndex: 2,
-  },
-  content: {
-    borderRadius: '7px',
-    border: 0,
-    width: '760px',
-    maxWidth: '80vw',
-    margin: '0 auto',
-    height: '575px',
-    top: 'calc(50% - 289px)',
-    backgroundColor: '#202227',
-    animation: 'floatInModal 0.5s 0s',
-    overflow: 'visible',
-  },
-};
-
 const SmallModalStyles = {
   overlay: {
     backgroundColor: 'rgba(0,0,0,0.6)',

+ 5 - 1
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -6,6 +6,7 @@ import { Context } from '../../../shared/Context';
 import PipelinesSection from './PipelinesSection';
 
 type PropsType = {
+  setCurrentView: (x: string) => void,
 };
 
 type StateType = {
@@ -32,7 +33,10 @@ export default class Dashboard extends Component<PropsType, StateType> {
             <Title>{currentProject && currentProject.name}</Title>
             <i
               className="material-icons"
-              onClick={() => this.context.setCurrentModal('UpdateProjectModal', { currentProject: currentProject })}
+              onClick={() => this.context.setCurrentModal('UpdateProjectModal', { 
+                currentProject: currentProject,
+                setCurrentView: this.props.setCurrentView,
+              })}
             >
               more_vert
           </i>

+ 19 - 2
dashboard/src/main/home/modals/UpdateProjectModal.tsx

@@ -25,6 +25,24 @@ export default class UpdateProjectModal extends Component<PropsType, StateType>
     status: null as string | null,
     showDeleteOverlay: false,
   };
+
+  // Possibly consolidate into context (w/ ProjectSection + NewProject)
+  getProjects = () => {
+    let { user, currentProject, projects, setProjects } = this.context;
+    api.getProjects('<token>', {}, { id: user.userId }, (err: any, res: any) => {
+      if (err) {
+        console.log(err)
+      } else if (res.data) {
+        setProjects(res.data);
+        if (res.data.length > 0) {
+          this.context.setCurrentProject(res.data[0]);
+        } else {
+          this.context.currentModalData.setCurrentView('new-project');
+        }
+        this.context.setCurrentModal(null, null);
+      }
+    });
+  }
   
   // TODO: Handle update to unmounted component
   handleDelete = () => {
@@ -35,8 +53,7 @@ export default class UpdateProjectModal extends Component<PropsType, StateType>
         this.setState({ status: 'error' });
         // console.log(err)
       } else {
-        this.context.setCurrentModal(null, null);
-        this.context.setCurrentProject(null);
+        this.getProjects();
         this.setState({ status: 'successful', showDeleteOverlay: false });
       }
     });

+ 168 - 0
dashboard/src/main/home/navbar/Navbar.tsx

@@ -0,0 +1,168 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import api from '../../../shared/api';
+import { Context } from '../../../shared/Context';
+
+type PropsType = {
+  logOut: () => void,
+};
+
+type StateType = {
+  showDropdown: boolean
+};
+
+export default class Navbar extends Component<PropsType, StateType> {
+  state = {
+    showDropdown: false,
+  }
+
+  handleLogout = (): void => {
+    let { logOut } = this.props;
+    let { setCurrentError } = this.context;
+
+    // Attempt user logout
+    api.logOutUser('<token>', {}, {}, (err: any, res: any) => {
+      err ? setCurrentError(err.response.data.errors[0]) : logOut();
+    }); 
+  }
+
+  renderSettingsDropdown = () => {
+    if (this.state.showDropdown) {
+      return (
+        <>
+          <CloseOverlay onClick={() => this.setState({ showDropdown: false })} />
+          <Dropdown dropdownWidth='250px' dropdownMaxHeight='200px'>
+            <DropdownLabel>{this.context.user && this.context.user.email}</DropdownLabel>
+            <LogOutButton onClick={this.handleLogout}>
+              <i className="material-icons">keyboard_return</i> Log Out
+            </LogOutButton>
+          </Dropdown>
+        </>
+      );
+    }
+  }
+
+  render() {
+    return (
+      <StyledNavbar>
+        <NavButton href='https://docs.getporter.dev/docs' target='_blank'>
+          <i className="material-icons">help_outline</i>
+        </NavButton>
+        <NavButton selected={this.state.showDropdown}>
+          <i 
+            className="material-icons-outlined" 
+            onClick={() => this.setState({ showDropdown: !this.state.showDropdown })}
+          >account_circle</i>
+          {this.renderSettingsDropdown()}
+        </NavButton>
+      </StyledNavbar>
+    );
+  }
+}
+
+Navbar.contextType = Context;
+
+const CloseOverlay = styled.div`
+  position: fixed;
+  width: 100vw;
+  height: 100vh;
+  z-index: 100;
+  top: 0;
+  left: 0;
+  cursor: default;
+`;
+
+const LogOutButton = styled.button`
+  padding: 13px;
+  height: 40px;
+  font-size: 13px;
+  font-weight: 500;
+  font-family: 'Work Sans', sans-serif;
+  color: white;
+  width: 100%;
+  border: 0;
+  text-align: left;
+  background: none;
+  cursor: ${(props) => (!props.disabled ? 'pointer' : 'default')};
+  user-select: none;
+  :focus { outline: 0 }
+  :hover {
+    background: #ffffff11;
+  }
+  display: flex;
+  align-items: center;
+
+  > i {
+    background: none;
+    border-radius: 3px;
+    display: flex;
+    font-size: 13px;
+    top: 11px;
+    margin-right: 10px;
+    padding: 1px;
+    align-items: center;
+    justify-content: center;
+    color: #ffffffaa;
+    border: 1px solid #ffffffaa;
+  }
+`;
+
+const DropdownLabel = styled.div`
+  font-size: 13px;
+  height: 40px;
+  color: #ffffff44;
+  font-weight: 500;
+  display: flex;
+  align-items: center;
+  padding: 13px;
+  max-width: 180px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+`;
+
+const Dropdown = styled.div`
+  position: absolute;
+  right: 0;
+  top: calc(100% + 5px);
+  background: #26282f;
+  width: ${(props: { dropdownWidth: string, dropdownMaxHeight: string }) => props.dropdownWidth};
+  max-height: ${(props: { dropdownWidth: string, dropdownMaxHeight: string }) => props.dropdownMaxHeight ? props.dropdownMaxHeight : '300px'};
+  border-radius: 3px;
+  z-index: 999;
+  overflow-y: auto;
+  margin-bottom: 20px;
+  box-shadow: 0 8px 20px 0px #00000088;
+`;
+
+const StyledNavbar = styled.div`
+  width: 100%;
+  height: 60px;
+  position: absolute;
+  top: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  padding-right: 5px;
+  justify-content: flex-end;
+`;
+
+const NavButton = styled.a`
+  display: flex;
+  position: relative;
+  align-items: center;
+  justify-content: center;
+  margin-right: 15px;
+  cursor: pointer;
+  :hover {
+    > i {
+      color: #ffffff;
+    }
+  }
+  
+  > i {
+    color: ${(props: { selected?: boolean }) => props.selected ? '#ffffff' : '#ffffff88'};
+    font-size: 24px;
+  }
+`;

+ 29 - 11
dashboard/src/main/home/new-project/NewProject.tsx

@@ -1,16 +1,16 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
 import gradient from '../../../assets/gradient.jpg';
-import awsWhite from '../../../assets/aws-white.png';
 import close from '../../../assets/close.png';
 
+import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
 import { integrationList } from '../../../shared/common';
+import { ProjectType } from '../../../shared/types';
+
 import InputRow from '../../../components/values-form/InputRow';
 import Helper from '../../../components/values-form/Helper';
 import Heading from '../../../components/values-form/Heading';
-import api from '../../../shared/api';
-import Loading from '../../../components/Loading';
 import SaveButton from '../../../components/SaveButton';
 
 const providers = ['aws', 'gcp', 'do',];
@@ -22,6 +22,7 @@ type PropsType = {
 type StateType = {
   projectName: string,
   selectedProvider: string | null,
+  awsRegion: string | null,
   awsAccessId: string | null,
   awsSecretKey: string | null,
   status: string | null,
@@ -31,6 +32,7 @@ export default class NewProject extends Component<PropsType, StateType> {
   state = {
     projectName: '',
     selectedProvider: null as string | null,
+    awsRegion: '' as string | null,
     awsAccessId: '' as string | null,
     awsSecretKey: '' as string | null,
     status: null as string | null,
@@ -75,6 +77,15 @@ export default class NewProject extends Component<PropsType, StateType> {
           </CloseButton>
           <DarkMatter />
           <Heading>AWS Credentials</Heading>
+          <InputRow
+            type='text'
+            value={this.state.awsRegion}
+            setValue={(x: string) => this.setState({ awsRegion: x })}
+            label='📍 AWS Region'
+            placeholder='ex: mars-north-12'
+            width='100%'
+            isRequired={true}
+          />
           <InputRow
             type='text'
             value={this.state.awsAccessId}
@@ -165,11 +176,11 @@ export default class NewProject extends Component<PropsType, StateType> {
   }
 
   validateForm = () => {
-    let { projectName, selectedProvider, awsAccessId, awsSecretKey } = this.state;
+    let { projectName, selectedProvider, awsAccessId, awsSecretKey, awsRegion } = this.state;
     if (!this.isAlphanumeric(projectName) || projectName === '') {
       return false;
     } else if (selectedProvider === 'aws') {
-      return awsAccessId !== '' && awsSecretKey !== '';
+      return awsAccessId !== '' && awsSecretKey !== '' && awsRegion !== '';
     }  else if (selectedProvider === 'skipped') {
       return true;
     }
@@ -189,9 +200,16 @@ export default class NewProject extends Component<PropsType, StateType> {
           if (err) {
             console.log(err)
           } else if (res.data) {
+            this.context.setProjects(res.data);
             if (res.data.length > 0) {
-              this.context.setCurrentProject(res.data[0]);
-              this.props.setCurrentView('dashboard');
+              let proj = res.data.find((el: ProjectType) => el.name === this.state.projectName);
+              this.context.setCurrentProject(proj);
+
+              if (this.state.selectedProvider === 'aws') {
+                this.props.setCurrentView('provisioner');
+              } else {
+                this.props.setCurrentView('dashboard');
+              }
             } 
           }
         });
@@ -201,7 +219,7 @@ export default class NewProject extends Component<PropsType, StateType> {
   
   render() {
     return (
-      <StyledNewProject>
+      <StyledNewProject height={this.state.selectedProvider === 'aws' ? '700px' : '600px'}>
         <TitleSection>
           <Title>New Project</Title>
         </TitleSection>
@@ -485,8 +503,8 @@ const TitleSection = styled.div`
 const StyledNewProject = styled.div`
   width: calc(90% - 150px);
   min-width: 300px;
-  height: 600px;
+  height: ${(props: { height: string }) => props.height};
   position: relative;
   padding-top: 50px;
-  margin-top: calc(50vh - 350px);
-`;
+  margin-top: ${(props: { height: string }) => props.height === '600px' ? 'calc(50vh - 350px)' : 'calc(50vh - 400px)'};
+`;

+ 164 - 0
dashboard/src/main/home/new-project/Provisioner.tsx

@@ -0,0 +1,164 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import api from '../../../shared/api';
+import { Context } from '../../../shared/Context';
+import { integrationList } from '../../../shared/common';
+import loading from '../../../assets/loading.gif';
+
+import Helper from '../../../components/values-form/Helper';
+
+type PropsType = {
+};
+
+type StateType = {
+  logs: string[],
+};
+
+const loadMax = 40;
+
+export default class Provisioner extends Component<PropsType, StateType> {
+  state = {
+    logs: [] as string[],
+  }
+
+  componentDidMount() {
+    this.setState({ logs: ['test-1', 'test-2'] })
+  }
+
+  scrollRef = React.createRef<HTMLDivElement>();
+
+  renderLogs = () => {
+    return this.state.logs.map((log, i) => {
+        return <div key={i}>{log}</div>
+    })
+  }
+  
+  render() {
+    return (
+      <StyledProvisioner>
+        <TitleSection>
+          <Title><img src={loading} /> Setting Up Porter</Title>
+        </TitleSection>
+
+        <Helper>
+          Porter is currently being provisioned to your AWS account:
+        </Helper>
+
+        <LoadingBar>
+          <Loaded progress={((7 / loadMax) * 100).toString() + '%'} />
+        </LoadingBar>
+
+        <LogStream ref={this.scrollRef}>
+          <Wrapper>
+            {this.renderLogs()}
+          </Wrapper>
+        </LogStream>
+
+        <Helper>
+          (Provisioning usually takes around 15 minutes. Brew some tea?)
+        </Helper>
+      </StyledProvisioner>
+    );
+  }
+}
+
+Provisioner.contextType = Context;
+
+const Wrapper = styled.div`
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+  padding: 20px 25px;
+`;
+
+const LogStream = styled.div`
+  height: 300px;
+  margin-top: 30px;
+  font-size: 13px;
+  border: 2px solid #ffffff55;
+  border-radius: 10px;
+  width: 100%;
+  background: #00000022;
+  user-select: text;
+`;
+
+const Message = styled.div`
+  display: flex;
+  height: 100%;
+  width: 100%;
+  align-items: center;
+  justify-content: center;
+  color: #ffffff44;
+  font-size: 13px;
+`;
+
+const Loaded = styled.div`
+  width: ${(props: { progress: string }) => props.progress};
+  height: 100%;
+  background: linear-gradient(to right, #4f8aff, #8e7dff, #4f8aff);
+  background-size: 400% 400%;
+
+  animation: linkLoad 2s infinite;
+
+  @keyframes linkLoad {
+    0%{background-position:91% 100%}
+    100%{background-position:10% 0%}
+  }
+`;
+
+const LoadingBar = styled.div`
+  width: 100%;
+  margin-top: 24px;
+  overflow: hidden;
+  height: 20px;
+  background: #ffffff11;
+  border-radius: 30px;
+`;
+
+const Title = styled.div`
+  font-size: 24px;
+  font-weight: 600;
+  font-family: 'Work Sans', sans-serif;
+  color: #ffffff;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+
+  > img {
+    width: 20px;
+    margin-right: 10px;
+    margin-bottom: -2px;
+  }
+`;
+
+const TitleSection = styled.div`
+  margin-bottom: 20px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+
+  > a {
+    > i {
+      display: flex;
+      align-items: center;
+      margin-bottom: -2px;
+      font-size: 18px;
+      margin-left: 18px;
+      color: #858FAAaa;
+      cursor: pointer;
+      :hover {
+        color: #aaaabb;
+      }
+    }
+  }
+`;
+
+const StyledProvisioner = styled.div`
+  width: calc(90% - 150px);
+  min-width: 300px;
+  height: 600px;
+  position: relative;
+  padding-top: 50px;
+  margin-top: calc(50vh - 350px);
+`;

+ 2 - 30
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -9,47 +9,20 @@ import { ProjectType } from '../../../shared/types';
 type PropsType = {
   currentProject: ProjectType,
   setCurrentView: (x: string) => void,
+  projects: ProjectType[],
 };
 
 type StateType = {
-  projects: ProjectType[],
   expanded: boolean
 };
 
 export default class ProjectSection extends Component<PropsType, StateType> {
   state = {
-    projects: [] as ProjectType[],
     expanded: false,
   };
 
-  updateProjects = () => {
-    let { user } = this.context;
-    api.getProjects('<token>', {}, { id: user.userId }, (err: any, res: any) => {
-      if (err) {
-        console.log(err)
-      } else if (res.data) {
-        this.setState({ projects: res.data });
-        if (res.data.length > 0 && !this.props.currentProject) {
-          this.context.setCurrentProject(res.data[0]);
-        } else if (res.data.length === 0) {
-          this.props.setCurrentView('new-project');
-        }
-      }
-    });
-  }
-
-  componentDidMount() {
-    this.updateProjects();
-  }
-
-  componentDidUpdate(prevProps: PropsType) {
-    if (!this.props.currentProject && (this.props.currentProject !== prevProps.currentProject)) {
-      this.updateProjects();
-    }
-  }
-
   renderOptionList = () => {
-    return this.state.projects.map((project: ProjectType, i: number) => {
+    return this.props.projects.map((project: ProjectType, i: number) => {
       return (
         <Option
           key={i}
@@ -91,7 +64,6 @@ export default class ProjectSection extends Component<PropsType, StateType> {
   }
 
   handleExpand = () => {
-    this.updateProjects();
     this.setState({ expanded: !this.state.expanded });
   }
 

+ 1 - 0
dashboard/src/main/home/sidebar/ProjectSectionContainer.tsx

@@ -20,6 +20,7 @@ export default class ProjectSectionContainer extends Component<PropsType, StateT
     return (
       <ProjectSection
         currentProject={this.context.currentProject}
+        projects={this.context.projects}
         setCurrentView={this.props.setCurrentView}
       />
     );

+ 18 - 28
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -1,19 +1,16 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
-import gradient from '../../../assets/gradient.jpg';
 import category from '../../../assets/category.svg';
-import pipelines from '../../../assets/pipelines.svg';
 import integrations from '../../../assets/integrations.svg';
 import filter from '../../../assets/filter.svg';
 
-import api from '../../../shared/api';
 import { Context } from '../../../shared/Context';
 
 import ClusterSection from './ClusterSection';
 import ProjectSectionContainer from './ProjectSectionContainer';
+import loading from '../../../assets/loading.gif';
 
 type PropsType = {
-  logOut: () => void,
   forceSidebar: boolean,
   setWelcome: (x: boolean) => void,
   setCurrentView: (x: string) => void,
@@ -92,22 +89,16 @@ export default class Sidebar extends Component<PropsType, StateType> {
     }
   };
 
-  handleLogout = (): void => {
-    let { logOut } = this.props;
-    let { setCurrentError } = this.context;
-
-    // Attempt user logout
-    api.logOutUser('<token>', {}, {}, (err: any, res: any) => {
-      // TODO: case and set logout error
-      
-      err ? setCurrentError(err.response.data.errors[0]) : logOut();
-    }); 
-  }
-
   renderProjectContents = () => {
-    if (this.context.currentProject) {
+    if (this.props.currentView === 'provisioner') {
+      return (
+        <ProjectPlaceholder>
+          <img src={loading} /> Creating . . .
+        </ProjectPlaceholder>
+      )
+    } else if (this.context.currentProject) {
       return (
-        <div>
+        <>
           <SidebarLabel>Home</SidebarLabel>
           <NavButton
             onClick={() => this.props.setCurrentView('dashboard')}
@@ -142,7 +133,7 @@ export default class Sidebar extends Component<PropsType, StateType> {
             setCurrentView={this.props.setCurrentView}
             isSelected={this.props.currentView === 'cluster-dashboard'}
           />
-        </div>
+        </>
       );
     }
 
@@ -177,12 +168,6 @@ export default class Sidebar extends Component<PropsType, StateType> {
           <br />
 
           {this.renderProjectContents()}
-
-          <BottomSection>
-            <LogOutButton onClick={this.handleLogout}>
-            Log Out <i className="material-icons">keyboard_return</i>
-            </LogOutButton>
-          </BottomSection>
         </StyledSidebar>
       </div>
     );
@@ -198,11 +183,16 @@ const ProjectPlaceholder = styled.div`
   display: flex;
   align-items: center;
   justify-content: center;
-  height: calc(100% - 180px);
+  height: calc(100% - 100px);
   font-size: 13px;
   font-family: 'Work Sans', sans-serif;
-  color: #ffffff44;
-  margin-top: 20px;
+  color: #aaaabb;
+  padding-bottom: 80px;
+
+  > img {
+    width: 17px;
+    margin-right: 10px;
+  }
 `;
 
 const NavButton = styled.div`

+ 16 - 0
dashboard/src/shared/Context.tsx

@@ -43,6 +43,10 @@ class ContextProvider extends Component {
     setCurrentProject: (currentProject: ProjectType) => {
       this.setState({ currentProject });
     },
+    projects: [] as ProjectType[],
+    setProjects: (projects: ProjectType[]) => {
+      this.setState({ projects });
+    },
     user: null as any,
     setUser: (userId: number, email: string) => {
       this.setState({ user: {userId, email} });
@@ -50,6 +54,18 @@ class ContextProvider extends Component {
     devOpsMode: true,
     setDevOpsMode: (devOpsMode: boolean) => {
       this.setState({ devOpsMode });
+    },
+    clearContext: () => {
+      this.setState({
+        currentModal: null,
+        currentModalData: null,
+        currentError: null,
+        currentCluster: null,
+        currentProject: null,
+        projects: [],
+        user: null,
+        devOpsMode: true,
+      });
     }
   };