Explorar o código

basic project reorganization

jusrhee %!s(int64=5) %!d(string=hai) anos
pai
achega
6f7c046dff

+ 9 - 3
dashboard/src/components/values-form/InputRow.tsx

@@ -2,11 +2,13 @@ import React, { ChangeEvent, Component } from 'react';
 import styled from 'styled-components';
 import styled from 'styled-components';
 
 
 type PropsType = {
 type PropsType = {
-  label: string,
+  label?: string,
   type: string,
   type: string,
   value: string | number,
   value: string | number,
   setValue: (x: string) => void,
   setValue: (x: string) => void,
   unit?: string
   unit?: string
+  placeholder?: string
+  width?: string
 };
 };
 
 
 type StateType = {
 type StateType = {
@@ -14,12 +16,14 @@ type StateType = {
 
 
 export default class InputRow extends Component<PropsType, StateType> {
 export default class InputRow extends Component<PropsType, StateType> {
   render() {
   render() {
-    let { label, value, type, unit } = this.props;
+    let { label, value, type, unit, placeholder, width } = this.props;
     return (
     return (
       <StyledInputRow>
       <StyledInputRow>
         <Label>{label}</Label>
         <Label>{label}</Label>
         <InputWrapper>
         <InputWrapper>
           <Input
           <Input
+            placeholder={placeholder}
+            width={width}
             type={type}
             type={type}
             value={value}
             value={value}
             onChange={(e: ChangeEvent<HTMLInputElement>) =>
             onChange={(e: ChangeEvent<HTMLInputElement>) =>
@@ -49,7 +53,7 @@ const Input = styled.input`
   background: #ffffff11;
   background: #ffffff11;
   border: 1px solid #ffffff55;
   border: 1px solid #ffffff55;
   border-radius: 3px;
   border-radius: 3px;
-  width: 270px;
+  width: ${(props: { width: string }) => props.width ? props.width : '270px'};
   color: white;
   color: white;
   padding: 5px 8px;
   padding: 5px 8px;
   margin-right: 8px;
   margin-right: 8px;
@@ -59,6 +63,8 @@ const Input = styled.input`
 const Label = styled.div`
 const Label = styled.div`
   color: #ffffff;
   color: #ffffff;
   margin-bottom: 10px;
   margin-bottom: 10px;
+  font-size: 13px;
+  font-family: 'Work Sans', sans-serif;
 `;
 `;
 
 
 const StyledInputRow = styled.div`
 const StyledInputRow = styled.div`

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

@@ -3,13 +3,15 @@ import styled from 'styled-components';
 import ReactModal from 'react-modal';
 import ReactModal from 'react-modal';
 
 
 import { Context } from '../../shared/Context';
 import { Context } from '../../shared/Context';
+import api from '../../shared/api';
 
 
 import Sidebar from './sidebar/Sidebar';
 import Sidebar from './sidebar/Sidebar';
 import Dashboard from './dashboard/Dashboard';
 import Dashboard from './dashboard/Dashboard';
-import ClusterConfigModal from './modals/ClusterConfigModal';
-import LaunchTemplateModal from './modals/LaunchTemplateModal';
 import Loading from '../../components/Loading';
 import Loading from '../../components/Loading';
 import Templates from './templates/Templates';
 import Templates from './templates/Templates';
+import ClusterConfigModal from './modals/ClusterConfigModal';
+import LaunchTemplateModal from './modals/LaunchTemplateModal';
+import CreateProjectModal from './modals/CreateProjectModal';
 
 
 type PropsType = {
 type PropsType = {
   logOut: () => void
   logOut: () => void
@@ -28,6 +30,19 @@ export default class Home extends Component<PropsType, StateType> {
     currentView: 'dashboard'
     currentView: 'dashboard'
   }
   }
 
 
+  componentDidMount() {
+    let { user } = this.context;
+    api.getProjects('<token>', {}, { id: user.userId }, (err: any, res: any) => {
+      if (err) {
+        // console.log(err)
+      } else if (res.data) {
+        if (res.data.length === 0) {
+          this.context.setCurrentModal('CreateProjectModal', { keepOpen: true });
+        }
+      }
+    });
+  }
+
   renderDashboard = () => {
   renderDashboard = () => {
     let { currentCluster, setCurrentModal } = this.context;
     let { currentCluster, setCurrentModal } = this.context;
 
 
@@ -73,24 +88,33 @@ export default class Home extends Component<PropsType, StateType> {
   }
   }
 
 
   render() {
   render() {
+    let { currentModal, setCurrentModal, currentProject } = this.context;
     return (
     return (
       <StyledHome>
       <StyledHome>
         <ReactModal
         <ReactModal
-          isOpen={this.context.currentModal === 'ClusterConfigModal'}
-          onRequestClose={() => this.context.setCurrentModal(null, null)}
+          isOpen={currentModal === 'ClusterConfigModal'}
+          onRequestClose={() => setCurrentModal(null, null)}
           style={MediumModalStyles}
           style={MediumModalStyles}
           ariaHideApp={false}
           ariaHideApp={false}
         >
         >
           <ClusterConfigModal />
           <ClusterConfigModal />
         </ReactModal>
         </ReactModal>
         <ReactModal
         <ReactModal
-          isOpen={this.context.currentModal === 'LaunchTemplateModal'}
-          onRequestClose={() => this.context.setCurrentModal(null, null)}
+          isOpen={currentModal === 'LaunchTemplateModal'}
+          onRequestClose={() => setCurrentModal(null, null)}
           style={MediumModalStyles}
           style={MediumModalStyles}
           ariaHideApp={false}
           ariaHideApp={false}
         >
         >
           <LaunchTemplateModal />
           <LaunchTemplateModal />
         </ReactModal>
         </ReactModal>
+        <ReactModal
+          isOpen={currentModal === 'CreateProjectModal'}
+          onRequestClose={() => currentProject ? setCurrentModal(null, null) : null }
+          style={ProjectModalStyles}
+          ariaHideApp={false}
+        >
+          <CreateProjectModal />
+        </ReactModal>
 
 
         <Sidebar
         <Sidebar
           logOut={this.props.logOut}
           logOut={this.props.logOut}
@@ -127,6 +151,25 @@ const MediumModalStyles = {
   },
   },
 };
 };
 
 
+const ProjectModalStyles = {
+  overlay: {
+    backgroundColor: 'rgba(0,0,0,0.6)',
+    zIndex: 2,
+  },
+  content: {
+    borderRadius: '7px',
+    border: 0,
+    width: '565px',
+    maxWidth: '80vw',
+    margin: '0 auto',
+    height: '225px',
+    top: 'calc(50% - 120px)',
+    backgroundColor: '#202227',
+    animation: 'floatInModal 0.5s 0s',
+    overflow: 'visible',
+  },
+};
+
 const StyledDashboard = styled.div`
 const StyledDashboard = styled.div`
   height: 100%;
   height: 100%;
   width: 100vw;
   width: 100vw;

+ 202 - 0
dashboard/src/main/home/modals/CreateProjectModal.tsx

@@ -0,0 +1,202 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+import close from '../../../assets/close.png';
+import gradient from '../../../assets/gradient.jpg';
+
+import api from '../../../shared/api';
+import { Context } from '../../../shared/Context';
+
+import SaveButton from '../../../components/SaveButton';
+import InputRow from '../../../components/values-form/InputRow';
+
+type PropsType = {
+};
+
+type StateType = {
+  projectName: string,
+  status: string | null
+};
+
+export default class CreateProjectModal extends Component<PropsType, StateType> {
+  state = {
+    projectName: '',
+    status: null as string | null,
+  };
+  
+  componentDidMount() {
+
+  }
+
+  createProject = () => {
+    this.setState({ status: 'loading' });
+    api.createProject('<token>', {
+      name: this.state.projectName
+    }, {}, (err: any, res: any) => {
+      if (err) {
+        console.log(err);
+      } else {
+        this.context.currentModalData.updateProjects();
+        this.context.setCurrentModal(null, null);
+      }
+    });
+  }
+
+  renderCloseButton = () => {
+    if (this.context.currentModalData && !this.context.currentModalData.keepOpen) {
+      return (
+        <CloseButton onClick={() => {
+          this.context.setCurrentModal(null, null);
+        }}>
+          <CloseButtonImg src={close} />
+        </CloseButton>
+      );
+    }
+  }
+
+  isAlphanumeric = (x: string) => {
+    let re = /^[a-z0-9-]+$/;
+    if (x.length == 0 || x.search(re) === -1) {
+      return false;
+    }
+    return true;
+  }
+
+  render() {
+    return (
+      <StyledCreateProjectModal>
+        {this.renderCloseButton()}
+
+        <ModalTitle>New Project</ModalTitle>
+        <Subtitle>
+          Project name
+          <Warning highlight={!this.isAlphanumeric(this.state.projectName) && this.state.projectName !== ''}>
+            (lowercase letters, numbers, and "-" only)
+          </Warning>
+        </Subtitle>
+
+        <InputWrapper>
+          <ProjectIcon>
+            <ProjectImage src={gradient} />
+            <Letter>{this.state.projectName ? this.state.projectName[0].toUpperCase() : '-'}</Letter>
+          </ProjectIcon>
+          <InputRow
+            type='string'
+            value={this.state.projectName}
+            setValue={(x: string) => this.setState({ projectName: x })}
+            placeholder='ex: perspective-vortex'
+            width='470px'
+          />
+        </InputWrapper>
+
+        <SaveButton
+          text='Create'
+          disabled={!this.isAlphanumeric(this.state.projectName) || this.state.projectName === ''}
+          onClick={this.createProject}
+          status={this.state.status}
+        />
+      </StyledCreateProjectModal>
+      );
+  }
+}
+
+CreateProjectModal.contextType = Context;
+
+const Warning = styled.span`
+  color: ${(props: { highlight: boolean }) => props.highlight ? '#f5cb42' : ''};
+  margin-left: 5px;
+`;
+
+const Letter = styled.div`
+  height: 100%;
+  width: 100%;
+  position: absolute;
+  background: #00000028;
+  top: 0;
+  left: 0;
+  display: flex;
+  color: white;
+  align-items: center;
+  justify-content: center;
+`;
+
+const ProjectImage = styled.img`
+  width: 100%;
+  height: 100%;
+`;
+
+const ProjectIcon = styled.div`
+  width: 25px;
+  min-width: 25px;
+  height: 25px;
+  border-radius: 3px;
+  overflow: hidden;
+  position: relative;
+  margin-right: 10px;
+  font-weight: 400;
+  margin-top: 14px;
+`;
+
+const InputWrapper = styled.div`
+  display: flex;
+  align-items: center;
+`;
+
+const Subtitle = styled.div`
+  margin-top: 23px;
+  font-family: 'Work Sans', sans-serif;
+  font-size: 13px;
+  color: #aaaabb;
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  margin-bottom: -10px;
+`;
+
+const ModalTitle = styled.div`
+  margin: 0px 0px 13px;
+  display: flex;
+  flex: 1;
+  font-family: 'Assistant';
+  font-size: 18px;
+  color: #ffffff;
+  user-select: none;
+  font-weight: 700;
+  align-items: center;
+  position: relative;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+const CloseButton = styled.div`
+  position: absolute;
+  display: block;
+  width: 40px;
+  height: 40px;
+  padding: 13px 0 12px 0;
+  z-index: 1;
+  text-align: center;
+  border-radius: 50%;
+  right: 15px;
+  top: 12px;
+  cursor: pointer;
+  :hover {
+    background-color: #ffffff11;
+  }
+`;
+
+const CloseButtonImg = styled.img`
+  width: 14px;
+  margin: 0 auto;
+`;
+
+const StyledCreateProjectModal= styled.div`
+  width: 100%;
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  padding: 25px 32px;
+  overflow: hidden;
+  border-radius: 6px;
+  background: #202227;
+`;

+ 66 - 43
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -33,37 +33,42 @@ export default class ProjectSection extends Component<PropsType, StateType> {
   };
   };
 
 
   updateProjects = () => {
   updateProjects = () => {
-    this.context.setCurrentProject('z');
+    console.log('rip')
+    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 });
+        this.context.setCurrentProject(res.data[0].name);
+      }
+    });
   }
   }
 
 
   componentDidMount() {
   componentDidMount() {
     this.updateProjects();
     this.updateProjects();
   }
   }
-
-  componentDidUpdate(prevProps: PropsType) {
-    if (prevProps !== this.props) {
-    }
-  }
   
   
   showProjectCreateModal = () => {
   showProjectCreateModal = () => {
-    this.context.setCurrentModal('ClusterConfigModal', { updateClusters: null });
-  }
-
-  handleOptionClick = (option: { value: string, label: string }) => {
-    this.context.setCurrentProject(option.value);
+    this.context.setCurrentModal('CreateProjectModal', {
+      keepOpen: false,
+      updateProjects: this.updateProjects
+    });
   }
   }
 
 
-
   renderOptionList = () => {
   renderOptionList = () => {
-    return options.map((option: { value: string, label: string }, i: number) => {
+    return this.state.projects.map((project: any, i: number) => {
       return (
       return (
         <Option
         <Option
           key={i}
           key={i}
-          selected={option.value === this.context.currentProject}
-          onClick={() => this.handleOptionClick(option)}
-          lastItem={i === options.length - 1}
+          selected={project.name === this.context.currentProject}
+          onClick={() => this.context.setCurrentProject(project.name)}
         >
         >
-          {option.label}
+          <ProjectIcon>
+            <ProjectImage src={gradient} />
+            <Letter>{project.name[0].toUpperCase()}</Letter>
+          </ProjectIcon>
+          <ProjectLabel>{project.name}</ProjectLabel>
         </Option>
         </Option>
       );
       );
     });
     });
@@ -76,22 +81,23 @@ export default class ProjectSection extends Component<PropsType, StateType> {
           <CloseOverlay onClick={() => this.setState({ expanded: false })} />
           <CloseOverlay onClick={() => this.setState({ expanded: false })} />
           <Dropdown>
           <Dropdown>
             {this.renderOptionList()}
             {this.renderOptionList()}
+            <Option
+              selected={false}
+              lastItem={true}
+              onClick={this.showProjectCreateModal}
+            >
+              <ProjectIconAlt>+</ProjectIconAlt>
+              <ProjectLabel>Add a project</ProjectLabel>
+            </Option>
           </Dropdown>
           </Dropdown>
         </div>
         </div>
-      )
-    }
-  }
-
-  getLabel = (value: string): any => {
-    let tgt = options.find((element: { value: string, label: string }) => element.value === value);
-    if (tgt) {
-      return tgt.label;
+      );
     }
     }
   }
   }
 
 
   render() {
   render() {
-    if (this.context.currentProject) {
-      let projectName = this.getLabel(this.context.currentProject);
+    let { currentProject } = this.context;
+    if (currentProject) {
       return (
       return (
         <StyledProjectSection>
         <StyledProjectSection>
           <MainSelector
           <MainSelector
@@ -100,9 +106,9 @@ export default class ProjectSection extends Component<PropsType, StateType> {
           >
           >
             <ProjectIcon>
             <ProjectIcon>
               <ProjectImage src={gradient} />
               <ProjectImage src={gradient} />
-              <Letter>{projectName && projectName[0].toUpperCase()}</Letter>
+              <Letter>{currentProject[0].toUpperCase()}</Letter>
             </ProjectIcon>
             </ProjectIcon>
-            <ProjectName>{projectName}</ProjectName>
+            <ProjectName>{currentProject}</ProjectName>
             <i className="material-icons">arrow_drop_down</i>
             <i className="material-icons">arrow_drop_down</i>
           </MainSelector>
           </MainSelector>
           {this.renderDropdown()}
           {this.renderDropdown()}
@@ -119,6 +125,19 @@ export default class ProjectSection extends Component<PropsType, StateType> {
 
 
 ProjectSection.contextType = Context;
 ProjectSection.contextType = Context;
 
 
+const ProjectLabel = styled.div`
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+const AddButton = styled.div`
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+  padding: 12px 15px;
+`;
+
 const Plus = styled.div`
 const Plus = styled.div`
   margin-right: 10px;
   margin-right: 10px;
   font-size: 15px;
   font-size: 15px;
@@ -129,9 +148,9 @@ const InitializeButton = styled.div`
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
-  width: calc(100% - 30px);
+  width: calc(100% - 10px);
   height: 38px;
   height: 38px;
-  margin: 20px 15px 0;
+  margin: 8px 5px;
   font-size: 13px;
   font-size: 13px;
   font-weight: 500;
   font-weight: 500;
   border-radius: 3px;
   border-radius: 3px;
@@ -148,19 +167,16 @@ const InitializeButton = styled.div`
 const Option = styled.div` 
 const Option = styled.div` 
   width: 100%;
   width: 100%;
   border-top: 1px solid #00000000;
   border-top: 1px solid #00000000;
-  border-bottom: 1px solid ${(props: { selected: boolean, lastItem: boolean }) => props.lastItem ? '#ffffff00' : '#ffffff15'};
-  height: 35px;
+  border-bottom: 1px solid ${(props: { selected: boolean, lastItem?: boolean }) => props.lastItem ? '#ffffff00' : '#ffffff15'};
+  height: 45px;
+  display: flex;
+  align-items: center;
   font-size: 13px;
   font-size: 13px;
-  padding-top: 9px;
   align-items: center;
   align-items: center;
-  padding-left: 15px;
+  padding-left: 10px;
   cursor: pointer;
   cursor: pointer;
   padding-right: 10px;
   padding-right: 10px;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  background: ${(props: { selected: boolean, lastItem: boolean }) => props.selected ? '#ffffff11' : ''};
-
+  background: ${(props: { selected: boolean, lastItem?: boolean }) => props.selected ? '#ffffff11' : ''};
   :hover {
   :hover {
     background: #ffffff22;
     background: #ffffff22;
   }
   }
@@ -181,12 +197,12 @@ const Dropdown = styled.div`
   top: calc(100% + 5px);
   top: calc(100% + 5px);
   background: #26282f;
   background: #26282f;
   width: 180px;
   width: 180px;
-  max-height: 300px;
+  max-height: 500px;
   border-radius: 3px;
   border-radius: 3px;
   z-index: 999;
   z-index: 999;
   overflow-y: auto;
   overflow-y: auto;
   margin-bottom: 20px;
   margin-bottom: 20px;
-  box-shadow: 0 8px 20px 0px #00000088;
+  box-shadow: 0 5px 15px 5px #00000077;
 `;
 `;
 
 
 const ProjectName = styled.div`
 const ProjectName = styled.div`
@@ -220,10 +236,16 @@ const ProjectIcon = styled.div`
   overflow: hidden;
   overflow: hidden;
   position: relative;
   position: relative;
   margin-right: 10px;
   margin-right: 10px;
-  margin-left: 20px;
   font-weight: 400;
   font-weight: 400;
 `;
 `;
 
 
+const ProjectIconAlt = styled(ProjectIcon)`
+  border: 1px solid #ffffff44;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+`;
+
 const StyledProjectSection = styled.div`
 const StyledProjectSection = styled.div`
   position: relative;
   position: relative;
 `;
 `;
@@ -237,6 +259,7 @@ const MainSelector = styled.div`
   font-weight: 600;
   font-weight: 600;
   cursor: pointer;
   cursor: pointer;
   padding: 10px 0;
   padding: 10px 0;
+  padding-left: 20px;
   :hover {
   :hover {
     > i {
     > i {
       background: #ffffff11;
       background: #ffffff11;

+ 1 - 1
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -141,7 +141,7 @@ export default class Sidebar extends Component<PropsType, StateType> {
     // Render placeholder if no project exists
     // Render placeholder if no project exists
     return (
     return (
       <ProjectPlaceholder>
       <ProjectPlaceholder>
-        You have no projects.
+        No projects found.
       </ProjectPlaceholder>
       </ProjectPlaceholder>
     );
     );
   }
   }

+ 11 - 1
dashboard/src/shared/api.tsx

@@ -112,6 +112,14 @@ const getBranchContents = baseApi<{ dir: string }, {
   return `/api/repos/github/${pathParams.repo}/${pathParams.branch}/contents`;
   return `/api/repos/github/${pathParams.repo}/${pathParams.branch}/contents`;
 });
 });
 
 
+const getProjects = baseApi<{}, { id: number }>('GET', pathParams => {
+  return `/api/users/${pathParams.id}/projects`;
+});
+
+const createProject = baseApi<{ name: string }, {}>('POST', pathParams => {
+  return `/api/projects`;
+});
+
 // Bundle export to allow default api import (api.<method> is more readable)
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
 export default {
   checkAuth,
   checkAuth,
@@ -131,5 +139,7 @@ export default {
   upgradeChartValues,
   upgradeChartValues,
   getTemplates,
   getTemplates,
   getBranches,
   getBranches,
-  getBranchContents
+  getBranchContents,
+  getProjects,
+  createProject
 }
 }