Full dashboard

This commit is contained in:
atyabbin
2024-07-18 17:58:42 +05:30
parent bd3e0c9289
commit 0c6d27c134
8 changed files with 775 additions and 29 deletions

View File

@@ -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;
}

View File

@@ -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>

View 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;

View 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;

View 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;

View 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;

View File

@@ -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>

View File

@@ -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>
);
};