Skip to content

Commit e961755

Browse files
authored
[Staff] Update support dashboard (#4198)
## Description ## Tests
2 parents 5a58bcf + 8d32cba commit e961755

File tree

3 files changed

+454
-305
lines changed

3 files changed

+454
-305
lines changed

infra/staff/src/App.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useEffect, useState } from "react";
99
import "./App.css";
1010
import FamilyTableComponent from "./components/FamilyComponentTable";
1111
import StorageBonusTableComponent from "./components/StorageBonusTableComponent";
12+
import TokensTableComponent from "./components/TokenTableComponent";
1213
import type { UserData } from "./components/UserComponent";
1314
import UserComponent from "./components/UserComponent";
1415
import duckieimage from "./components/duckie.png";
@@ -45,11 +46,13 @@ interface Security {
4546
isEmailMFAEnabled: boolean;
4647
isTwoFactorEnabled: boolean;
4748
passkeys: string;
49+
passkeyCount: number;
4850
}
4951

5052
interface UserResponse {
5153
user: User;
5254
subscription: Subscription;
55+
authCodes?: number;
5356
details?: {
5457
usage?: number;
5558
storageBonus?: number;
@@ -181,7 +184,12 @@ const App: React.FC = () => {
181184
.isTwoFactorEnabled
182185
? "Enabled"
183186
: "Disabled",
184-
Passkeys: "None",
187+
Passkeys:
188+
(userDataResponse.details?.profileData.passkeyCount ??
189+
0) > 0
190+
? "Enabled"
191+
: "Disabled",
192+
AuthCodes: `${userDataResponse.authCodes ?? 0}`,
185193
},
186194
};
187195

@@ -309,6 +317,7 @@ const App: React.FC = () => {
309317
<Tab label="User" />
310318
<Tab label="Family" />
311319
<Tab label="Bonuses" />
320+
<Tab label="Devices" />
312321
</Tabs>
313322
</Box>
314323
<Box
@@ -336,6 +345,11 @@ const App: React.FC = () => {
336345
<StorageBonusTableComponent />
337346
</div>
338347
)}
348+
{tabValue === 3 && userData && (
349+
<div>
350+
<TokensTableComponent />
351+
</div>
352+
)}
339353
</Box>
340354
</>
341355
) : (
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import {
2+
Paper,
3+
Table,
4+
TableBody,
5+
TableCell,
6+
TableContainer,
7+
TableHead,
8+
TableRow,
9+
TableSortLabel,
10+
} from "@mui/material";
11+
import * as React from "react";
12+
import { useEffect, useState } from "react";
13+
import { getEmail, getToken } from "../App";
14+
import { apiOrigin } from "../services/support";
15+
16+
interface TokenData {
17+
creationTime: number;
18+
lastUsedTime: number;
19+
ua: string;
20+
isDeleted: boolean;
21+
app: string;
22+
}
23+
24+
interface UserData {
25+
tokens: TokenData[];
26+
}
27+
28+
const TokensTableComponent: React.FC = () => {
29+
const [tokens, setTokens] = useState<TokenData[]>([]);
30+
const [loading, setLoading] = useState(true);
31+
const [error, setError] = useState<string | null>(null);
32+
const [order, setOrder] = useState<"asc" | "desc">("asc");
33+
const [orderBy, setOrderBy] = useState<keyof TokenData>("lastUsedTime");
34+
35+
useEffect(() => {
36+
const fetchData = async () => {
37+
try {
38+
const encodedEmail = encodeURIComponent(getEmail());
39+
const token = getToken();
40+
const url = `${apiOrigin}/admin/user?email=${encodedEmail}`;
41+
const response = await fetch(url, {
42+
method: "GET",
43+
headers: {
44+
"Content-Type": "application/json",
45+
"X-Auth-Token": token,
46+
},
47+
});
48+
if (!response.ok) {
49+
throw new Error("Failed to fetch token data");
50+
}
51+
const userData = (await response.json()) as UserData;
52+
setTokens(userData.tokens);
53+
} catch (error) {
54+
console.error("Error fetching token data:", error);
55+
setError("No token data");
56+
} finally {
57+
setLoading(false);
58+
}
59+
};
60+
61+
fetchData().catch((error: unknown) =>
62+
console.error("Fetch data error:", error),
63+
);
64+
}, []);
65+
66+
const handleRequestSort = (property: keyof TokenData) => {
67+
const isAsc = orderBy === property && order === "asc";
68+
setOrder(isAsc ? "desc" : "asc");
69+
setOrderBy(property);
70+
};
71+
72+
const sortedTokens = tokens.sort((a, b) => {
73+
if (orderBy === "lastUsedTime" || orderBy === "creationTime") {
74+
return order === "asc"
75+
? a[orderBy] - b[orderBy]
76+
: b[orderBy] - a[orderBy];
77+
}
78+
return 0;
79+
});
80+
81+
const formatDate = (timestamp: number): string => {
82+
const date = new Date(timestamp / 1000);
83+
return date.toLocaleDateString();
84+
};
85+
86+
if (loading) {
87+
return <p>Loading...</p>;
88+
}
89+
90+
if (error) {
91+
return <p>Error: {error}</p>;
92+
}
93+
94+
if (tokens.length === 0) {
95+
return <p>No token data available</p>;
96+
}
97+
98+
return (
99+
<div style={{ marginTop: "20px", marginBottom: "20px" }}>
100+
<TableContainer
101+
component={Paper}
102+
style={{
103+
backgroundColor: "#F1F1F3",
104+
}}
105+
>
106+
<Table aria-label="tokens-table">
107+
<TableHead>
108+
<TableRow>
109+
<TableCell
110+
sortDirection={
111+
orderBy === "creationTime" ? order : false
112+
}
113+
>
114+
<TableSortLabel
115+
active={orderBy === "creationTime"}
116+
direction={
117+
orderBy === "creationTime"
118+
? order
119+
: "asc"
120+
}
121+
onClick={() =>
122+
handleRequestSort("creationTime")
123+
}
124+
>
125+
Created At
126+
</TableSortLabel>
127+
</TableCell>
128+
<TableCell
129+
sortDirection={
130+
orderBy === "lastUsedTime" ? order : false
131+
}
132+
>
133+
<TableSortLabel
134+
active={orderBy === "lastUsedTime"}
135+
direction={
136+
orderBy === "lastUsedTime"
137+
? order
138+
: "asc"
139+
}
140+
onClick={() =>
141+
handleRequestSort("lastUsedTime")
142+
}
143+
>
144+
Last Used At
145+
</TableSortLabel>
146+
</TableCell>
147+
<TableCell>
148+
<b>User Agent</b>
149+
</TableCell>
150+
<TableCell>
151+
<b>App</b>
152+
</TableCell>
153+
<TableCell>
154+
<b>Is Deleted</b>
155+
</TableCell>
156+
</TableRow>
157+
</TableHead>
158+
<TableBody>
159+
{sortedTokens.map((token, index) => (
160+
<TableRow key={index}>
161+
<TableCell>
162+
{formatDate(token.creationTime)}
163+
</TableCell>
164+
<TableCell>
165+
{formatDate(token.lastUsedTime)}
166+
</TableCell>
167+
<TableCell>{token.ua}</TableCell>
168+
<TableCell>{token.app}</TableCell>
169+
<TableCell>
170+
<span
171+
style={{
172+
backgroundColor: token.isDeleted
173+
? "#494949"
174+
: "transparent",
175+
color: token.isDeleted
176+
? "white"
177+
: "inherit",
178+
padding: "4px 8px",
179+
borderRadius: "10px",
180+
}}
181+
>
182+
{token.isDeleted ? "Yes" : "No"}
183+
</span>
184+
</TableCell>
185+
</TableRow>
186+
))}
187+
</TableBody>
188+
</Table>
189+
</TableContainer>
190+
</div>
191+
);
192+
};
193+
194+
export default TokensTableComponent;

0 commit comments

Comments
 (0)