node-mongo-demo
node.js and mongodb demo
git clone https://9o.is/git/node-mongo-demo.git
Lucky7.js
(4780B)
1 import React from 'react';
2 import * as api from '../api';
3
4 const useSession = () => {
5 const [session, setSession] = React.useState(undefined);
6
7 React.useEffect(() => {
8 const func = async () => {
9 const response = await api.lucky7Session();
10 setSession(response.data);
11 };
12 func();
13 }, []);
14
15 return {
16 session,
17 };
18 };
19
20 const useBetEvents = onEvent => {
21 const [listening, setListening] = React.useState(false);
22
23 React.useEffect(() => {
24 const connect = async () => {
25 const response = await api.lucky7BetEvents();
26 const stream = response.data;
27
28 const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
29 setListening(true);
30 while (true) {
31 const { value, done } = await reader.read();
32 if (done) {
33 setListening(false);
34 break;
35 }
36 const json = JSON.parse(value.slice(5));
37 onEvent(json);
38 }
39 };
40 if (!listening) connect();
41 }, [listening, onEvent]);
42 };
43
44 const getNextGameTime = (createdAt) => {
45 const startDateTime = new Date(createdAt);
46 const currentSeconds = Math.ceil(new Date() / 1000);
47 const startSeconds = Math.floor(startDateTime.getTime() / 1000);
48 const difference = currentSeconds - startSeconds;
49
50 const nextChunk = Math.ceil(difference / 15) * 15;
51 return new Date((startSeconds + nextChunk) * 1000);
52 };
53
54 const millisRemaining = date => date - Date.now();
55 const secondsRemaining = date => Math.ceil((date - Date.now()) / 1000);
56
57 const Timer = ({ createdAt }) => {
58 const [nextGameTime, setNextGameTime] = React.useState(getNextGameTime(createdAt));
59 const [seconds, setSeconds] = React.useState(secondsRemaining(nextGameTime));
60
61 React.useEffect(() => {
62 let timeout;
63 let interval;
64
65 const countdown = () => {
66 setSeconds((prevSeconds) => prevSeconds === 0 ? 0 : prevSeconds - 1);
67 };
68
69 const resetTimer = () => {
70 const nextGameTime = getNextGameTime(createdAt);
71 setNextGameTime(nextGameTime);
72 setSeconds(secondsRemaining(nextGameTime));
73 clearInterval(interval);
74
75 interval = setInterval(countdown, 1000);
76 timeout = setTimeout(resetTimer, millisRemaining(nextGameTime));
77 };
78
79 resetTimer();
80
81 return () => {
82 clearTimeout(timeout);
83 clearInterval(interval);
84 };
85 }, [createdAt, setSeconds, setNextGameTime]);
86
87 return (
88 <>
89 Timer: {seconds}s<br/>
90 </>
91 );
92 };
93
94 const sortBets = bets =>
95 Object.values(bets).sort((a, b) => a.rollAt < b.rollAt ? 1 : -1);
96
97 const Lucky7 = user => {
98 const { session } = useSession();
99
100 const [bets, setBets] = React.useState({});
101 const [error, setError] = React.useState(undefined);
102
103 const eventHandler = React.useCallback(e => {
104 const { bet } = e;
105 setBets(bets => ({ ...bets, [bet.id]: { ...bets[bet.id], ...bet } }));
106 setError(undefined);
107 }, [setBets, setError]);
108
109 useBetEvents(eventHandler);
110
111 const handleBet = lucky => async () => {
112 try {
113 await api.lucky7Bet(lucky);
114 setError(undefined);
115 } catch (error) {
116 if (error.status === 409 && error.response.data.code === 'SESSION_TIMER') {
117 setError(error.response.data.error);
118 } else {
119 console.error(error)
120 }
121 }
122 };
123
124 return (
125 <>
126 {error ? <div>{error}</div> : <> </>}
127 <div>
128 <button onClick={handleBet(true)}>Lucky</button>
129 <button onClick={handleBet(false)}>Unlucky</button>
130 {session ? <Timer createdAt={session.createdAt} /> : null}
131 </div>
132 <table>
133 <thead>
134 <tr>
135 <th>state</th>
136 <th>rollAt</th>
137 <th>lucky</th>
138 <th>roll</th>
139 <th>win</th>
140 </tr>
141 </thead>
142 <tbody>
143 {sortBets(bets).map(bet => (
144 <tr key={bet.id}>
145 <td>{bet.state}</td>
146 <td>{bet.rollAt}</td>
147 <td>{String(bet.lucky ?? '')}</td>
148 <td>{(bet.roll || []).join(',')}</td>
149 <td>{String(bet.win ?? '')}</td>
150 </tr>
151 ))}
152 </tbody>
153 </table>
154 </>
155 );
156 };
157
158 export default Lucky7;
159