Full dashboard
This commit is contained in:
@@ -304,3 +304,44 @@
|
||||
.active-field input {
|
||||
width: 50px;
|
||||
}
|
||||
/* Add to App.css or your CSS file */
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.input-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 300px;
|
||||
}
|
||||
|
||||
.horizontal-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fetch-button-container {
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.text-field-token,
|
||||
.text-field-email {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ import TextField from "@mui/material/TextField";
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import "./App.css";
|
||||
import StorageBonusTableComponent from "./components/StorageBonusTableComponent";
|
||||
import FamilyTableComponent from "./components/FamilyComponentTable";
|
||||
import type { UserData } from "./components/UserComponent";
|
||||
import UserComponent from "./components/UserComponent";
|
||||
import duckieimage from "./components/duckie.png";
|
||||
import { apiOrigin } from "./services/support";
|
||||
|
||||
// Define and export email and token variables and their setter functions
|
||||
export let email = "";
|
||||
export let token = "";
|
||||
|
||||
@@ -43,7 +44,7 @@ interface Subscription {
|
||||
interface Security {
|
||||
isEmailMFAEnabled: boolean;
|
||||
isTwoFactorEnabled: boolean;
|
||||
passkeys: string; // Replace with actual passkey value if available
|
||||
passkeys: string;
|
||||
}
|
||||
|
||||
interface UserResponse {
|
||||
@@ -57,8 +58,8 @@ interface UserResponse {
|
||||
}
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [localEmail, setLocalEmail] = useState<string>(getEmail());
|
||||
const [localToken, setLocalToken] = useState<string>(getToken());
|
||||
const [localEmail, setLocalEmail] = useState<string>("");
|
||||
const [localToken, setLocalToken] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [fetchSuccess, setFetchSuccess] = useState<boolean>(false);
|
||||
@@ -91,14 +92,34 @@ const App: React.FC = () => {
|
||||
}
|
||||
}, [localEmail]);
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlEmail = urlParams.get("email");
|
||||
const urlToken = urlParams.get("token");
|
||||
|
||||
if (urlEmail && urlToken) {
|
||||
setLocalEmail(urlEmail);
|
||||
setLocalToken(urlToken);
|
||||
console.log(localEmail);
|
||||
console.log(localToken);
|
||||
setEmail(urlEmail);
|
||||
setToken(urlToken);
|
||||
fetchData().catch((error: unknown) =>
|
||||
console.error("Fetch data error:", error),
|
||||
);
|
||||
}
|
||||
console.log(email);
|
||||
console.log(token);
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
setFetchSuccess(false);
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const encodedEmail = encodeURIComponent(localEmail);
|
||||
const encodedToken = encodeURIComponent(localToken);
|
||||
const encodedEmail = encodeURIComponent(email);
|
||||
const encodedToken = encodeURIComponent(token);
|
||||
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
console.log(`Fetching data from URL: ${url}`);
|
||||
const response = await fetch(url);
|
||||
@@ -156,7 +177,7 @@ const App: React.FC = () => {
|
||||
.isTwoFactorEnabled
|
||||
? "Enabled"
|
||||
: "Disabled",
|
||||
Passkeys: "None", // Replace with actual passkey value if available
|
||||
Passkeys: "None",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -195,7 +216,7 @@ const App: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container center-table">
|
||||
<div className="container">
|
||||
<form className="input-form" onKeyPress={handleKeyPress}>
|
||||
<div className="horizontal-group">
|
||||
<a
|
||||
@@ -259,7 +280,6 @@ const App: React.FC = () => {
|
||||
width: "100%",
|
||||
maxWidth: "600px",
|
||||
bgcolor: "#FAFAFA",
|
||||
marginTop: "300px",
|
||||
borderRadius: "7px",
|
||||
position: "relative",
|
||||
zIndex: 1000,
|
||||
@@ -278,12 +298,6 @@ const App: React.FC = () => {
|
||||
"& .MuiTab-root": {
|
||||
textTransform: "none",
|
||||
},
|
||||
"& .Mui-selected": {
|
||||
color: "black !important",
|
||||
},
|
||||
"& .MuiTab-root.Mui-selected": {
|
||||
color: "black !important",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tab label="User" />
|
||||
@@ -294,26 +308,32 @@ const App: React.FC = () => {
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
maxWidth: "600px",
|
||||
mt: 4,
|
||||
minHeight: "400px",
|
||||
maxWidth: "900px",
|
||||
bgcolor: "#FAFAFA",
|
||||
borderRadius: "7px",
|
||||
padding: "20px",
|
||||
position: "relative",
|
||||
zIndex: 999,
|
||||
marginTop: "16px",
|
||||
}}
|
||||
>
|
||||
{tabValue === 0 && (
|
||||
{tabValue === 0 && userData && (
|
||||
<UserComponent userData={userData} />
|
||||
)}
|
||||
{tabValue === 1 && <div>Family tab content</div>}
|
||||
{tabValue === 2 && <div>Bonuses tab content</div>}
|
||||
{tabValue === 1 && userData && (
|
||||
<div>
|
||||
<FamilyTableComponent />
|
||||
</div>
|
||||
)}
|
||||
{tabValue === 2 && userData && (
|
||||
<div>
|
||||
<StorageBonusTableComponent />
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<div className="duckie-container">
|
||||
<img
|
||||
src={duckieimage}
|
||||
alt="Duckie"
|
||||
className="duckie-image"
|
||||
/>
|
||||
</div>
|
||||
<img src={duckieimage} alt="duckie" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
157
infra/staff/src/components/CloseFamily.tsx
Normal file
157
infra/staff/src/components/CloseFamily.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Paper,
|
||||
} from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { getEmail, getToken } from "../App"; // Import getEmail and getToken functions
|
||||
import { apiOrigin } from "../services/support";
|
||||
|
||||
interface UserData {
|
||||
subscription?: {
|
||||
userID: string;
|
||||
// Add other properties as per your API response structure
|
||||
};
|
||||
// Add other properties as per your API response structure
|
||||
}
|
||||
|
||||
interface CloseFamilyProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleCloseFamily: () => void; // Callback to handle closing family
|
||||
}
|
||||
|
||||
const CloseFamily: React.FC<CloseFamilyProps> = ({
|
||||
open,
|
||||
handleClose,
|
||||
handleCloseFamily,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleClosure = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const email = getEmail();
|
||||
const token = getToken();
|
||||
|
||||
if (!email) {
|
||||
throw new Error("Email not found");
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Token not found");
|
||||
}
|
||||
|
||||
const encodedEmail = encodeURIComponent(email);
|
||||
const encodedToken = encodeURIComponent(token);
|
||||
|
||||
// Fetch user data
|
||||
const userUrl = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
const userResponse = await fetch(userUrl);
|
||||
if (!userResponse.ok) {
|
||||
throw new Error("Failed to fetch user data");
|
||||
}
|
||||
const userData = (await userResponse.json()) as UserData;
|
||||
const userId = userData.subscription?.userID;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("User ID not found");
|
||||
}
|
||||
|
||||
// Close family action
|
||||
const closeFamilyUrl = `${apiOrigin}/admin/user/close-family?token=${encodedToken}`;
|
||||
const body = JSON.stringify({ userId });
|
||||
const closeFamilyResponse = await fetch(closeFamilyUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: body,
|
||||
});
|
||||
|
||||
if (!closeFamilyResponse.ok) {
|
||||
const errorResponse = await closeFamilyResponse.text();
|
||||
throw new Error(`Failed to close family: ${errorResponse}`);
|
||||
}
|
||||
|
||||
handleCloseFamily(); // Notify parent component of successful action
|
||||
handleClose(); // Close dialog on successful action
|
||||
console.log("Family closed successfully");
|
||||
} catch (error) {
|
||||
console.error("Error closing family:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
handleClose(); // Close dialog
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
PaperComponent={Paper}
|
||||
sx={{
|
||||
width: "499px",
|
||||
height: "286px",
|
||||
margin: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
BackdropProps={{
|
||||
style: {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.9)", // Semi-transparent backdrop
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Close Family?"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to close family relations for this
|
||||
account?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center" }}>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
sx={{
|
||||
bgcolor: "white",
|
||||
color: "black",
|
||||
"&:hover": { bgcolor: "#FAFAFA" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleClosure().catch((error: unknown) =>
|
||||
console.error(error),
|
||||
);
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: "#F4473D",
|
||||
color: "white",
|
||||
"&:hover": { bgcolor: "#E53935" },
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Closing..." : "Close"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloseFamily;
|
||||
157
infra/staff/src/components/DisablePasskeys.tsx
Normal file
157
infra/staff/src/components/DisablePasskeys.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Paper,
|
||||
} from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
import { getEmail, getToken } from "../App"; // Import getEmail and getToken functions
|
||||
import { apiOrigin } from "../services/support";
|
||||
|
||||
interface UserData {
|
||||
subscription?: {
|
||||
userID: string;
|
||||
// Add other properties as per your API response structure
|
||||
};
|
||||
// Add other properties as per your API response structure
|
||||
}
|
||||
|
||||
interface DisablePasskeysProps {
|
||||
open: boolean;
|
||||
handleClose: () => void;
|
||||
handleDisablePasskeys: () => void; // Callback to handle disabling passkeys
|
||||
}
|
||||
|
||||
const DisablePasskeys: React.FC<DisablePasskeysProps> = ({
|
||||
open,
|
||||
handleClose,
|
||||
handleDisablePasskeys,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleDisabling = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const email = getEmail();
|
||||
const token = getToken();
|
||||
|
||||
if (!email) {
|
||||
throw new Error("Email not found");
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new Error("Token not found");
|
||||
}
|
||||
|
||||
const encodedEmail = encodeURIComponent(email);
|
||||
const encodedToken = encodeURIComponent(token);
|
||||
|
||||
// Fetch user data
|
||||
const userUrl = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
const userResponse = await fetch(userUrl);
|
||||
if (!userResponse.ok) {
|
||||
throw new Error("Failed to fetch user data");
|
||||
}
|
||||
const userData = (await userResponse.json()) as UserData;
|
||||
const userId = userData.subscription?.userID;
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("User ID not found");
|
||||
}
|
||||
|
||||
// Disable passkeys action
|
||||
const disablePasskeysUrl = `${apiOrigin}/admin/user/disable-passkeys?token=${encodedToken}`;
|
||||
const body = JSON.stringify({ userId });
|
||||
const disablePasskeysResponse = await fetch(disablePasskeysUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: body,
|
||||
});
|
||||
|
||||
if (!disablePasskeysResponse.ok) {
|
||||
const errorResponse = await disablePasskeysResponse.text();
|
||||
throw new Error(`Failed to disable passkeys: ${errorResponse}`);
|
||||
}
|
||||
|
||||
handleDisablePasskeys(); // Notify parent component of successful action
|
||||
handleClose(); // Close dialog on successful action
|
||||
console.log("Passkeys disabled successfully");
|
||||
} catch (error) {
|
||||
console.error("Error disabling passkeys:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
handleClose(); // Close dialog
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
PaperComponent={Paper}
|
||||
sx={{
|
||||
width: "499px",
|
||||
height: "286px",
|
||||
margin: "auto",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
BackdropProps={{
|
||||
style: {
|
||||
backgroundColor: "rgba(255, 255, 255, 0.9)", // Semi-transparent backdrop
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Disable Passkeys?"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to disable passkeys for this
|
||||
account?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: "center" }}>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
sx={{
|
||||
bgcolor: "white",
|
||||
color: "black",
|
||||
"&:hover": { bgcolor: "#FAFAFA" },
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleDisabling().catch((error: unknown) =>
|
||||
console.error(error),
|
||||
);
|
||||
}}
|
||||
sx={{
|
||||
bgcolor: "#F4473D",
|
||||
color: "white",
|
||||
"&:hover": { bgcolor: "#E53935" },
|
||||
}}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? "Disabling..." : "Disable"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisablePasskeys;
|
||||
176
infra/staff/src/components/FamilyComponentTable.tsx
Normal file
176
infra/staff/src/components/FamilyComponentTable.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@mui/material";
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getEmail, getToken } from "../App";
|
||||
import { apiOrigin } from "../services/support";
|
||||
import CloseFamily from "./CloseFamily";
|
||||
|
||||
interface FamilyMember {
|
||||
id: string;
|
||||
email: string;
|
||||
status: string;
|
||||
usage: number;
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
details: {
|
||||
familyData: {
|
||||
members: FamilyMember[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const FamilyTableComponent: React.FC = () => {
|
||||
const [familyMembers, setFamilyMembers] = useState<FamilyMember[]>([]);
|
||||
const [closeFamilyOpen, setCloseFamilyOpen] = useState(false);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const encodedEmail = encodeURIComponent(getEmail());
|
||||
const encodedToken = encodeURIComponent(getToken());
|
||||
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const userData = (await response.json()) as UserData; // Typecast to UserData interface
|
||||
const members: FamilyMember[] =
|
||||
userData.details.familyData.members;
|
||||
setFamilyMembers(members);
|
||||
} catch (error) {
|
||||
console.error("Error fetching family data:", error);
|
||||
setError("No family data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData().catch((error: unknown) =>
|
||||
console.error("Fetch data error:", error),
|
||||
);
|
||||
}, []);
|
||||
|
||||
const formatUsageToGB = (usage: number): string => {
|
||||
const usageInGB = (usage / (1024 * 1024 * 1024)).toFixed(2);
|
||||
return `${usageInGB} GB`;
|
||||
};
|
||||
|
||||
const handleOpenCloseFamily = () => {
|
||||
setCloseFamilyOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseCloseFamily = () => {
|
||||
setCloseFamilyOpen(false);
|
||||
};
|
||||
|
||||
const handleCloseFamily = () => {
|
||||
console.log("Close family action");
|
||||
handleOpenCloseFamily();
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error}</div>;
|
||||
}
|
||||
|
||||
if (familyMembers.length === 0) {
|
||||
return <div>No family data available</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
style={{
|
||||
marginTop: "20px",
|
||||
backgroundColor: "#F1F1F3",
|
||||
}}
|
||||
>
|
||||
<Table aria-label="family-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<b>User</b>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b>Status</b>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b>Usage</b>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b>ID</b>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{familyMembers.map((member) => (
|
||||
<TableRow key={member.id}>
|
||||
<TableCell>{member.email}</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
style={{
|
||||
backgroundColor:
|
||||
member.status === "SELF"
|
||||
? "#00B33C"
|
||||
: "transparent",
|
||||
color:
|
||||
member.status === "SELF"
|
||||
? "white"
|
||||
: "inherit",
|
||||
padding: "4px 8px",
|
||||
borderRadius: "10px",
|
||||
}}
|
||||
>
|
||||
{member.status === "SELF"
|
||||
? "ADMIN"
|
||||
: member.status}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{formatUsageToGB(member.usage)}
|
||||
</TableCell>
|
||||
<TableCell>{member.id}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={handleCloseFamily}
|
||||
>
|
||||
Close Family
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{closeFamilyOpen && (
|
||||
<CloseFamily
|
||||
open={closeFamilyOpen}
|
||||
handleClose={handleCloseCloseFamily}
|
||||
handleCloseFamily={handleCloseCloseFamily}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default FamilyTableComponent;
|
||||
160
infra/staff/src/components/StorageBonusTableComponent.tsx
Normal file
160
infra/staff/src/components/StorageBonusTableComponent.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import {
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
} from "@mui/material";
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getEmail, getToken } from "../App";
|
||||
import { apiOrigin } from "../services/support";
|
||||
|
||||
interface BonusData {
|
||||
storage: number;
|
||||
type: string;
|
||||
createdAt: number;
|
||||
validTill: number;
|
||||
isRevoked: boolean;
|
||||
}
|
||||
|
||||
interface UserData {
|
||||
details: {
|
||||
bonusData: {
|
||||
storageBonuses: BonusData[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const StorageBonusTableComponent: React.FC = () => {
|
||||
const [storageBonuses, setStorageBonuses] = useState<BonusData[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const encodedEmail = encodeURIComponent(getEmail());
|
||||
const encodedToken = encodeURIComponent(getToken());
|
||||
const url = `${apiOrigin}/admin/user?email=${encodedEmail}&token=${encodedToken}`;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch bonus data");
|
||||
}
|
||||
const userData = (await response.json()) as UserData; // Typecast to UserData interface
|
||||
const bonuses: BonusData[] =
|
||||
userData.details.bonusData.storageBonuses;
|
||||
setStorageBonuses(bonuses);
|
||||
} catch (error) {
|
||||
console.error("Error fetching bonus data:", error);
|
||||
setError("No bonus data");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData().catch((error: unknown) =>
|
||||
console.error("Fetch data error:", error),
|
||||
);
|
||||
}, []);
|
||||
|
||||
const formatCreatedAt = (createdAt: number): string => {
|
||||
const date = new Date(createdAt / 1000);
|
||||
return date.toLocaleDateString(); // Adjust date formatting as needed
|
||||
};
|
||||
|
||||
const formatValidTill = (validTill: number): string => {
|
||||
if (validTill === 0) {
|
||||
return "Forever";
|
||||
} else {
|
||||
const date = new Date(validTill / 1000);
|
||||
return date.toLocaleDateString(); // Adjust date formatting as needed
|
||||
}
|
||||
};
|
||||
|
||||
const formatStorage = (storage: number): string => {
|
||||
const inGB = storage / (1024 * 1024 * 1024);
|
||||
return `${inGB.toFixed(2)} GB`;
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <p>Error: {error}</p>;
|
||||
}
|
||||
|
||||
if (storageBonuses.length === 0) {
|
||||
return <p>No bonus data available</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "20px", marginBottom: "20px" }}>
|
||||
<TableContainer
|
||||
component={Paper}
|
||||
style={{
|
||||
backgroundColor: "#F1F1F3",
|
||||
}}
|
||||
>
|
||||
<Table aria-label="storage-bonus-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<b>Storage</b>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b>Type</b>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b>Created At</b>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b>Valid Till</b>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<b>Is Revoked</b>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{storageBonuses.map((bonus, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>
|
||||
{formatStorage(bonus.storage)}
|
||||
</TableCell>
|
||||
<TableCell>{bonus.type}</TableCell>
|
||||
<TableCell>
|
||||
{formatCreatedAt(bonus.createdAt)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{formatValidTill(bonus.validTill)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: bonus.isRevoked
|
||||
? "#494949"
|
||||
: "transparent",
|
||||
color: bonus.isRevoked
|
||||
? "white"
|
||||
: "inherit",
|
||||
padding: "4px 8px",
|
||||
borderRadius: "10px",
|
||||
}}
|
||||
>
|
||||
{bonus.isRevoked ? "Yes" : "No"}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StorageBonusTableComponent;
|
||||
@@ -246,6 +246,7 @@ const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
|
||||
<MenuItem value="stripe">Stripe</MenuItem>
|
||||
<MenuItem value="paypal">PayPal</MenuItem>
|
||||
<MenuItem value="bitpay">BitPay</MenuItem>
|
||||
<MenuItem value="None">None</MenuItem>
|
||||
</Select>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import Box from "@mui/material/Box";
|
||||
import Button from "@mui/material/Button";
|
||||
|
||||
import Grid from "@mui/material/Grid";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Paper from "@mui/material/Paper";
|
||||
@@ -15,8 +17,8 @@ import * as React from "react";
|
||||
import ChangeEmail from "./ChangeEmail";
|
||||
import DeleteAccount from "./DeleteAccont";
|
||||
import Disable2FA from "./Disable2FA";
|
||||
import DisablePasskeys from "./DisablePasskeys";
|
||||
import UpdateSubscription from "./UpdateSubscription";
|
||||
|
||||
export interface UserData {
|
||||
User: Record<string, string>;
|
||||
Storage: Record<string, string>;
|
||||
@@ -36,6 +38,7 @@ const UserComponent: React.FC<UserComponentProps> = ({ userData }) => {
|
||||
const [updateSubscriptionOpen, setUpdateSubscriptionOpen] =
|
||||
React.useState(false);
|
||||
const [changeEmailOpen, setChangeEmailOpen] = React.useState(false); // State for ChangeEmail dialog
|
||||
const [DisablePasskeysOpen, setDisablePasskeysOpen] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (userData?.Security["Two factor 2FA"] === "Enabled") {
|
||||
@@ -87,6 +90,20 @@ const UserComponent: React.FC<UserComponentProps> = ({ userData }) => {
|
||||
setUpdateSubscriptionOpen(false);
|
||||
};
|
||||
|
||||
const handleOpenDisablePasskeys = () => {
|
||||
setDisablePasskeysOpen(true); // Open the CloseFamily dialog
|
||||
};
|
||||
|
||||
const handleCloseDisablePasskeys = () => {
|
||||
setDisablePasskeysOpen(false); // Close the CloseFamily dialog
|
||||
};
|
||||
|
||||
const handleDisablePasskeys = () => {
|
||||
// Implement your logic to close family here
|
||||
console.log("Close family action");
|
||||
handleOpenDisablePasskeys(); // Open CloseFamily dialog after closing family
|
||||
};
|
||||
|
||||
if (!userData) {
|
||||
return null;
|
||||
}
|
||||
@@ -99,6 +116,7 @@ const UserComponent: React.FC<UserComponentProps> = ({ userData }) => {
|
||||
component={Paper}
|
||||
variant="outlined"
|
||||
sx={{
|
||||
backgroundColor: "#F1F1F3",
|
||||
minHeight: 300,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@@ -214,6 +232,15 @@ const UserComponent: React.FC<UserComponentProps> = ({ userData }) => {
|
||||
/>
|
||||
</IconButton>
|
||||
</Box>
|
||||
) : label === "Passkeys" ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={
|
||||
handleDisablePasskeys
|
||||
}
|
||||
>
|
||||
Disable Passkeys
|
||||
</Button>
|
||||
) : typeof value === "string" ? (
|
||||
label === "Two factor 2FA" ? (
|
||||
is2FADisabled ||
|
||||
@@ -328,6 +355,13 @@ const UserComponent: React.FC<UserComponentProps> = ({ userData }) => {
|
||||
open={changeEmailOpen}
|
||||
onClose={handleCloseChangeEmail}
|
||||
/>
|
||||
|
||||
{/* Render Passkeys Dialog */}
|
||||
<DisablePasskeys
|
||||
open={DisablePasskeysOpen}
|
||||
handleClose={handleCloseDisablePasskeys}
|
||||
handleDisablePasskeys={handleCloseDisablePasskeys}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user