Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add login feature to Zabo Boards #9

Merged
merged 10 commits into from
May 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
## VITE_API_SERVER_URL should end without "/"
VITE_API_SERVER_URL=
VITE_ANIMATION_DURATION=
VITE_TRANSITION_INTERVAL=
VITE_TRANSITION_INTERVAL=

## put s3 url for initial content for zabo boards
VITE_INIT_CONTENT1=
VITE_INIT_CONTENT2=
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@
"prefer": "type-imports"
}
],
"arrow-body-style": ["warn"]
"arrow-body-style": ["warn"],
"jsx-a11y/label-has-associated-control": [ 2, {
jinho-choi123 marked this conversation as resolved.
Show resolved Hide resolved
"some": [ "nesting", "id" ]
}]
},
"settings": {
"import/parsers": {
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

### Node ###
# Logs
.idea
.uuid
logs
*.log
npm-debug.log*
Expand Down
1 change: 1 addition & 0 deletions src/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@
.read-the-docs {
color: #888;
}

4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Board } from "@/components";
import { AuthPage } from "@/components";
import { Provider } from "react-redux";
import { store } from "@/redux/store";

const App = () => (
<Provider store={store}>
<Board />
<AuthPage />
</Provider>
);

Expand Down
46 changes: 46 additions & 0 deletions src/components/Auth/Auth.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@import "src/styles";

.modalWrapper {
display: flex;
justify-content: center;
align-items: center;
padding-top: 500px;
}

.modalContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 300px;
max-width: 1800px;
}

.modalTitle {
font-size: 100px;
}

.modalContent {
font-size: 50px;
}

.form {
display: flex;
flex-direction: column;
margin-top: 100px;
}

.input {
font-size: 50px;
margin: 10px;
}

.submit {
margin-top: 20px;
font-size: 50px;
}

.errorBox {
font-size: 50px;
color: red;
}
46 changes: 46 additions & 0 deletions src/components/Auth/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { type ChangeEvent, useState } from "react";
import { useAppDispatch, useAppSelector } from "@/types";
import { type RootState } from "@/redux/store";
import { Board } from "@/components/Board";
import { loginThunk } from "@/redux/auth/loginThunk";
import { LoginPage } from "@/components/Auth/LoginPage";

export const AuthPage = () => {
const [deviceId, setDeviceId] = useState("");
const [pin, setPin] = useState("");

const isLoggedIn = useAppSelector(
(state: RootState) => state.auth.isLoggedIn,
);

const errorMessage = useAppSelector(
(state: RootState) => state.auth.errorMessage,
);

const onDeviceIdChange = (e: ChangeEvent<HTMLInputElement>) => {
setDeviceId(e.target.value);
};

const onPinChange = (e: ChangeEvent<HTMLInputElement>) => {
setPin(e.target.value);
};

const dispatch = useAppDispatch();

const onSubmitHandler = () => {
dispatch(loginThunk(deviceId, pin));
setDeviceId("");
setPin("");
};

return isLoggedIn ? (
<Board />
) : (
<LoginPage
errorMessage={errorMessage}
onDeviceIdChange={onDeviceIdChange}
onPinChange={onPinChange}
onSubmitHandler={onSubmitHandler}
/>
);
};
53 changes: 53 additions & 0 deletions src/components/Auth/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { ChangeEvent } from "react";
import style from "./Auth.module.scss";

type LoginPageProps = {
onDeviceIdChange: (e: ChangeEvent<HTMLInputElement>) => void;
onPinChange: (e: ChangeEvent<HTMLInputElement>) => void;
onSubmitHandler: () => void;
errorMessage: string;
};

export const LoginPage = ({
onDeviceIdChange,
onPinChange,
onSubmitHandler,
errorMessage,
}: LoginPageProps) => (
<div className={style.modalWrapper}>
<div className={style.modalContainer}>
<h1 className={style.modalTitle}> Zabo Board Login</h1>
<br />
<p className={style.modalContent}>
Zabo Board 서비스를 이용하기 위해서는 등록된 device Id와 PIN을
입력하세요.
<br />
Zabo Board 서비스를 등록하기 위해서는 zabo.sparcs.org 채널톡으로
문의해주세요.
</p>
<div className={style.form}>
<label className={style.input}>
device Id
<input
className={style.input}
name="deviceId"
onChange={onDeviceIdChange}
/>
</label>
<label className={style.input}>
PIN
<input
className={style.input}
type="password"
name="PIN"
onChange={onPinChange}
/>
</label>
</div>
<button type="submit" className={style.submit} onClick={onSubmitHandler}>
Submit
</button>
<div className={style.errorBox}>{errorMessage}</div>
</div>
</div>
);
1 change: 1 addition & 0 deletions src/components/Auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./Auth";
8 changes: 5 additions & 3 deletions src/components/Board/Board.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { useInterval } from "@/hooks";
import { moveToNext, type ZaboListState } from "@/redux/zabos/zaboSlice";
import { moveToNext } from "@/redux/zabos/zaboSlice";
import { fetchZaboThunk } from "@/redux/zabos/fetchZaboThunk";
import { useAppSelector, useAppDispatch, type ZaboJson } from "@/types";
import { Zabo } from "@/components/Zabo";
Expand All @@ -9,12 +9,14 @@ import { Background } from "@/components/Background";
import { Qr } from "@/components/Qr";
import { Logo } from "@/components/Logo";
import { TRANSITION_INTERVAL } from "@/config";
import { type RootState } from "@/redux/store";

import style from "./Board.module.scss";

export const Board = () => {
const zaboList = useAppSelector((state: ZaboListState) => state.zaboList);
const zaboList = useAppSelector((state: RootState) => state.zabo.zaboList);
const leftOverZaboLength = useAppSelector(
(state: ZaboListState) => state.leftoverLength,
(state: RootState) => state.zabo.leftoverLength,
);

const dispatch = useAppDispatch();
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./Board";
export * from "./Auth";
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export const API_SERVER_URL = import.meta.env.VITE_API_SERVER_URL;
export const ANIMATION_DURATION = import.meta.env.VITE_ANIMATION_DURATION;
export const TRANSITION_INTERVAL = import.meta.env.VITE_TRANSITION_INTERVAL;

export const INIT_CONTENT1 = import.meta.env.VITE_INIT_CONTENT1;
export const INIT_CONTENT2 = import.meta.env.VITE_INIT_CONTENT2;
28 changes: 28 additions & 0 deletions src/redux/auth/authSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createSlice } from "@reduxjs/toolkit";

export interface AuthState {
isLoggedIn: boolean;
errorMessage: string;
}

const initialState: AuthState = {
isLoggedIn: false,
errorMessage: "",
};

const authSlice = createSlice({
name: "auth",
initialState,
reducers: {
setIsLoggedIn: (state, action) => {
state.isLoggedIn = action.payload;
},
setErrorMessage: (state, action) => {
state.errorMessage = action.payload;
},
},
});

export const { setIsLoggedIn, setErrorMessage } = authSlice.actions;

export const authReducer = authSlice.reducer;
24 changes: 24 additions & 0 deletions src/redux/auth/loginThunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { type AppDispatch } from "@/redux/store";
import axios from "axios";
import { setIsLoggedIn, setErrorMessage } from "./authSlice";

// send device id and pin to server and get session cookie
export const loginThunk =
(deviceId: string, pin: string) => async (dispatch: AppDispatch) => {
// request zabo board login and get response

// mock api request time
const response = await axios.post(`/api/board/login`, {
name: deviceId,
password: pin,
});

const isLoginSuccess = response.data.success;

// if success, then set zabo store's isLogin to true
if (isLoginSuccess) {
dispatch(setIsLoggedIn(true));
} else {
dispatch(setErrorMessage(response.data.error));
}
};
13 changes: 13 additions & 0 deletions src/redux/rootReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { combineReducers } from "@reduxjs/toolkit";
import { type ZaboListState, zaboReducer } from "./zabos/zaboSlice";
import { type AuthState, authReducer } from "./auth/authSlice";

export interface RootState {
zabo: ZaboListState;
auth: AuthState;
}

export const rootReducer = combineReducers({
zabo: zaboReducer,
auth: authReducer,
});
4 changes: 2 additions & 2 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { configureStore } from "@reduxjs/toolkit";
import { zaboReducer } from "./zabos/zaboSlice";
import { rootReducer } from "./rootReducer";

export const store = configureStore({
reducer: zaboReducer,
reducer: rootReducer,
});

// Infer the `RootState` and `AppDispatch` types from the store itself
Expand Down
Loading