diff --git a/client/src/components/Form/index.js b/client/src/components/Form/index.js index 68c5095..2e5ef65 100644 --- a/client/src/components/Form/index.js +++ b/client/src/components/Form/index.js @@ -98,3 +98,18 @@ export const SelectOption = styled.option` outline: none; `; + +export const UnorderedList = styled.ul.attrs(props => ({ + className: props.className +}))` +${tw` +`} +`; + +export const ListItem = styled.li.attrs(props => ({ + className: props.className +}))` +${tw` +py-1 +`} +`; diff --git a/client/src/components/Tourneys/MediaListTile.js b/client/src/components/Tourneys/MediaListTile.js new file mode 100644 index 0000000..0d022ab --- /dev/null +++ b/client/src/components/Tourneys/MediaListTile.js @@ -0,0 +1,59 @@ +import styled from 'styled-components'; +import tw from 'twin.macro'; + +import { + FlexContainer +} from '../base'; + +import { + NormalText +} from '../Text'; + + +import { RiEditCircleFill } from 'react-icons/ri'; +import { FaTrashAlt } from 'react-icons/fa'; + +const ListTileImage = styled.img` +width: ${props => props.w || '64px'}; +height: ${props => props.h || '64px'}; +object-fit: scale-down; +${tw` +`} +`; + + + +const ActionButtonContainer = styled.div` +${tw` +p-2 +flex +justify-center +items-center +rounded-md +shadow-md +bg-[#922626] +`} + +&:hover { +filter: brightness(140%); +cursor: pointer; +} +`; + + +export default function ListMediaTile(){ + return ( + + + 850 x 1332 + + + + + + + + + + ); +} diff --git a/client/src/containers/DashboardPage/LeftSideBar/StyledElements.js b/client/src/containers/DashboardPage/LeftSideBar/StyledElements.js index a5f8faa..e6fd073 100644 --- a/client/src/containers/DashboardPage/LeftSideBar/StyledElements.js +++ b/client/src/containers/DashboardPage/LeftSideBar/StyledElements.js @@ -3,7 +3,7 @@ import tw from 'twin.macro'; export const LeftBarContainer = styled.div` width: ${props => props.active ? '372px' : '60px'}; -position: absolute; +position: fixed; left: 0; top: 0; ${tw` diff --git a/client/src/containers/DashboardPage/Tournaments/TourneyFullView.js b/client/src/containers/DashboardPage/Tournaments/TourneyFullView.js index bfaec80..fc3e411 100644 --- a/client/src/containers/DashboardPage/Tournaments/TourneyFullView.js +++ b/client/src/containers/DashboardPage/Tournaments/TourneyFullView.js @@ -112,6 +112,7 @@ export default function TourneyFullView({ tourney }) { registerTourney({ tourneyId: tourney.id, userId: auth.userId, + }).unwrap(), { pending: `Registering to ${tourney.title}`, diff --git a/client/src/containers/TourneyDashboardPage/LeftSideBar/index.js b/client/src/containers/TourneyDashboardPage/LeftSideBar/index.js index e82b3d2..c533e94 100644 --- a/client/src/containers/TourneyDashboardPage/LeftSideBar/index.js +++ b/client/src/containers/TourneyDashboardPage/LeftSideBar/index.js @@ -51,7 +51,6 @@ export default function LeftSideBar({ menuItems, onChangeMenu, activeMenu }){ return ( - { setOpen(!open); }} pad={ open ? '2rem' : '1rem'}> diff --git a/client/src/containers/TourneyDashboardPage/MenuItems.js b/client/src/containers/TourneyDashboardPage/MenuItems.js index f413901..ba58feb 100644 --- a/client/src/containers/TourneyDashboardPage/MenuItems.js +++ b/client/src/containers/TourneyDashboardPage/MenuItems.js @@ -12,6 +12,7 @@ import { AiOutlineShareAlt } from 'react-icons/ai'; import { NotFound } from '../NotFoundPage/NotFound.js'; import OverviewPage from './Overview'; +import SettingsPage from './Settings'; import Participants from './Participants'; import Registrations from './Registrations'; import Structure from './Structure'; @@ -26,7 +27,7 @@ export const MenuItems = [ { 'name': 'Settings', 'icon': , - 'content': + 'content': }, { 'name': 'Structure', diff --git a/client/src/containers/TourneyDashboardPage/Overview/index.js b/client/src/containers/TourneyDashboardPage/Overview/index.js index c4e876c..2b44655 100644 --- a/client/src/containers/TourneyDashboardPage/Overview/index.js +++ b/client/src/containers/TourneyDashboardPage/Overview/index.js @@ -1,6 +1,13 @@ import styled from "styled-components"; import tw from "twin.macro"; import { useSelector } from "react-redux"; +import { useParams } from "react-router-dom"; +import BounceLoader from "react-spinners/BounceLoader"; + + +import { + useGetTourneyQuery +} from '../../../redux/TourneyApi'; import StatusCard from "./components/StatusCard"; @@ -55,9 +62,17 @@ const StructureTile = ({ stage, matchType, players, status }) => ( ); export default function OverviewPage() { - const { selectedTourney } = useSelector( - (state) => state.tourney - ); + const { tourneyId } = useParams(); + const { data: tourney, error } = useGetTourneyQuery(tourneyId); + + if(error){ + return ( + + Server ERROR | 500 + { console.log(error) } + + ) + } return ( @@ -76,7 +91,7 @@ export default function OverviewPage() { - { selectedTourney.members.length } + { tourney.members.length } Participants @@ -84,7 +99,7 @@ export default function OverviewPage() { Checked In - { selectedTourney.max_players } + { tourney.max_players } Tournament Size diff --git a/client/src/containers/TourneyDashboardPage/Participants/index.js b/client/src/containers/TourneyDashboardPage/Participants/index.js index f3df82c..973b981 100644 --- a/client/src/containers/TourneyDashboardPage/Participants/index.js +++ b/client/src/containers/TourneyDashboardPage/Participants/index.js @@ -2,6 +2,11 @@ import { useSelector } from "react-redux"; import styled from "styled-components"; import tw from "twin.macro"; +import { useParams } from "react-router-dom"; +import { + useGetTourneyQuery +} from '../../../redux/TourneyApi'; + import { NormalText, @@ -88,7 +93,8 @@ const TRow = styled.tr` export default function Participants(){ - const { selectedTourney } = useSelector((state) => state.tourney); + const { tourneyId } = useParams(); + const { data: tourney, error } = useGetTourneyQuery(tourneyId); return ( @@ -160,7 +166,7 @@ export default function Participants(){ - {selectedTourney.members.map((m) => { + {!error && tourney.members.map((m) => { return ( { m.status } diff --git a/client/src/containers/TourneyDashboardPage/Placements/index.js b/client/src/containers/TourneyDashboardPage/Placements/index.js index 3810cf5..35fda4e 100644 --- a/client/src/containers/TourneyDashboardPage/Placements/index.js +++ b/client/src/containers/TourneyDashboardPage/Placements/index.js @@ -1,6 +1,11 @@ import styled from 'styled-components'; import tw from 'twin.macro'; +import { useParams } from "react-router-dom"; +import { + useGetTourneyQuery +} from '../../../redux/TourneyApi'; + import { NormalText, BoldText, @@ -74,6 +79,9 @@ items-center export default function Placements(){ + const { tourneyId } = useParams(); + const { data: tourney, error } = useGetTourneyQuery(tourneyId); + return ( diff --git a/client/src/containers/TourneyDashboardPage/Registrations/index.js b/client/src/containers/TourneyDashboardPage/Registrations/index.js index 822211b..8cba0ff 100644 --- a/client/src/containers/TourneyDashboardPage/Registrations/index.js +++ b/client/src/containers/TourneyDashboardPage/Registrations/index.js @@ -2,16 +2,20 @@ import { useSelector } from "react-redux"; import styled from "styled-components"; import tw from "twin.macro"; -import { NormalText, BoldText, Text } from "../../../components/Text"; +import { useParams } from "react-router-dom"; +import { useGetTourneyQuery, useUpdateTourneyMutation } from "../../../redux/TourneyApi"; +import { NormalText, BoldText, Text } from "../../../components/Text"; import { FlexContainer, WrapContainer } from "../../../components/base"; - import Button, { IconButton } from "../../../components/Button"; +import { Marginer } from '../../../components/Marginer'; import { RiRefreshLine, RiFilter3Fill } from "react-icons/ri"; import { AiOutlineFileDone } from "react-icons/ai"; import { MdOutlineCancel } from "react-icons/md"; +import { toast } from 'react-toastify'; + const Container = styled.div` ${tw` w-full @@ -57,9 +61,11 @@ const Table = styled.table` box-shadow: 0 5px 10px black; background-color: black; text-align: left; + table-layout: fixed; border-radius: 5px; color: white; + overflow: hidden; `; const THead = styled.th` @@ -68,23 +74,68 @@ const THead = styled.th` letter-spacing: 0.05rem; font-size: 1rem; font-weight: 800; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; `; const TData = styled.td` padding: 0.7rem 2rem; font-size: 0.9rem; font-weight: 600; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; `; const TRow = styled.tr` - &:nth-child(event) { - background-color: #f40b41; + &:nth-child(even) { + ${tw`bg-red-800/40`} color: white; } `; +const StatusBadge = ({ status }) => { + let bgColor = 'bg-blue-500'; + + switch(status){ + case 'accepted': + bgColor = 'bg-green-500' + break; + case 'rejected': + bgColor = 'bg-red-500' + break; + case 'cancelled': + bgColor = 'bg-yellow-500' + break; + } + + return
+

+ { status } +

+
; +}; + export default function Participants() { - const { selectedTourney } = useSelector((state) => state.tourney); + const { tourneyId } = useParams(); + const { data: tourney, error } = useGetTourneyQuery(tourneyId); + const [updateTourney] = useUpdateTourneyMutation(); + + const totalRegistered = tourney.members.length; + const totalPending = tourney.members.filter( + (m) => m.status === "pending" + ).length; + const totalAccepted = tourney.members.filter( + (m) => m.status === "accepted" + ).length; + const totalRejected = tourney.members.filter( + (m) => m.status === "rejected" + ).length; + const totalCancelled = tourney.members.filter( + (m) => m.status === "cancelled" + ).length; + return ( @@ -99,31 +150,30 @@ export default function Participants() { - 1 + {totalRegistered} Total - 1 + {totalPending} - Pending - 1 + {totalRejected} - Refused + Rejected - 1 + {totalAccepted} Accepted - 1 + {totalCancelled} Cancelled @@ -136,7 +186,7 @@ export default function Participants() { List of Registrations Last Update - 5/22/2022 + { tourney.updatedAt.substring(0, 10) }
@@ -156,34 +206,76 @@ export default function Participants() { Player ID Reg ID Registered Date + Entry Fee Actions
- {selectedTourney.members.map((m) => { - return ( - - { m.status } - { m.member_id } - { m.reg_id} - N/A - - - } - > - } - > - - - - ); - })} + {!error && + tourney.members.map((m, index) => { + return ( + + + {m.member_id} + {m.reg_id} + {m.registered_date.substring(0, 10)} + + {m.fee_paid ? ( + + PAID + + ) : ( + + NOT PAID + + )} + + + + { + if(!m.fee_paid) { + toast.error("Player has not paid the registration fee!"); + return; + } + + if(m.status === 'accepted') { + toast.error("Player has already been accepted!"); + return; + } + + + + let allMembers = [ ...(tourney.members) ]; + let updatedMember = { ...m }; + allMembers.splice(index, 1); + + updatedMember.status = 'accepted'; + allMembers.push(updatedMember); + + toast.promise(updateTourney({ id: tourneyId, members: allMembers }).unwrap(), + { + pending: 'Approving player registration...', + success: 'Approved!', + error: 'Couldnt approve!' + }); + }} + pad={"0.4rem"} + icon={} + > + } + > + + + + ); + })} + +
); } diff --git a/client/src/containers/TourneyDashboardPage/Settings/BasicSettingsSection.js b/client/src/containers/TourneyDashboardPage/Settings/BasicSettingsSection.js new file mode 100644 index 0000000..d502482 --- /dev/null +++ b/client/src/containers/TourneyDashboardPage/Settings/BasicSettingsSection.js @@ -0,0 +1,8 @@ +import styled from "styled-components"; +import tw from "twin.macro"; + +export default function BasicSettingsSection(){ + + // return +} + diff --git a/client/src/containers/TourneyDashboardPage/Settings/index.js b/client/src/containers/TourneyDashboardPage/Settings/index.js new file mode 100644 index 0000000..3a1d3f2 --- /dev/null +++ b/client/src/containers/TourneyDashboardPage/Settings/index.js @@ -0,0 +1,131 @@ +import styled from 'styled-components'; +import tw from 'twin.macro'; + +import { useParams } from "react-router-dom"; +import { + useGetTourneyQuery +} from '../../../redux/TourneyApi'; + +import { + Text +} from '../../../components/Text'; + +import { + FlexContainer +} from '../../../components/base'; + +import { + Marginer +} from '../../../components/Marginer'; + +import Button from '../../../components/Button'; +import ListTileImage from '../../../components/Tourneys/MediaListTile'; + +import { + Input, + TextArea, + UnorderedList, + ListItem +} from '../../../components/Form'; + +const Container = styled.div` +${tw` +w-full +`} +`; + +export default function Settings(){ + const { tourneyId } = useParams(); + const { data: tourney, error } = useGetTourneyQuery(tourneyId); + + console.log(tourney); + + return ( + + Settings + + { tourney.title } + Description + + + Medias / Promotion + + Share photos or a video of/for the tournament. Medias can't exceed 10 photos & 1 video + + + + + + + + + + Sponsers / Supporters + + + + + + + + Managers + + Add managers so they can manage this tournamnet too + + + Saroj Rai + Ajaya RajBhandari + + + + + + Location + + Specify where you tournaments will held + + + + + Start Date + + When will the tournament start + + + + Registraion End Date + Tournament Start Date + Tournament End Date + + + + + + + + + + + + + ) +} diff --git a/client/src/containers/TourneyDashboardPage/Structure/index.js b/client/src/containers/TourneyDashboardPage/Structure/index.js index 7a468ad..139142b 100644 --- a/client/src/containers/TourneyDashboardPage/Structure/index.js +++ b/client/src/containers/TourneyDashboardPage/Structure/index.js @@ -1,6 +1,11 @@ import styled from "styled-components"; import tw from "twin.macro"; +import { useParams } from "react-router-dom"; +import { + useGetTourneyQuery +} from '../../../redux/TourneyApi'; + import { NormalText, BoldText, Text } from "../../../components/Text"; import { FlexContainer, WrapContainer } from "../../../components/base"; import Button, { IconButton } from "../../../components/Button"; @@ -32,6 +37,9 @@ bg-black `; export default function Structure() { + const { tourneyId } = useParams(); + const { data: tourney, error } = useGetTourneyQuery(tourneyId); + return ( diff --git a/client/src/containers/TourneyDashboardPage/index.js b/client/src/containers/TourneyDashboardPage/index.js index f3392b1..c70044c 100644 --- a/client/src/containers/TourneyDashboardPage/index.js +++ b/client/src/containers/TourneyDashboardPage/index.js @@ -12,6 +12,11 @@ import { setActiveMenu, setSelectedTourney, } from "../../redux/TourneySlice"; + +import { + useGetTourneyQuery +} from '../../redux/TourneyApi'; + import LeftSideBar from "./LeftSideBar"; import { MenuItems } from "./MenuItems.js"; import { FlexContainer } from "../../components/base"; @@ -45,34 +50,14 @@ export default function TourneyDashboardPage() { const dispatch = useDispatch(); const auth = useSelector((state) => state.auth); - const { dashboard, selectedTourney, isPending, isError, errorMessages } = - useSelector((state) => state.tourney); - - useEffect(() => { - dispatch(pending()); - (async () => { - try { - const options = { - method: "GET", - url: `${config.serverUrl}/api/v1/tourneys/${tourneyId}`, - headers: { - "Content-Type": "application/json", - Authorization: "Bearer " + auth.accessToken, - }, - }; - - const response = await axios.request(options); - dispatch(setSelectedTourney(response.data.tourney)); - } catch (e) { - if (e.response) dispatch(error(e.response.data.errorList)); - } - })(); - }, [auth.accessToken, dispatch, tourneyId]); + const { data: tourney, isPending, error } = useGetTourneyQuery(tourneyId); + const { dashboard } = useSelector((state) => state.tourney); const renderContent = MenuItems.find( (menu) => menu.name === dashboard.activeMenu ).content; + return ( dispatch(setActiveMenu(menuName))} /> - {isError && ( + {error && ( - {" "} - ERROR{" "} + SERVER ERROR | 500 - {errorMessages.map((err) => { + {error?.errorList?.map((err) => { return ( {err} @@ -106,12 +90,12 @@ export default function TourneyDashboardPage() { )} - {(isPending || !selectedTourney) ? ( + {(isPending || !tourney) ? ( ) : ( - !isError && renderContent + !error && renderContent )} diff --git a/client/src/redux/TourneyApi.js b/client/src/redux/TourneyApi.js index 0ea42a5..b3b86e6 100644 --- a/client/src/redux/TourneyApi.js +++ b/client/src/redux/TourneyApi.js @@ -25,6 +25,7 @@ export const tourneyApi = createApi({ getTourney: builder.query({ query: (id) => `tourneys/${id}`, providesTags: (_result, _error, id) => [{ type: "Tourneys", id }], + transformResponse: (response, _meta, _arg) => response.status === 'success' ? response.tourney : response.errorList }), getTourneys: builder.query({ query: ({ pageQuery = {}, ...query }) => ({ diff --git a/client/src/redux/UserApi.js b/client/src/redux/UserApi.js index e11341c..d40a600 100644 --- a/client/src/redux/UserApi.js +++ b/client/src/redux/UserApi.js @@ -20,7 +20,7 @@ export const userApi = createApi({ getUser: builder.query({ query: (id) => `users/${id}`, providesTags: (_result, _error, id) => [{ type: "Users", id }], - transformResponse: (response, meta, arg) => response.status === 'success' ? response.user : response.errorList + transformResponse: (response, _meta, _arg) => response.status === 'success' ? response.user : response.errorList }), getUsers: builder.query({ query: ({ pageQuery, ...query } = {}) => ({ diff --git a/controllers/tourney/register-tourney.js b/controllers/tourney/register-tourney.js index 5107402..46c8efc 100644 --- a/controllers/tourney/register-tourney.js +++ b/controllers/tourney/register-tourney.js @@ -30,6 +30,7 @@ module.exports = function makeRegisterPlayerTourney(tourneyAccess) { { member_id: playerId, reg_id: rand.generate(), + registered_date: new Date(), status: 'pending', fee_paid: false }, diff --git a/data-access/tourney-db/mongo-db/serializer.js b/data-access/tourney-db/mongo-db/serializer.js index 596f7cb..898770d 100644 --- a/data-access/tourney-db/mongo-db/serializer.js +++ b/data-access/tourney-db/mongo-db/serializer.js @@ -16,7 +16,9 @@ const _serializeSingle = (tourney) => { "live_link": tourney.live_link, "start_date": tourney.start_date, "end_date": tourney.end_date, - "registration_fee": tourney.registration_fee + "registration_fee": tourney.registration_fee, + "createdAt": tourney.createdAt, + "updatedAt": tourney.updatedAt }; }; diff --git a/models/tourney/tourney-schema.js b/models/tourney/tourney-schema.js index 90f1180..8250a3d 100644 --- a/models/tourney/tourney-schema.js +++ b/models/tourney/tourney-schema.js @@ -14,14 +14,21 @@ const tourneyUpdateSchema = Joi.object().keys({ members: Joi.array().items(Joi.object().keys({ member_id: Joi.objectId().required(), reg_id: Joi.string().min(6).required(), - // status for registration, pending | accepted | rejected - status: Joi.string().valid('pending', 'accepted', 'rejected').required(), + status: Joi.string().valid('pending', 'accepted', 'rejected', 'cancelled').required(), + registered_date: Joi.date().required(), fee_paid: Joi.boolean().required() })), managers: Joi.array().items(Joi.objectId()), - sponserships: Joi.array().items(Joi.objectId()), + sponserships: Joi.array().items(Joi.object().keys({ + name: Joi.string(), + description: Joi.string().min(8), + logo_url: Joi.string(), + website_url: Joi.string(), + sponser_value: Joi.number() + })), + prizes: Joi.array().items(Joi.object().keys({ prize_title: Joi.string().required(), prize_description: Joi.string().required(), @@ -30,18 +37,19 @@ const tourneyUpdateSchema = Joi.object().keys({ matches: Joi.array().items(Joi.object().keys({ // match id is ref to match match_id: Joi.objectId().required(), - // match_tourney_id is the match id for this tournmanet, // for e.g. For tourney Round 1 & Match no. 2, this will be 'M1.2' match_tourney_id: Joi.string().required(), - match_played: Joi.boolean().required(), })), + + hypes: Joi.array().items(Joi.objectId()), game: Joi.string(), max_players: Joi.number(), location: Joi.string(), registration_fee: Joi.number(), live_link: Joi.string(), + registration_end_date: Joi.date(), start_date: Joi.date().min('9-1-2021'), end_date: Joi.date().greater(Joi.ref('start_date')),