Skip to content
Permalink
Newer
Older
100644 242 lines (219 sloc) 5.73 KB
February 28, 2024 14:20
1
import {
2
Button,
3
Container,
4
Heading,
5
Link,
6
Modal,
7
ModalBody,
8
ModalCloseButton,
9
ModalContent,
10
ModalFooter,
11
ModalHeader,
12
ModalOverlay,
13
Table,
14
TableContainer,
15
Tbody,
16
Td,
17
Text,
18
Textarea,
19
Th,
20
Thead,
21
Tr,
22
useDisclosure,
23
useToast,
24
} from "@chakra-ui/react";
25
import { LoaderFunctionArgs } from "@remix-run/cloudflare";
26
import { useLoaderData } from "@remix-run/react";
27
import { useState } from "react";
February 28, 2024 14:20
28
29
export async function loader({
30
context,
31
params,
32
}: {
33
context: RequestContext;
34
params: LoaderFunctionArgs & { uid: string };
35
}) {
36
const { current_user: user } = context.data;
37
38
if (!user)
39
throw new Response(null, {
40
status: 401,
41
});
42
43
if (![1 << 3, 1 << 4, 1 << 12].find((p) => user.permissions & p))
44
throw new Response(null, {
45
status: 403,
46
});
47
48
const strikeData = await context.env.D1.prepare(
49
"SELECT * FROM et_strikes WHERE user = ?;",
50
)
51
.bind(params.uid)
52
.all();
53
54
return {
55
can_manage: Boolean([1 << 4, 1 << 12].find((p) => user.permissions & p)),
56
strikes: strikeData.results,
57
user: params.uid,
58
};
59
}
60
61
export default function () {
62
const { can_manage, strikes, user } = useLoaderData<typeof loader>();
63
const [strikeData, setStrikeData] = useState(strikes);
February 28, 2024 14:20
64
const toast = useToast();
65
const [rmStrikeId, setRmStrikeId] = useState("");
66
const [strikeReason, setStrikeReason] = useState("");
67
68
async function removeStrike(id: string) {
69
const removeResp = await fetch(`/api/events-team/strikes/${id}`, {
70
method: "DELETE",
71
});
72
73
if (!removeResp.ok) {
74
let msg = "Unknown error";
75
76
try {
77
msg = ((await removeResp.json()) as { error: string }).error;
78
} catch {}
79
80
toast({
81
description: msg,
82
status: "error",
83
title: "Failed to remove strike",
84
});
85
86
return;
87
}
88
89
toast({
90
description: `Strike ${id} was removed`,
91
status: "success",
92
title: "Strike Removed",
93
});
94
95
setStrikeData(strikeData.filter((strike) => strike.id !== id));
96
closeRmStrike();
97
}
98
99
async function addStrike() {
100
const addStrikeResp = await fetch("/api/events-team/strikes/new", {
101
body: JSON.stringify({
102
reason: strikeReason,
103
user,
104
}),
105
headers: {
106
"content-type": "application/json",
107
},
108
method: "POST",
109
});
110
111
if (!addStrikeResp.ok) {
112
let msg = "Unknown error";
113
114
try {
115
msg = ((await addStrikeResp.json()) as { error: string }).error;
116
} catch {}
117
118
toast({
119
description: msg,
120
status: "error",
121
title: "Failed to add strike",
122
});
123
124
return;
125
}
126
127
toast({
128
description: "Strike added",
129
status: "success",
130
title: "Success",
131
});
132
133
const newStrikeData = strikeData;
134
135
newStrikeData.push(await addStrikeResp.json());
136
setStrikeData(newStrikeData);
137
closeAddStrike();
138
}
139
140
const {
141
isOpen: rmStrikeOpen,
142
onClose: closeRmStrike,
143
onOpen: openRmStrike,
144
} = useDisclosure();
145
const {
146
isOpen: addStrikeOpen,
147
onClose: closeAddStrike,
148
onOpen: openAddStrike,
149
} = useDisclosure();
150
151
return (
152
<Container maxW="container.lg">
153
<Modal isOpen={rmStrikeOpen} onClose={closeRmStrike}>
154
<ModalOverlay />
155
<ModalContent>
156
<ModalHeader>Remove Strike</ModalHeader>
157
<ModalCloseButton />
158
<ModalBody>
159
<Text>Are you sure you want to remove this strike?</Text>
160
</ModalBody>
161
<ModalFooter>
162
<Button mr="8px">No</Button>
163
<Button
164
colorScheme="red"
165
onClick={async () => await removeStrike(rmStrikeId)}
166
>
167
Yes
168
</Button>
169
</ModalFooter>
170
</ModalContent>
171
</Modal>
172
<Modal isOpen={addStrikeOpen} onClose={closeAddStrike}>
173
<ModalOverlay />
174
<ModalContent>
175
<ModalHeader>Add Strike</ModalHeader>
176
<ModalCloseButton />
177
<ModalBody>
178
<Heading mb="8px" size="xs">
179
Reason
180
</Heading>
181
<Textarea
182
onChange={(e) => setStrikeReason(e.target.value)}
183
placeholder="Strike reason"
184
value={strikeReason}
185
/>
186
</ModalBody>
187
<ModalFooter>
188
<Button
189
mr="8px"
190
onClick={() => {
191
closeAddStrike();
192
setStrikeReason("");
193
}}
194
>
195
Cancel
196
</Button>
197
<Button colorScheme="red" onClick={async () => await addStrike()}>
198
Add Strike
199
</Button>
200
</ModalFooter>
201
</ModalContent>
202
</Modal>
203
<Heading my="16px">Strikes</Heading>
204
<TableContainer>
205
<Table variant="simple">
206
<Thead>
207
<Tr>
208
<Th>Time Added</Th>
209
<Th>Added By</Th>
210
<Th>Reason</Th>
211
<Th>Remove</Th>
212
</Tr>
February 28, 2024 14:20
213
</Thead>
214
<Tbody>
215
{strikeData.map((strike: { [k: string]: any }) => (
216
<Tr>
217
<Td>{new Date(strike.created_at).toUTCString()}</Td>
218
<Td>{strike.created_by}</Td>
219
<Td>{strike.reason}</Td>
220
<Td>
221
{can_manage ? (
222
<Link
223
onClick={() => {
224
setRmStrikeId(strike.id);
225
openRmStrike();
226
}}
227
>
228
Remove
229
</Link>
230
) : null}
231
</Td>
232
</Tr>
233
))}
234
</Tbody>
235
</Table>
236
</TableContainer>
237
<Link color="#646cff" onClick={openAddStrike} py="16px">
238
Add Strike
239
</Link>
240
</Container>
241
);
242
}