diff --git a/pkg/app/web/src/components/login-form.stories.tsx b/pkg/app/web/src/components/login-form.stories.tsx new file mode 100644 index 0000000000..4d8b370065 --- /dev/null +++ b/pkg/app/web/src/components/login-form.stories.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { LoginForm } from "./login-form"; + +export default { + title: "LoginForm", + component: LoginForm, +}; + +export const overview: React.FC = () => ; diff --git a/pkg/app/web/src/components/login-form.tsx b/pkg/app/web/src/components/login-form.tsx new file mode 100644 index 0000000000..2a17d98553 --- /dev/null +++ b/pkg/app/web/src/components/login-form.tsx @@ -0,0 +1,84 @@ +import React, { FC, memo } from "react"; +import { makeStyles, TextField, Button, Typography } from "@material-ui/core"; +import { STATIC_LOGIN_ENDPOINT } from "../constants"; +import { useProjectName, clearProjectName } from "../modules/login"; +import { useDispatch } from "react-redux"; + +const useStyles = makeStyles((theme) => ({ + root: { + display: "flex", + alignItems: "center", + flexDirection: "column", + flex: 1, + }, + form: { + display: "flex", + flexDirection: "column", + textAlign: "center", + marginTop: theme.spacing(4), + width: 320, + }, + fields: { + display: "flex", + flexDirection: "column", + marginTop: theme.spacing(4), + }, + buttons: { + display: "flex", + justifyContent: "flex-end", + marginTop: theme.spacing(3), + }, +})); + +export const LoginForm: FC = memo(function LoginForm() { + const classes = useStyles(); + const dispatch = useDispatch(); + const projectName = useProjectName(); + + const handleReset = (): void => { + dispatch(clearProjectName()); + }; + + return ( +
+ Sign in to {projectName} +
+ + + +
+ + +
+ +
+ ); +}); diff --git a/pkg/app/web/src/modules/index.ts b/pkg/app/web/src/modules/index.ts index 9d6b492292..2e56ecb0e8 100644 --- a/pkg/app/web/src/modules/index.ts +++ b/pkg/app/web/src/modules/index.ts @@ -12,6 +12,7 @@ import { commandsSlice } from "./commands"; import { applicationFilterOptionsSlice } from "./application-filter-options"; import { meSlice } from "./me"; import { deploymentFilterOptionsSlice } from "./deployment-filter-options"; +import { loginSlice } from "./login"; export const reducers = combineReducers({ deployments: deploymentsSlice.reducer, @@ -26,6 +27,7 @@ export const reducers = combineReducers({ commands: commandsSlice.reducer, toasts: toastsSlice.reducer, me: meSlice.reducer, + login: loginSlice.reducer, }); export type AppState = ReturnType; diff --git a/pkg/app/web/src/modules/login.test.ts b/pkg/app/web/src/modules/login.test.ts new file mode 100644 index 0000000000..39af47275e --- /dev/null +++ b/pkg/app/web/src/modules/login.test.ts @@ -0,0 +1,15 @@ +import { loginSlice } from "./login"; + +describe("loginSlice reducer", () => { + it("should handle initial state", () => { + expect( + loginSlice.reducer(undefined, { + type: "TEST_ACTION", + }) + ).toMatchInlineSnapshot(` + Object { + "projectName": null, + } + `); + }); +}); diff --git a/pkg/app/web/src/modules/login.ts b/pkg/app/web/src/modules/login.ts new file mode 100644 index 0000000000..cdbdbb5adf --- /dev/null +++ b/pkg/app/web/src/modules/login.ts @@ -0,0 +1,32 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { AppState } from "."; +import { useSelector } from "react-redux"; + +export interface LoginState { + projectName: string | null; +} + +const initialState: LoginState = { + projectName: null, +}; + +export const loginSlice = createSlice({ + name: "login", + initialState, + reducers: { + setProjectName(state, action: PayloadAction) { + state.projectName = action.payload; + }, + clearProjectName(state) { + state.projectName = null; + }, + }, +}); + +export const { clearProjectName, setProjectName } = loginSlice.actions; + +export const useProjectName = (): string | null => { + return useSelector( + (state) => state.login.projectName + ); +}; diff --git a/pkg/app/web/src/pages/login.tsx b/pkg/app/web/src/pages/login.tsx index 8ac79754e0..ca9b3e614a 100644 --- a/pkg/app/web/src/pages/login.tsx +++ b/pkg/app/web/src/pages/login.tsx @@ -1,66 +1,92 @@ -import { Button, makeStyles, TextField } from "@material-ui/core"; -import React, { FC, memo } from "react"; -import { Redirect } from "react-router"; -import { PAGE_PATH_APPLICATIONS, STATIC_LOGIN_ENDPOINT } from "../constants"; +import { + Button, + Card, + makeStyles, + TextField, + Typography, +} from "@material-ui/core"; +import ArrowRightAltIcon from "@material-ui/icons/ArrowRightAlt"; +import React, { FC, memo, useState } from "react"; +import { useDispatch } from "react-redux"; +import { LoginForm } from "../components/login-form"; +import { setProjectName, useProjectName } from "../modules/login"; import { useMe } from "../modules/me"; +import { PAGE_PATH_APPLICATIONS } from "../constants"; +import { Redirect } from "react-router-dom"; const useStyles = makeStyles((theme) => ({ root: { padding: theme.spacing(2), + display: "flex", + alignItems: "center", + justifyContent: "center", + flex: 1, }, - form: { + content: { display: "flex", flexDirection: "column", + padding: theme.spacing(3), + width: 500, + textAlign: "center", + }, + fields: { + display: "flex", + flexDirection: "column", + marginTop: theme.spacing(4), }, buttons: { display: "flex", justifyContent: "flex-end", + marginTop: theme.spacing(4), }, })); export const LoginPage: FC = memo(function LoginPage() { const classes = useStyles(); + const dispatch = useDispatch(); const me = useMe(); + const projectName = useProjectName(); + const [name, setName] = useState(""); + + const handleOnContinue = (): void => { + dispatch(setProjectName(name)); + }; return (
{me && me.isLogin && } -
- - - -
- -
- + + {projectName === null ? ( + <> + Sign in to your project +
+ setName(e.currentTarget.value)} + /> +
+
+ +
+ + ) : ( + + )} +
); });