diff --git a/src/app/components/Card/Card.tsx b/src/app/components/Card/Card.tsx new file mode 100644 index 0000000..cb53977 --- /dev/null +++ b/src/app/components/Card/Card.tsx @@ -0,0 +1,50 @@ +import React from "react"; +import { style } from "typestyle"; +import { Color, Spacing } from "../../constants"; +import { Avatar } from "../Avatar/Avatar"; + +interface IProps { + description: string; + featureId: string; + hypothesis: string; +} +const styles = { + body: style({ + flexGrow: 1, + padding: Spacing.L + }), + container: style({ + border: `1px solid ${Color.GREY}`, + display: "flex", + flexDirection: "column", + height: 250, + padding: Spacing.NONE, + width: 250 + }), + featureId: style({ + marginLeft: Spacing.S + }), + footer: style({ + alignItems: "center", + borderTop: `1px solid ${Color.GREY}`, + display: "flex", + paddingBottom: Spacing.M, + paddingLeft: Spacing.L, + paddingTop: Spacing.M + }) +}; +export const Card: React.FC = (props): JSX.Element => { + return ( +
+
+ {props.description} +
+ {props.hypothesis} +
+
+ + {props.featureId} +
+
+ ); +}; diff --git a/src/app/containers/App.tsx b/src/app/containers/App.tsx index c5a341b..420ae49 100644 --- a/src/app/containers/App.tsx +++ b/src/app/containers/App.tsx @@ -7,6 +7,7 @@ import { stylesheet } from "typestyle"; import { config as appConfig } from "../../../config"; import { setupCss } from "../helpers/setupCss"; import { HomePage } from "../pages/HomePage"; +import { RunsPage } from "../pages/RunsPage"; import { IStore } from "../redux/IStore"; import { RoutePageMap } from "../routes/routes"; import { Header } from "./Header"; @@ -15,8 +16,9 @@ setupCss(); const classNames = stylesheet({ container: { - margin: 0, - padding: 0 + margin: "0 auto", + padding: 0, + width: 1024 } }); @@ -30,7 +32,7 @@ interface IStateToProps { class App extends React.Component { private components: RoutePageMap = { homePage: HomePage, - runsPage: null + runsPage: RunsPage }; public render(): JSX.Element { const { route, translations: { notFound } } = this.props; diff --git a/src/app/containers/EmptyState.tsx b/src/app/containers/EmptyState.tsx index 707195e..9b7712b 100644 --- a/src/app/containers/EmptyState.tsx +++ b/src/app/containers/EmptyState.tsx @@ -8,11 +8,9 @@ const styles = { container: style({ display: "flex", flexDirection: "column", - margin: "0 auto", marginTop: Spacing.XXXL, padding: Spacing.L, - paddingLeft: Spacing.XXXL, - width: 960 + paddingLeft: Spacing.XXXL }), iconContainer: style({ width: 200 @@ -23,6 +21,7 @@ const styles = { }; interface IProps { + buttonLabel: string; onActionClick: () => void; } @@ -33,9 +32,9 @@ export const EmptyState: React.FC = (props) => {

No items yet

-

There are no features created yet, why don't we start by creating one?

+

{props.children}

- +
); diff --git a/src/app/pages/HomePage.tsx b/src/app/pages/HomePage.tsx index 4c4a6d6..db008b2 100644 --- a/src/app/pages/HomePage.tsx +++ b/src/app/pages/HomePage.tsx @@ -1,13 +1,17 @@ import autobind from "autobind-decorator"; import * as React from "react"; import { connect } from "react-redux"; +import { ConnectedLink } from "react-router5"; import { Dispatch } from "redux"; +import { style } from "typestyle"; import { ICreateFeatureRequest, IFeature } from "../../Sdk/nodes/Features"; +import { Card } from "../components/Card/Card"; import { EmptyState } from "../containers/EmptyState"; import { ErrorState } from "../containers/ErrorState"; import { LoadingState } from "../containers/LoadingState"; import { IStore } from "../redux/IStore"; import { createFeatureActionCreators, featureActionCreators } from "../redux/modules/features/featureActionCreators"; +import { getRoutes } from "../routes/routes"; interface IStateToProps { error: string; @@ -20,6 +24,13 @@ interface IDispatchToProps { loadFeatures: () => void; } +const styles = { + container: style({ + display: "flex", + flexDirection: "row", + flexWrap: "wrap" + }) +}; class HomePage extends React.Component { public componentDidMount(): void { if (!this.props.loaded) { @@ -40,13 +51,14 @@ class HomePage extends React.Component { } if (this.props.features.length === 0) { return ( - + + There are no features created yet, why don't we start by creating one? + ); } - // finally return actual list return ( -
- Home Page +
+ {this.props.features.map(this.renderFeatures)}
); } @@ -59,6 +71,17 @@ class HomePage extends React.Component { const hypothesis = prompt("enter hypothesis", ""); this.props.createNewFeature({ hypothesis, featId, description }); } + + // eslint-disable-next-line @typescript-eslint/member-ordering + @autobind + private renderFeatures(feature: IFeature): JSX.Element { + const routes = getRoutes(); + return ( + + + + ); + } } function mapStateToProps(state: Pick): IStateToProps { diff --git a/src/app/pages/RunsPage.tsx b/src/app/pages/RunsPage.tsx new file mode 100644 index 0000000..4b4c1e3 --- /dev/null +++ b/src/app/pages/RunsPage.tsx @@ -0,0 +1,106 @@ +import autobind from "autobind-decorator"; +import React from "react"; +import { connect } from "react-redux"; +import { Dispatch } from "redux"; +import { createRouteNodeSelector } from "redux-router5"; +import { State as IRouterState } from "router5"; +import { ICreateFeatureRunRequest, IFeatureRun } from "../../Sdk/nodes/FeatureRuns"; +import { EmptyState } from "../containers/EmptyState"; +import { ErrorState } from "../containers/ErrorState"; +import { LoadingState } from "../containers/LoadingState"; +import { IStore } from "../redux/IStore"; +import { + createFeatureRunForFeature, + fetchFeatureRunsForFeature +} from "../redux/modules/featureRuns/fetchFeatureRunsForFeature"; + +interface IStateToProps { + error: string; + featureId: string; + loaded: boolean; + pending: boolean; + runs: IFeatureRun[]; +} + +interface IDispatchToProps { + createRun: (run: ICreateFeatureRunRequest) => void; + loadRuns: (featId: string) => void; +} + +class Page extends React.PureComponent { + public componentDidMount(): void { + if (!this.props.loaded) { + this.props.loadRuns(this.props.featureId); + } + } + + public render(): JSX.Element { + if (this.props.pending) { + return ( + Pending + ); + } + if (this.props.error) { + return ( + {this.props.error} + ); + } + if (this.props.runs.length === 0) { + return ( + + No runs currently exists, let's create a run so people can start to see the feature. + + ); + } + // finally return actual list + return ( +
+ Runs Page +
+ ); + } + + // eslint-disable-next-line @typescript-eslint/member-ordering + @autobind + private handleCreateNewRunClick(): void { + const allocation = parseInt(prompt("enter allocation", "100"), 10); + if (isNaN(allocation)) { + alert("allocation must be a number"); + return; + } + const currentTime = new Date(); + const startAt = prompt("start time: (default selected for you in UTC timezone)", currentTime.toISOString()); + currentTime.setDate(currentTime.getDate() + 2); + const endAt = prompt("end time: (default selected after 2 days for you in UTC timezone) (this is optional, feel free to remove it)", currentTime.toISOString()); + const request: ICreateFeatureRunRequest = { + allocation, + featId: this.props.featureId, + startAt + }; + if (endAt.trim() !== "") { + request.endAt = endAt; + } + // todo: find a way to parse date correctly, make sure timezones are handled correctly on backend. + this.props.createRun(request); + } +} + +const mapStateToProps = (state: Pick): IStateToProps => { + const route: IRouterState = createRouteNodeSelector("")(state).route; + return { + error: state.featureRuns.error, + featureId: route.params.featId, + loaded: state.featureRuns.loaded, + pending: state.featureRuns.pending, + runs: state.featureRuns.runs + }; +}; + +const mapDispatchToProps = (dispatch: Dispatch): IDispatchToProps => { + return { + createRun: (run: ICreateFeatureRunRequest) => dispatch(createFeatureRunForFeature.invoke(run)), + loadRuns: (featId: string) => dispatch(fetchFeatureRunsForFeature.invoke(featId)) + }; +}; + +export const RunsPage = connect(mapStateToProps, mapDispatchToProps)(Page); diff --git a/src/app/sagas/FeatureRunSaga.ts b/src/app/sagas/FeatureRunSaga.ts index cffa33b..e5a7b80 100644 --- a/src/app/sagas/FeatureRunSaga.ts +++ b/src/app/sagas/FeatureRunSaga.ts @@ -53,6 +53,7 @@ export class FeatureRunSaga extends BaseSaga { } protected* registerListeners(): IterableIterator { - return yield takeLatest(getType(fetchFeatureRunsForFeature.invoke), this.fetchRuns); + yield takeLatest(getType(fetchFeatureRunsForFeature.invoke), this.fetchRuns); + yield takeLatest(getType(createFeatureRunForFeature.invoke), this.createFeatureRun); } }