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)}
+ />
+
+
+ }
+ onClick={handleOnContinue}
+ disabled={name === ""}
+ >
+ Continue
+
+
+ >
+ ) : (
+
+ )}
+
);
});