Skip to content

Commit

Permalink
Merge pull request #110 from RisingStack/master
Browse files Browse the repository at this point in the history
feat: move item purchases to different tab + add config options
  • Loading branch information
celo0213 authored Oct 25, 2023
2 parents 53832d8 + 880ca97 commit b51ab41
Show file tree
Hide file tree
Showing 23 changed files with 518 additions and 318 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,25 @@ This section has moved here: https://facebook.github.io/create-react-app/docs/de
### `npm run build` fails to minify

This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

# Airtable Handbook

### PURCHASED MITIGATIONS

In the game, mitigations are organized into groups according to their category. You can customize the order of these mitigations using the following steps:

1. Access the "purchase_mitigations" table.
2. In the toolbar, click on the "Group" option, and then select the "category" field. Airtable will automatically reorganize the mitigations, grouping them just as they appear in the application.
3. Within each category, you can rearrange the mitigations according to your preferences using the drag-and-drop feature. The order you set here will reflect how they appear in the actual game.

### LOCATIONS

Currently, the game exclusively accommodates exactly two locations. You have the flexibility to name these locations as you desire within the "locations" table.

:warning: **Please exercise caution and refrain from modifying the "location_code" fields. Altering the default values ('hq', 'local') here can disrupt the application's functionality!** :warning:

Changing the names of the locations in this section will solely impact how they are displayed in the header menu, tabs, and action titles. Role names (e.g., "HQ IT Team") remain distinct and should be configured separately within the **ROLES** table.

## DICTIONARY

You have the ability to modify the terminology associated with "polls" in this section. For instance, you can replace "poll" or "budget" with alternative words. To introduce a new synonym, simply edit the synonym column as needed.
14 changes: 11 additions & 3 deletions src/components/BPT.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { view } from '@risingstack/react-easy-state';
import { gameStore } from './GameStore';
import { numberToUsd } from '../util';
import useTimeTaken from '../hooks/useTimeTaken';
import { useStaticData } from './StaticDataProvider';

const TimeTaken = view(({ big }) => {
const { paused } = gameStore;
Expand All @@ -30,6 +31,7 @@ const TimeTaken = view(({ big }) => {
// BudgetPollTimer
const BPT = view(({ big }) => {
const { budget, poll } = gameStore;
const { getTextWithSynonyms } = useStaticData();

return (
<Row
Expand All @@ -46,7 +48,9 @@ const BPT = view(({ big }) => {
})}
>
{big && (
<h2 className="font-weight-bold my-2">Available Budget</h2>
<h2 className="font-weight-bold my-2">
{getTextWithSynonyms('Available Budget')}
</h2>
)}
<h4 className="bpt-item font-weight-normal mb-0">
{numberToUsd(budget).replace('$', '$ ')}
Expand All @@ -61,7 +65,9 @@ const BPT = view(({ big }) => {
})}
>
{big && (
<h2 className="font-weight-bold my-2">Support in Polls</h2>
<h2 className="font-weight-bold my-2">
{getTextWithSynonyms('Support in Polls')}
</h2>
)}
<h4 className="bpt-item font-weight-normal mb-0">{poll} %</h4>
</Col>
Expand All @@ -74,7 +80,9 @@ const BPT = view(({ big }) => {
})}
>
{big && (
<h2 className="font-weight-bold my-2">Time Elapsed</h2>
<h2 className="font-weight-bold my-2">
{getTextWithSynonyms('Time Elapsed')}
</h2>
)}
<TimeTaken big={big} />
</Col>
Expand Down
86 changes: 85 additions & 1 deletion src/components/EnterGame.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Link } from 'react-router-dom';

import { SocketEvents } from '../constants';
import { gameStore } from './GameStore';
import { useStaticData } from './StaticDataProvider';

const gameIdFromLocalStorage = localStorage.getItem('gameId');

Expand All @@ -14,11 +15,18 @@ const EnterGame = view(() => {
actions: { enterGame },
} = gameStore;

const { getTextWithSynonyms } = useStaticData();

const [gameId, setGameId] = useState(gameIdFromLocalStorage || '');
const [rememberGameId, setRememberGameId] = useState(
!!gameIdFromLocalStorage,
);

const [adjustGameConfig, setAdjustGameConfig] = useState(false);
const [initialBudget, setInitialBudget] = useState(6000);
const [initialPollPercentage, setInitialPollPercentage] =
useState(55.0);

return (
<Container fluid="md" className="mt-5 pt-5">
<Button
Expand All @@ -44,13 +52,15 @@ const EnterGame = view(() => {
eventType: SocketEvents.CREATEGAME,
gameId,
rememberGameId,
initialBudget,
initialPollPercentage,
});
}}
>
<Form.Group controlId="GameId">
<Form.Label>
<h5 className="font-weight-normal mb-0">
Your games name:
Your game's name:
</h5>
</Form.Label>
<Form.Control
Expand All @@ -71,6 +81,78 @@ const EnterGame = view(() => {
style={{ fontSize: '1.125rem' }}
/>
</Form.Group>
<Form.Group controlId="AdjustGameConfig">
<Form.Check
type="switch"
label={<span>Adjust initial game options</span>}
defaultChecked={adjustGameConfig}
onChange={(e) =>
setAdjustGameConfig(e.target.checked)
}
style={{ fontSize: '1.125rem' }}
/>
</Form.Group>
{adjustGameConfig && (
<div className="p-3 border border-primary">
<Form.Group controlId="Budget">
<Form.Label column="lg">
<h5 className="font-weight-normal mb-0">
{getTextWithSynonyms('Budget:')}
</h5>
</Form.Label>
<Form.Control
type="number"
placeholder={getTextWithSynonyms('Budget')}
size="sm"
onChange={(event) => {
const newValue = !event.target.value.length
? ''
: parseInt(event.target.value, 10);

setInitialBudget(newValue);
}}
value={initialBudget}
step={100}
min={0}
isInvalid={!initialBudget}
autoComplete="off"
style={{ fontSize: '1.125rem' }}
/>
</Form.Group>

<Form.Group
controlId="PollPercentage"
className="mb-0"
>
<Form.Label column="lg">
<h5 className="font-weight-normal mb-0">
{getTextWithSynonyms('Poll percentage:')}
</h5>
</Form.Label>
<Form.Control
type="number"
size="sm"
placeholder={getTextWithSynonyms(
'Poll percentage',
)}
onChange={(event) => {
const newValue = !event.target.value.length
? ''
: parseFloat(event.target.value);

setInitialPollPercentage(newValue);
}}
value={initialPollPercentage}
autoComplete="off"
step={0.5}
max={100}
min={0}
isInvalid={!initialPollPercentage}
style={{ fontSize: '1.125rem' }}
/>
</Form.Group>
</div>
)}
<Row className="my-4">
<Col>
<Button
Expand All @@ -83,6 +165,8 @@ const EnterGame = view(() => {
eventType: SocketEvents.CREATEGAME,
gameId,
rememberGameId,
initialBudget,
initialPollPercentage,
})
}
>
Expand Down
23 changes: 7 additions & 16 deletions src/components/EventLogs/BudgetItemLog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,12 @@ import Log from './Log';
import { useStaticData } from '../StaticDataProvider';
import { msToMinutesSeconds, numberToUsd } from '../../util';

const BudgetItemLog = ({
game_timer,
type,
mitigation_type,
mitigation_id,
}) => {
const BudgetItemLog = ({ game_timer, type, mitigation_id }) => {
const { mitigations } = useStaticData();
const mitigation = useMemo(() => mitigations[mitigation_id], [
mitigations,
mitigation_id,
]);
const mitigation = useMemo(
() => mitigations[mitigation_id],
[mitigations, mitigation_id],
);

return (
<Log
Expand All @@ -31,14 +26,10 @@ const BudgetItemLog = ({
>
<Card.Body>
<Row>
<Col xs={8}>{mitigation.description}</Col>
<Col xs={2}>
<span className="font-weight-bold">Location: </span>
<span className="text-uppercase">{mitigation_type}</span>
</Col>
<Col xs={10}>{mitigation.description}</Col>
<Col xs={2} className="text-right">
<span className="font-weight-bold">Cost: </span>
{numberToUsd(mitigation[`${mitigation_type}_cost`])}
{numberToUsd(mitigation.cost)}
</Col>
</Row>
</Card.Body>
Expand Down
11 changes: 4 additions & 7 deletions src/components/EventLogs/EventLogSwitch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const EventLogSwitch = view(
game_timer,
description,
type,
mitigation_type,
mitigation_id,
response_id,
action_id,
Expand All @@ -27,10 +26,10 @@ const EventLogSwitch = view(
},
filter,
}) => {
const shouldDisplay = useMemo(() => filter[type] || false, [
filter,
type,
]);
const shouldDisplay = useMemo(
() => filter[type] || false,
[filter, type],
);

const eventLog = useMemo(() => {
switch (type) {
Expand All @@ -39,7 +38,6 @@ const EventLogSwitch = view(
<BudgetItemLog
game_timer={game_timer}
type={type}
mitigation_type={mitigation_type}
mitigation_id={mitigation_id}
/>
);
Expand Down Expand Up @@ -153,7 +151,6 @@ const EventLogSwitch = view(
}, [
type,
game_timer,
mitigation_type,
mitigation_id,
response_id,
action_id,
Expand Down
10 changes: 1 addition & 9 deletions src/components/Game.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React, { useEffect } from 'react';
import qs from 'query-string';
import { view } from '@risingstack/react-easy-state';
import { Spinner } from 'react-bootstrap';
Expand All @@ -17,13 +16,6 @@ const Game = view(() => {
const { state: gameState, socketConnected } = gameStore;
const { loading: loadingStaticData } = useStaticData();

useEffect(() => {
document.querySelector('#root').scrollIntoView({
behavior: 'smooth',
block: 'start',
});
});

if (loadingStaticData || !socketConnected) {
return (
<div
Expand All @@ -47,7 +39,7 @@ const Game = view(() => {
}

if (gameState === GameStates.PREPARATION) {
return <Mitigations className="mb-5 pb-5" />;
return <Mitigations className="mb-5 pb-5" allowSell={true} />;
}

if (gameState === GameStates.SIMULATION) {
Expand Down
48 changes: 32 additions & 16 deletions src/components/GameStore.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,16 @@ export const gameStore = store({
);
} else if (key === 'mitigations') {
gameStore.mitigations = game.mitigations.reduce(
(acc, { mitigation_id, location, state }) => ({
(acc, { mitigation_id, state }) => ({
...acc,
[`${mitigation_id}_${location}`]: state,
[mitigation_id]: state,
}),
{},
);
gameStore.preparationMitigations = game.mitigations.reduce(
(acc, { mitigation_id, location, preparation }) => ({
(acc, { mitigation_id, preparation }) => ({
...acc,
[`${mitigation_id}_${location}`]: preparation,
[mitigation_id]: preparation,
}),
{},
);
Expand Down Expand Up @@ -99,21 +99,33 @@ export const gameStore = store({

// ACTIONS
actions: {
enterGame: ({ eventType, gameId, rememberGameId }) => {
enterGame: ({
eventType,
gameId,
rememberGameId,
initialBudget = 6000,
initialPollPercentage = 55,
}) => {
gameStore.loading = true;
socket.emit(eventType, gameId, ({ error, game }) => {
if (!error) {
gameStore.setGame(game);
if (rememberGameId) {
localStorage.setItem('gameId', gameId);
socket.emit(
eventType,
gameId,
initialBudget,
initialPollPercentage,
({ error, game }) => {
if (!error) {
gameStore.setGame(game);
if (rememberGameId) {
localStorage.setItem('gameId', gameId);
} else {
localStorage.removeItem('gameId');
}
} else {
localStorage.removeItem('gameId');
gameStore.popError(error);
}
} else {
gameStore.popError(error);
}
gameStore.loading = false;
});
gameStore.loading = false;
},
);
},
resumeSimulation: () =>
gameStore.emitEvent(SocketEvents.STARTSIMULATION),
Expand Down Expand Up @@ -177,6 +189,8 @@ socket.on(SocketEvents.RECONNECT, () => {
socket.emit(
SocketEvents.JOINGAME,
gameStore.id,
null, // initialBudget (only for CREATE GAME)
null, // initialPollPercentage (only for CREATE GAME)
({ error, game: g }) => {
if (!error) {
gameStore.setGame(g);
Expand All @@ -196,6 +210,8 @@ if (gameIdFromQuery) {
socket.emit(
SocketEvents.JOINGAME,
gameIdFromQuery,
null, // initialBudget (only for CREATE GAME)
null, // initialPollPercentage (only for CREATE GAME)
({ error, game }) => {
if (!error) {
gameStore.setGame(game);
Expand Down
Loading

0 comments on commit b51ab41

Please sign in to comment.