From a33006e0b90fe22ea31bc0d5945d6ced7185e413 Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Thu, 3 Aug 2023 19:45:49 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E5=89=B5=E5=BB=BA=E6=88=BF=E9=96=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/js/api/index.js | 15 +++ packages/frontend/js/common/axiosInstance.js | 26 ++++ packages/frontend/js/components/App.jsx | 11 +- packages/frontend/js/components/Modal.jsx | 22 ++++ .../frontend/js/components/Pagination.jsx | 123 +++++++++++++++++ packages/frontend/js/components/RoomList.jsx | 124 ++++++++++++++++++ packages/frontend/js/redux/slice/roomSlice.js | 64 +++++++++ packages/frontend/js/redux/store/index.js | 4 +- packages/frontend/js/routers/index.js | 4 +- packages/frontend/src/scss/_Pagination.scss | 59 +++++++++ packages/frontend/src/scss/_init.scss | 90 +++++++++++++ packages/frontend/src/scss/_roomList.scss | 119 +++++++++++++++++ packages/frontend/src/scss/index.scss | 2 + 13 files changed, 658 insertions(+), 5 deletions(-) create mode 100644 packages/frontend/js/common/axiosInstance.js create mode 100644 packages/frontend/js/components/Modal.jsx create mode 100644 packages/frontend/js/components/Pagination.jsx create mode 100644 packages/frontend/js/components/RoomList.jsx create mode 100644 packages/frontend/js/redux/slice/roomSlice.js create mode 100644 packages/frontend/src/scss/_Pagination.scss create mode 100644 packages/frontend/src/scss/_roomList.scss diff --git a/packages/frontend/js/api/index.js b/packages/frontend/js/api/index.js index e69de29b..c5bd5440 100644 --- a/packages/frontend/js/api/index.js +++ b/packages/frontend/js/api/index.js @@ -0,0 +1,15 @@ +import axiosInstance from '../common/axiosInstance' + +const url = + process.env.NODE_ENV === 'development' + ? 'https://001f08b9-acb7-4c3a-a54f-a9254b7e8e55.mock.pstmn.io' + : '' +const axios = axiosInstance(url) + +export const getRoomList = (payload) => { + return axios.get('/games', payload) +} + +export const createRoom = (payload) => { + return axios.post('/createroom', payload) +} diff --git a/packages/frontend/js/common/axiosInstance.js b/packages/frontend/js/common/axiosInstance.js new file mode 100644 index 00000000..36801d1c --- /dev/null +++ b/packages/frontend/js/common/axiosInstance.js @@ -0,0 +1,26 @@ +import axios from 'axios' + +export default (baseUrl) => { + const instance = axios.create({ + baseURL: baseUrl + }) + instance.interceptors.response.use( + (res) => { + if (res && res.status === 200) { + return res.data + } + }, + (error) => { + if (error.response) { + switch (error.response.status) { + case 404: + break + default: + console.log(error.message) + } + } + return Promise.reject(error) + } + ) + return instance +} diff --git a/packages/frontend/js/components/App.jsx b/packages/frontend/js/components/App.jsx index 18b750b8..df50e29b 100644 --- a/packages/frontend/js/components/App.jsx +++ b/packages/frontend/js/components/App.jsx @@ -1,10 +1,19 @@ +import { Routes, Route, Navigate } from 'react-router-dom' +import RoomList from './RoomList' +import Game from './Game' + import getAPI from '../api/getAPI' const App = () => { const { data } = getAPI() return ( <> -
title:{data.msg}
+ {/*
title:{data.msg}
*/} + + } /> + } /> + } /> + ) } diff --git a/packages/frontend/js/components/Modal.jsx b/packages/frontend/js/components/Modal.jsx new file mode 100644 index 00000000..474ab230 --- /dev/null +++ b/packages/frontend/js/components/Modal.jsx @@ -0,0 +1,22 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const Modal = (props) => { + const { isModalOpen, modalContent } = props + return ( + <> + {isModalOpen && ( +
+
{modalContent}
+
+ )} + + ) +} + +Modal.propTypes = { + isModalOpen: PropTypes.bool.isRequired, + modalContent: PropTypes.node.isRequired +} + +export default Modal diff --git a/packages/frontend/js/components/Pagination.jsx b/packages/frontend/js/components/Pagination.jsx new file mode 100644 index 00000000..111ad47f --- /dev/null +++ b/packages/frontend/js/components/Pagination.jsx @@ -0,0 +1,123 @@ +import { useState } from 'react' + +/** + * Render pagination + * @param {Object} props + * @param {number} props.totalPage - total page + * @param {number} props.current - current page + * @param {function} props.onPrevClick - callback triggers when click prev btn + * @param {function} props.onNextClick - callback triggers when click next btn + * @param {string} props.className - classname applies on wrapper of pagination + */ + +const Pagination = ({ + totalPage, + current, + onPrevClick, + onNextClick, + className = '' +}) => { + const [currentPage, setCurrentPage] = useState(current) + const renderPrevTabs = () => { + const pages = [] + let count = 0 + const maxCount = 2 + for (let i = currentPage - 1; i >= 1; i--) { + if (count === maxCount && i !== 1) { + pages.push( +
+ ••• +
+ ) + pages.push( +
handleClickPage(1)} className='page-btn'> + 1 +
+ ) + break + } else { + pages.push( +
handleClickPage(i)} className='page-btn'> + {i} +
+ ) + count++ + } + } + return pages.reverse() + } + const renderNextTabs = () => { + const pages = [] + let count = 0 + const maxCount = 2 + for (let i = currentPage + 1; i <= totalPage; i++) { + if (count === maxCount && i !== totalPage) { + pages.push( +
+ ••• +
+ ) + pages.push( +
handleClickPage(totalPage)} + className='page-btn' + > + {totalPage} +
+ ) + break + } else { + pages.push( +
handleClickPage(i)} className='page-btn'> + {i} +
+ ) + count++ + } + } + return pages + } + const handleClickPage = (page) => { + setCurrentPage(page) + } + const handleClickPrev = () => { + if (currentPage > 1) { + setCurrentPage((prev) => prev - 1) + if (onPrevClick) onPrevClick() + } + } + const handleClickNext = () => { + if (currentPage < totalPage) { + setCurrentPage((prev) => prev + 1) + if (onNextClick) onNextClick() + } + } + return ( +
+
+ {renderPrevTabs()} + { +
handleClickPage(currentPage)} + className='page-btn active' + > + {currentPage} +
+ } + {renderNextTabs()} +
+
+ ) +} + +export default Pagination + +Pagination.propTypes = { + totalPage: PropTypes.number, + current: PropTypes.number, + onPrevClick: PropTypes.func, + onNextClick: PropTypes.func, + className: PropTypes.string +} diff --git a/packages/frontend/js/components/RoomList.jsx b/packages/frontend/js/components/RoomList.jsx new file mode 100644 index 00000000..99e7267d --- /dev/null +++ b/packages/frontend/js/components/RoomList.jsx @@ -0,0 +1,124 @@ +import { useState, useEffect } from 'react' +import Pagination from './Pagination' +import { useDispatch, useSelector } from 'react-redux' +import { getRoomList, createRoom } from '../redux/slice/roomSlice' +import Modal from './Modal' + +const RoomList = () => { + const dispatch = useDispatch() + const { data = [] } = useSelector((state) => state.room) + const isOdd = (totalAmout) => totalAmout % 2 !== 0 + useEffect(() => { + let init = true + if (init) dispatch(getRoomList()) + return () => { + init = false + } + }, []) + + // 創建房間 + const [isModalOpen, setIsModalOpen] = useState(false) + const showModal = () => { + console.log('isModalOpen', isModalOpen) + setIsModalOpen(!isModalOpen) + } + + const [newRoom, setNewRoom] = useState({ + playerName: '', + roomName: '' + }) + + const handleRoomNameChange = (e) => { + setNewRoom({ + ...newRoom, + [e.target.name]: e.target.value + }) + } + + const handleSubmitRoomName = (event) => { + event.preventDefault() + console.log('roomName', newRoom) + dispatch(createRoom(newRoom)) + } + const modalContent = ( +
+
+ + handleRoomNameChange(e)} + placeholder='輸入房間名稱' + className='main_input' + /> +
+ +
+ + +
+
+ ) + + return ( +
+
+
+ + 王老先生 +
+
{ + showModal() + }} + > + + 創建房間 +
+
+
+ {!data &&
loading ...
} + {data?.map((room, index) => ( +
+
+
+
房主
+
{room.holderName}
+
+
+
人數
+
{room.totalPlayers}
+
+
+ ))} + {isOdd(data?.length) &&
} +
+
+ +
+ +
+ ) +} + +export default RoomList diff --git a/packages/frontend/js/redux/slice/roomSlice.js b/packages/frontend/js/redux/slice/roomSlice.js new file mode 100644 index 00000000..69e50c9f --- /dev/null +++ b/packages/frontend/js/redux/slice/roomSlice.js @@ -0,0 +1,64 @@ +import { createSlice, createAsyncThunk } from '@reduxjs/toolkit' +import * as api from '../../api' + +export const getRoomList = createAsyncThunk( + 'room/getRoomList', + async (payload, { rejectWithValue }) => { + try { + const res = await api.getRoomList(payload) + console.log(res) + if (res.status === 'OK') { + return res + } else { + return rejectWithValue(res.msg) + } + } catch (error) { + return rejectWithValue(error) + } + } +) + +export const createRoom = createAsyncThunk( + 'room/createRoom', + async (payload, { rejectWithValue }) => { + try { + const res = await api.createRoom(payload) + console.log(res) + if (res.status === 'OK') { + console.log('OK') + return res + } else { + return rejectWithValue(res.msg) + } + } catch (error) { + return rejectWithValue(error) + } + } +) + +const roomSlice = createSlice({ + name: 'room', + initialState: { + data: null + }, + reducers: { + clearData: (state, action) => { + state.data = null + } + }, + extraReducers: (builder) => { + builder + .addCase(getRoomList.fulfilled, (state, action) => { + state.data = action.payload.rooms + return state + }) + .addCase(createRoom.fulfilled, (state, action) => { + state.data = [...state.data, action.payload] + return state + }) + } +}) + +export const { clearData } = roomSlice.actions + +export default roomSlice.reducer diff --git a/packages/frontend/js/redux/store/index.js b/packages/frontend/js/redux/store/index.js index 160a140b..c4553259 100644 --- a/packages/frontend/js/redux/store/index.js +++ b/packages/frontend/js/redux/store/index.js @@ -1,8 +1,10 @@ import { configureStore } from '@reduxjs/toolkit' import mainReducer from '../slice/mainSlice' +import roomReducer from '../slice/roomSlice' export default configureStore({ reducer: { - main: mainReducer + main: mainReducer, + room: roomReducer } }) diff --git a/packages/frontend/js/routers/index.js b/packages/frontend/js/routers/index.js index 69786f8d..3bbadd68 100644 --- a/packages/frontend/js/routers/index.js +++ b/packages/frontend/js/routers/index.js @@ -1,15 +1,13 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom' import App from '../components/App' import Login from '../components/Login' -import Game from '../components/Game' const Routers = () => { return ( - } /> } /> - } /> + } /> ) diff --git a/packages/frontend/src/scss/_Pagination.scss b/packages/frontend/src/scss/_Pagination.scss new file mode 100644 index 00000000..c43bf169 --- /dev/null +++ b/packages/frontend/src/scss/_Pagination.scss @@ -0,0 +1,59 @@ +.pagination { + @include flex-center-row; + gap: 9px; + .prev-btn, + .next-btn { + cursor: pointer; + background-color: $cyan; + width: 38px; + height: 38px; + border-radius: 50%; + position: relative; + &::after, + &::before { + content: ''; + display: inline-block; + background-image: $g-blue; + position: absolute; + width: 11px; + height: 2px; + border-radius: 5px; + left: 12px; + top: 15px; + transform: rotate(320deg); + } + &::before { + transform: rotate(220deg); + top: 21px; + } + } + .next-btn { + &::after { + transform: rotate(220deg); + left: unset; + right: 12px; + } + &::before { + transform: rotate(135deg); + left: unset; + right: 12px; + } + } + .page-btn { + cursor: pointer; + color: $blue; + width: 38px; + height: 38px; + background-color: $light-blue; + border-radius: 50%; + @include flex-center-row; + font-size: 20px; + &.active { + background-color: #dfda5d; + cursor: auto; + } + } + .ellipsis { + color: #fff; + } +} diff --git a/packages/frontend/src/scss/_init.scss b/packages/frontend/src/scss/_init.scss index 65c8a8ab..ecc73d2a 100644 --- a/packages/frontend/src/scss/_init.scss +++ b/packages/frontend/src/scss/_init.scss @@ -10,6 +10,7 @@ /*g means gradient */ $g-blue: linear-gradient(45deg, #7e66c5, #567ecc); $g-yellow: linear-gradient(45deg, #f8f148, #fcd139); +$g-gray: linear-gradient(217deg, #cbcabc 0%, #aca99e 100%); $blue: #5891ff; $cyan: #8fdbe0; $light-blue: #ecf4f9; @@ -37,6 +38,13 @@ $purple: #514673; font-family: Inter; font-weight: 600; } +@mixin f-20-w { + color: $white; + font-size: clamp(0.75rem, 1.25rem, 1.75rem); + font-family: Inter; + font-weight: 600; +} + @mixin f-40-w { color: $white; font-size: clamp(1.5rem, 2.5rem, 3rem); @@ -72,6 +80,18 @@ $purple: #514673; align-items: center; } +/*定義rwd斷點*/ +@mixin md { + @media screen and (max-width: 768px) { + @content; + } +} +@mixin lg { + @media screen and (max-width: 992px) { + @content; + } +} + /* 拔除input樣式 */ input[type='number']::-webkit-outer-spin-button, myAuth input[type='number']::-webkit-inner-spin-button { @@ -90,6 +110,76 @@ input:focus { outline: none !important; } +img { + max-width: 100%; + object-fit: cover; +} + body { background-image: linear-gradient(45deg, #7e66c5, #567ecc); + background-repeat: no-repeat; + // height: 100vh; +} + +.bg { + &--trans { + background-color: transparent !important; + } +} + +/*輸入欄樣式*/ +.main_input { + width: min(256px, 320px); + height: 54px; + border: 0px; + border-radius: 300px; + padding: 0 20px; + @include f-24-b; +} + +/*彈跳視窗*/ +.custom-modal { + position: fixed; + width: 100%; + height: 100%; + top: 0px; + bottom: 0px; + right: 0px; + left: 0px; + background-color: rgba(86, 87, 123, 0.8); + z-index: 100; + display: flex; + justify-content: center; + align-items: center; +} +.custom-modal-content { + width: auto; + height: min(300px, 500px); + background-color: $light-blue; + border-radius: 20px; + padding: 50px; + display: flex; + justify-content: center; + align-items: center; +} + +/*可共用按鈕*/ +.cancel-btn { + width: min(100px, 200px); + height: 40px; + border-radius: 30px; + background-image: $g-gray; + border: none; + @include f-20-w; + cursor: pointer; +} + +.blue-btn { + width: min(160px, 300px); + height: 40px; + border-radius: 30px; + background-image: $g-blue; + border: none; + @include f-20-w; + cursor: pointer; } diff --git a/packages/frontend/src/scss/_roomList.scss b/packages/frontend/src/scss/_roomList.scss new file mode 100644 index 00000000..91abf176 --- /dev/null +++ b/packages/frontend/src/scss/_roomList.scss @@ -0,0 +1,119 @@ +.room-list { + .navbar { + padding: 30px 40px; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 50px; + &__role { + display: flex; + align-items: center; + gap: 17px; + .avatar { + border-radius: 50%; + width: 52px; + height: 52px; + background-color: white; + display: inline-block; + } + .name { + color: #fff; + font-weight: 600; + font-size: 20px; + } + } + &__btn { + position: relative; + .cross-btn { + background-color: $cyan; + width: 53px; + height: 53px; + border-radius: 50%; + position: absolute; + left: -18px; + top: -13px; + &::before, + &::after { + content: ''; + display: inline-block; + width: 25px; + height: 5px; + border-radius: 10px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #567ecc; + } + &::after { + width: 5px; + height: 25px; + } + } + .text { + border-radius: 20px; + background-color: $blue; + color: #fff; + font-size: 1.5rem; + font-weight: 600; + padding: 5px 17px 5px 44px; + cursor: pointer; + } + } + } + .list__card { + display: flex; + align-items: center; + padding-left: 50px; + gap: 21px; + font-weight: 600; + font-size: 20px; + line-height: 30px; + width: 525px; + background-color: rgba(252, 252, 252, 0.67); + height: 171px; + border-radius: 30px; + filter: drop-shadow(0px 4px 4px rgba(64, 109, 175, 0.25)); + flex: 1 40%; + div .title { + color: #737787; + } + div .detail { + color: #414555; + } + .avatar { + border-radius: 50%; + width: 52px; + height: 52px; + background-color: white; + display: inline-block; + } + .host { + flex: 2; + } + .players { + flex: 2; + } + } + .row { + &__list { + @include flex-center-row; + gap: 28px; + flex-wrap: wrap; + &.list { + max-width: 1078px; + margin: 0 auto; + } + } + &__pagination { + margin: 20px 0; + } + } +} + +.createRoom { + width: 100%; + display: flex; + flex-direction: column; + gap: 20px; +} diff --git a/packages/frontend/src/scss/index.scss b/packages/frontend/src/scss/index.scss index 425480a1..90278c8a 100644 --- a/packages/frontend/src/scss/index.scss +++ b/packages/frontend/src/scss/index.scss @@ -1,3 +1,5 @@ @import './_init.scss'; @import './_login.scss'; @import './_game.scss'; +@import './_roomList'; +@import './Pagination'; From 93895488350e6969f66ea831f5e16dac22ea9764 Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Thu, 10 Aug 2023 22:08:04 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E8=AA=BF=E6=95=B4=E5=89=B5=E5=BB=BAAPI?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/js/components/RoomList.jsx | 19 +++++++++--- packages/frontend/js/redux/slice/roomSlice.js | 31 +++---------------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/packages/frontend/js/components/RoomList.jsx b/packages/frontend/js/components/RoomList.jsx index 99e7267d..91afc850 100644 --- a/packages/frontend/js/components/RoomList.jsx +++ b/packages/frontend/js/components/RoomList.jsx @@ -1,7 +1,9 @@ import { useState, useEffect } from 'react' +import { useNavigate } from 'react-router-dom' import Pagination from './Pagination' import { useDispatch, useSelector } from 'react-redux' -import { getRoomList, createRoom } from '../redux/slice/roomSlice' +import { getRoomList } from '../redux/slice/roomSlice' +import { createRoom } from '../api' import Modal from './Modal' const RoomList = () => { @@ -34,11 +36,20 @@ const RoomList = () => { [e.target.name]: e.target.value }) } - + const navigate = useNavigate() const handleSubmitRoomName = (event) => { event.preventDefault() - console.log('roomName', newRoom) - dispatch(createRoom(newRoom)) + createRoom() + .then((res) => { + if (res.status === 'OPEN') { + navigate('/game') + // TODO : 加入URLParamter + } + }) + .catch((err) => { + console.log(err) + // TODO : showAnotherModal + }) } const modalContent = (
diff --git a/packages/frontend/js/redux/slice/roomSlice.js b/packages/frontend/js/redux/slice/roomSlice.js index 69e50c9f..004320e2 100644 --- a/packages/frontend/js/redux/slice/roomSlice.js +++ b/packages/frontend/js/redux/slice/roomSlice.js @@ -18,24 +18,6 @@ export const getRoomList = createAsyncThunk( } ) -export const createRoom = createAsyncThunk( - 'room/createRoom', - async (payload, { rejectWithValue }) => { - try { - const res = await api.createRoom(payload) - console.log(res) - if (res.status === 'OK') { - console.log('OK') - return res - } else { - return rejectWithValue(res.msg) - } - } catch (error) { - return rejectWithValue(error) - } - } -) - const roomSlice = createSlice({ name: 'room', initialState: { @@ -47,15 +29,10 @@ const roomSlice = createSlice({ } }, extraReducers: (builder) => { - builder - .addCase(getRoomList.fulfilled, (state, action) => { - state.data = action.payload.rooms - return state - }) - .addCase(createRoom.fulfilled, (state, action) => { - state.data = [...state.data, action.payload] - return state - }) + builder.addCase(getRoomList.fulfilled, (state, action) => { + state.data = action.payload.rooms + return state + }) } }) From b5e2b599122dc03080097b8657e380859a0875d6 Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Thu, 10 Aug 2023 22:10:45 +0800 Subject: [PATCH 03/28] =?UTF-8?q?=E5=BE=AE=E8=AA=BFButtonPadding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/scss/_init.scss | 3 +++ packages/frontend/src/scss/_roomList.scss | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/packages/frontend/src/scss/_init.scss b/packages/frontend/src/scss/_init.scss index ecc73d2a..660df2d5 100644 --- a/packages/frontend/src/scss/_init.scss +++ b/packages/frontend/src/scss/_init.scss @@ -172,6 +172,8 @@ body { border: none; @include f-20-w; cursor: pointer; + padding-top: 3px; + } .blue-btn { @@ -182,4 +184,5 @@ body { border: none; @include f-20-w; cursor: pointer; + padding-top: 3px; } diff --git a/packages/frontend/src/scss/_roomList.scss b/packages/frontend/src/scss/_roomList.scss index 91abf176..b3e81198 100644 --- a/packages/frontend/src/scss/_roomList.scss +++ b/packages/frontend/src/scss/_roomList.scss @@ -117,3 +117,9 @@ flex-direction: column; gap: 20px; } + +.button_group{ + display: flex; + justify-content: center; + gap: 30px; +} \ No newline at end of file From 0ecb1b5d56c81ae97bf9a0e8b8de6decc350dc01 Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Thu, 10 Aug 2023 22:45:13 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E5=89=B5=E5=BB=BA=E6=88=BF=E9=96=93?= =?UTF-8?q?=E5=A4=B1=E6=95=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../frontend/js/components/ErrorModal.jsx | 28 +++++++++++++++++ packages/frontend/js/components/RoomList.jsx | 12 +++++++- packages/frontend/src/scss/_init.scss | 30 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 packages/frontend/js/components/ErrorModal.jsx diff --git a/packages/frontend/js/components/ErrorModal.jsx b/packages/frontend/js/components/ErrorModal.jsx new file mode 100644 index 00000000..72fe6591 --- /dev/null +++ b/packages/frontend/js/components/ErrorModal.jsx @@ -0,0 +1,28 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const ErrorModal = (props) => { + const { isErrorVisible, onClose, errorText } = props + return ( + <> + {isErrorVisible && ( +
+
+ {errorText} + +
+
+ )} + + ) +} + +ErrorModal.propTypes = { + isErrorVisible: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + errorText: PropTypes.node.isRequired +} + +export default ErrorModal diff --git a/packages/frontend/js/components/RoomList.jsx b/packages/frontend/js/components/RoomList.jsx index 91afc850..11aea211 100644 --- a/packages/frontend/js/components/RoomList.jsx +++ b/packages/frontend/js/components/RoomList.jsx @@ -5,6 +5,7 @@ import { useDispatch, useSelector } from 'react-redux' import { getRoomList } from '../redux/slice/roomSlice' import { createRoom } from '../api' import Modal from './Modal' +import ErrorModal from './ErrorModal' const RoomList = () => { const dispatch = useDispatch() @@ -37,6 +38,9 @@ const RoomList = () => { }) } const navigate = useNavigate() + const [isErrorVisible, setIsErrorVisible] = useState(false) + const [errorText, setErrorText] = useState('') + const handleSubmitRoomName = (event) => { event.preventDefault() createRoom() @@ -48,7 +52,8 @@ const RoomList = () => { }) .catch((err) => { console.log(err) - // TODO : showAnotherModal + setIsErrorVisible(true) + setErrorText('連線發生錯誤') }) } const modalContent = ( @@ -128,6 +133,11 @@ const RoomList = () => { + setIsErrorVisible(false)} + errorText={errorText} + > ) } diff --git a/packages/frontend/src/scss/_init.scss b/packages/frontend/src/scss/_init.scss index 660df2d5..986c5864 100644 --- a/packages/frontend/src/scss/_init.scss +++ b/packages/frontend/src/scss/_init.scss @@ -163,6 +163,36 @@ body { align-items: center; } +/*錯誤訊息彈跳視窗*/ +.error-modal { + position: fixed; + width: 100%; + height: 100%; + top: 0px; + bottom: 0px; + right: 0px; + left: 0px; + background-color: rgba(86, 87, 123, 0.8); + z-index: 200; + display: flex; + justify-content: center; + align-items: center; +} +.error-modal-content { + width: auto; + background-color: rgba(238, 238, 239, 0.8); + border-radius: 20px; + padding: 50px; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + gap: 20px; +} +.error-text{ + @include f-20-b +} + /*可共用按鈕*/ .cancel-btn { width: min(100px, 200px); From 76d0e9609b6e61b200309ea54fded41d2f774a68 Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Thu, 10 Aug 2023 22:45:42 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E5=A4=B1=E6=95=97Modal=E6=A8=A3=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/scss/_init.scss | 5 ++--- packages/frontend/src/scss/_roomList.scss | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/frontend/src/scss/_init.scss b/packages/frontend/src/scss/_init.scss index 986c5864..fa9466e1 100644 --- a/packages/frontend/src/scss/_init.scss +++ b/packages/frontend/src/scss/_init.scss @@ -189,8 +189,8 @@ body { align-items: center; gap: 20px; } -.error-text{ - @include f-20-b +.error-text { + @include f-20-b; } /*可共用按鈕*/ @@ -203,7 +203,6 @@ body { @include f-20-w; cursor: pointer; padding-top: 3px; - } .blue-btn { diff --git a/packages/frontend/src/scss/_roomList.scss b/packages/frontend/src/scss/_roomList.scss index b3e81198..bca0a8ef 100644 --- a/packages/frontend/src/scss/_roomList.scss +++ b/packages/frontend/src/scss/_roomList.scss @@ -118,8 +118,8 @@ gap: 20px; } -.button_group{ +.button_group { display: flex; justify-content: center; gap: 30px; -} \ No newline at end of file +} From c50a36cd9fb9290f2ed46c8b402c65d2a0304e9d Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Sun, 13 Aug 2023 17:56:56 +0800 Subject: [PATCH 06/28] =?UTF-8?q?ESlint=E6=8E=92=E9=99=A4Cypress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/.eslintignore | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/frontend/.eslintignore b/packages/frontend/.eslintignore index e3fbd983..cd2cd859 100644 --- a/packages/frontend/.eslintignore +++ b/packages/frontend/.eslintignore @@ -1,2 +1,3 @@ build node_modules +cypress \ No newline at end of file From 6f7b9c95f882a530d1a8deb0d63643a4000e5e3a Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Sun, 13 Aug 2023 17:57:40 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E5=88=AA=E9=99=A4CyEslint=E8=A8=BB?= =?UTF-8?q?=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/cypress/e2e/APITesting.cy.js | 3 --- packages/frontend/cypress/e2e/useMockAPI.cy.js | 7 ------- 2 files changed, 10 deletions(-) diff --git a/packages/frontend/cypress/e2e/APITesting.cy.js b/packages/frontend/cypress/e2e/APITesting.cy.js index 162087cc..6e39f9ec 100644 --- a/packages/frontend/cypress/e2e/APITesting.cy.js +++ b/packages/frontend/cypress/e2e/APITesting.cy.js @@ -1,8 +1,5 @@ -// eslint-disable-next-line no-undef describe('HTTPRequest', () => { - // eslint-disable-next-line no-undef it('GET Call', () => { - // eslint-disable-next-line no-unused-expressions, no-undef cy.request( 'GET', 'https://001f08b9-acb7-4c3a-a54f-a9254b7e8e55.mock.pstmn.io/get?msg=hello' diff --git a/packages/frontend/cypress/e2e/useMockAPI.cy.js b/packages/frontend/cypress/e2e/useMockAPI.cy.js index aa47edfe..daae2061 100644 --- a/packages/frontend/cypress/e2e/useMockAPI.cy.js +++ b/packages/frontend/cypress/e2e/useMockAPI.cy.js @@ -1,9 +1,6 @@ -// eslint-disable-next-line no-undef describe('E2E Tests to MockAPI', () => { - // eslint-disable-next-line no-undef beforeEach(() => { // 跑E2E測試時,intercept時會攔截XHR、fetch、跨域請求,將get方法呼叫的API改為放在fixtures資料夾的hello.json* - // eslint-disable-next-line no-undef cy.intercept( 'GET', 'https://001f08b9-acb7-4c3a-a54f-a9254b7e8e55.mock.pstmn.io/get?msg=hello', @@ -13,17 +10,13 @@ describe('E2E Tests to MockAPI', () => { ).as('getAPI') }) - // eslint-disable-next-line no-undef it('should load data from Local Mock API', () => { - // eslint-disable-next-line no-undef cy.visit('http://localhost:3001/') - // eslint-disable-next-line no-undef cy.wait('@getAPI', { timeout: 10000 }) .its('response.body') .then((data) => { // 在這裡設定預期從API獲取到的數據 - // eslint-disable-next-line no-undef expect(data.args.msg).to.equal('hello') }) }) From b19469b6d426670f347615f588e1feed37c4788f Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Sun, 13 Aug 2023 17:58:28 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=89=B5=E5=BB=BA?= =?UTF-8?q?=E6=88=BF=E9=96=93E2E=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/cypress/e2e/RoomTest.cy.js | 26 ++++++++++++++ packages/frontend/cypress/e2e/spec.cy.js | 8 ----- packages/frontend/cypress/support/commands.js | 35 +++++++++++++++++++ 3 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 packages/frontend/cypress/e2e/RoomTest.cy.js delete mode 100644 packages/frontend/cypress/e2e/spec.cy.js diff --git a/packages/frontend/cypress/e2e/RoomTest.cy.js b/packages/frontend/cypress/e2e/RoomTest.cy.js new file mode 100644 index 00000000..6a767209 --- /dev/null +++ b/packages/frontend/cypress/e2e/RoomTest.cy.js @@ -0,0 +1,26 @@ +beforeEach(() => { + cy.setupIntercept() +}) + +describe('Modal E2E Test', () => { + it('should open modal, input room name, send POST request, and verify response', () => { + cy.visit('http://localhost:3001/rooms') + + cy.get('.navbar__btn').click() + + // 顯示modal + cy.get('.custom-modal').should('be.visible') + + // 輸入欄位輸入'一起玩富饒之城' + cy.get('#roomName').type('一起玩富饒之城') + + // 按下確認按鈕 + cy.get('.blue-btn').click() + + // 創建房間POST應該拿到statusOK和200 + cy.wait('@createRoomRequest').should(({ request, response }) => { + expect(response.statusCode).to.equal(200) + expect(response.body.status).to.equal('OK') + }) + }) +}) diff --git a/packages/frontend/cypress/e2e/spec.cy.js b/packages/frontend/cypress/e2e/spec.cy.js deleted file mode 100644 index a4e31f5f..00000000 --- a/packages/frontend/cypress/e2e/spec.cy.js +++ /dev/null @@ -1,8 +0,0 @@ -// eslint-disable-next-line no-undef -describe('My First Test', () => { - // eslint-disable-next-line no-undef - it('Visits the Kitchen Sink', () => { - // eslint-disable-next-line no-undef - cy.visit('http://localhost:3001/') - }) -}) diff --git a/packages/frontend/cypress/support/commands.js b/packages/frontend/cypress/support/commands.js index 119ab03f..fed271ed 100644 --- a/packages/frontend/cypress/support/commands.js +++ b/packages/frontend/cypress/support/commands.js @@ -23,3 +23,38 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// commands.js or another custom command file +// commands.js or another custom command file +Cypress.Commands.add('setupIntercept', () => { + cy.intercept( + 'POST', + 'https://001f08b9-acb7-4c3a-a54f-a9254b7e8e55.mock.pstmn.io/createroom', + (req) => { + req.body = { + roomName: '一起玩富饒之城', + userName: '陳XX', + userImage: 'imageName1' + } + req.reply({ + createTime: '2023-07-04T19:29:54.001Z', + status: 'OK', + msg: '', + room: { + roomId: 'sxsxs111', + roomName: '一起玩富饒之城', + holderId: 'player1', + holderName: '陳XX', + users: [ + { + userId: 'player1', + userImage: 'imageName1', + userName: '陳XX' + } + ], + status: 'OPEN', + totalUsers: 1 + } + }) + } + ).as('createRoomRequest') +}) From f711c0e83a26831a4613dd78424fef2b7d0a0f0a Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Sun, 13 Aug 2023 17:59:07 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E4=BF=AE=E6=94=B9API=E8=A6=8F=E6=A0=BC(?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=9C=96=E7=89=87)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/js/components/RoomList.jsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/frontend/js/components/RoomList.jsx b/packages/frontend/js/components/RoomList.jsx index 11aea211..5fb6682c 100644 --- a/packages/frontend/js/components/RoomList.jsx +++ b/packages/frontend/js/components/RoomList.jsx @@ -27,8 +27,9 @@ const RoomList = () => { } const [newRoom, setNewRoom] = useState({ - playerName: '', - roomName: '' + roomName: '', + userName: '陳XX', + userImage: 'imageName1' }) const handleRoomNameChange = (e) => { @@ -43,10 +44,12 @@ const RoomList = () => { const handleSubmitRoomName = (event) => { event.preventDefault() - createRoom() + console.log('click') + createRoom(newRoom) .then((res) => { - if (res.status === 'OPEN') { + if (res.status === 'OK') { navigate('/game') + console.log(res) // TODO : 加入URLParamter } }) @@ -63,7 +66,7 @@ const RoomList = () => { 房間名稱: Date: Mon, 14 Aug 2023 20:51:04 +0800 Subject: [PATCH 10/28] =?UTF-8?q?=E7=8D=B2=E5=8F=96localStorage=E7=9A=84na?= =?UTF-8?q?me=E5=92=8Cimage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/js/components/RoomList.jsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/frontend/js/components/RoomList.jsx b/packages/frontend/js/components/RoomList.jsx index 5fb6682c..37f0a07c 100644 --- a/packages/frontend/js/components/RoomList.jsx +++ b/packages/frontend/js/components/RoomList.jsx @@ -28,14 +28,21 @@ const RoomList = () => { const [newRoom, setNewRoom] = useState({ roomName: '', - userName: '陳XX', - userImage: 'imageName1' + userName: '', + userImage: '' }) - + const [userName, setUserName] = useState(localStorage.getItem('userName')) + const [userImage, setUserImage] = useState(localStorage.getItem('userImage')) + localStorage.setItem('userName', '陳XX') // 正式版要刪除 + localStorage.setItem('userImage', 'userImage') // 正式版要刪除 const handleRoomNameChange = (e) => { setNewRoom({ ...newRoom, - [e.target.name]: e.target.value + [e.target.name]: e.target.value, + // eslint-disable-next-line object-shorthand + userName: userName, + // eslint-disable-next-line object-shorthand + userImage: userImage }) } const navigate = useNavigate() @@ -48,8 +55,8 @@ const RoomList = () => { createRoom(newRoom) .then((res) => { if (res.status === 'OK') { - navigate('/game') - console.log(res) + const roomID = res.room.roomId + navigate(`/game/${roomID}`) // TODO : 加入URLParamter } }) From f290f53c51663b340de4910d69d01710aa0fb29b Mon Sep 17 00:00:00 2001 From: Enya <26k1234o3@gmail.com> Date: Mon, 14 Aug 2023 20:52:03 +0800 Subject: [PATCH 11/28] =?UTF-8?q?URL=E8=A8=AD=E7=BD=AEroomId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/js/components/App.jsx | 3 +-- packages/frontend/js/components/Game.jsx | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/frontend/js/components/App.jsx b/packages/frontend/js/components/App.jsx index df50e29b..d0dd8898 100644 --- a/packages/frontend/js/components/App.jsx +++ b/packages/frontend/js/components/App.jsx @@ -1,7 +1,6 @@ import { Routes, Route, Navigate } from 'react-router-dom' import RoomList from './RoomList' import Game from './Game' - import getAPI from '../api/getAPI' const App = () => { @@ -12,7 +11,7 @@ const App = () => { } /> } /> - } /> + } /> ) diff --git a/packages/frontend/js/components/Game.jsx b/packages/frontend/js/components/Game.jsx index cd448f54..15138859 100644 --- a/packages/frontend/js/components/Game.jsx +++ b/packages/frontend/js/components/Game.jsx @@ -1,4 +1,9 @@ +import { useParams } from 'react-router-dom' + const Game = () => { + const { roomId } = useParams() // 獲取路徑參數 roomId + console.log(roomId) + return ( <>