much improved version

This commit is contained in:
Jincheng Lu 2025-11-28 12:03:29 +08:00
parent e87992cbbd
commit 9b2bf6b658
6 changed files with 237 additions and 22 deletions

38
app/Components/About.tsx Normal file
View File

@ -0,0 +1,38 @@
import { Box, Card, CardContent, Typography, Link, Button, Avatar, Stack } from "@mui/material";
import GitHubIcon from "@mui/icons-material/GitHub";
export default function About() {
const repo = "https://github.com/";
return (
<Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
<Card sx={{ maxWidth: 800, width: "100%" }}>
<CardContent>
<Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 2 }}>
<Avatar sx={{ bgcolor: "primary.main" }}>
<GitHubIcon />
</Avatar>
<Typography variant="h5">Depin Web Client</Typography>
</Stack>
<Typography variant="body1" color="text.secondary" paragraph>
Web client for the MSBD5017 Depin project.
</Typography>
<Typography variant="body2" paragraph>
In case the website is down, you can run your own local instance by following the instructions in the GitHub repository.
</Typography>
<Stack direction={{ xs: "column", sm: "row" }} spacing={2} alignItems="center">
<Link href={repo} target="_blank" rel="noopener noreferrer" underline="none">
<Button startIcon={<GitHubIcon />} variant="outlined">
Open GitHub Repository
</Button>
</Link>
</Stack>
</CardContent>
</Card>
</Box>
);
}
// ...existing code...

View File

@ -0,0 +1,126 @@
// ...existing code...
import React, { useState } from "react";
import { useSyncProviders } from "./useSyncProviders";
import useAuth from "~/hooks/useAuth";
import type { EIP6963ProviderDetail } from "./EthereumProviderTypes";
import {
Box,
Grid,
Card,
CardContent,
Avatar,
Typography,
Button,
Stack,
CircularProgress,
Alert,
} from "@mui/material";
const shortAddress = (addr: string) =>
addr.length > 12 ? `${addr.slice(0, 6)}...${addr.slice(-6)}` : addr;
export const DiscoverWalletProviders: React.FC = () => {
const providers = useSyncProviders();
const { auth, setAuth } = useAuth();
const [loadingUuid, setLoadingUuid] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const handleConnect = async (providerWithInfo: EIP6963ProviderDetail) => {
setError(null);
setLoadingUuid(providerWithInfo.info.uuid);
try {
const accounts = await providerWithInfo.provider.request({
method: "eth_requestAccounts",
});
if (Array.isArray(accounts) && accounts.length > 0 && typeof accounts[0] === "string") {
setAuth({ providerWithInfo, accounts });
} else {
setError("No accounts returned from wallet.");
}
} catch (err: any) {
console.error(err);
setError(err?.message ?? String(err));
} finally {
setLoadingUuid(null);
}
};
return (
<Box>
<Typography variant="h6" gutterBottom>
Wallets Detected
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
{providers.length === 0 ? (
<Alert severity="info">There are no announced providers.</Alert>
) : (
<Grid container spacing={2}>
{providers.map((p) => (
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={p.info.uuid}>
<Card variant="outlined">
<CardContent>
<Stack direction="row" spacing={2} alignItems="center">
<Avatar src={p.info.icon} alt={p.info.name} />
<Box sx={{ flex: 1 }}>
<Typography variant="subtitle1">{p.info.name}</Typography>
</Box>
<Box>
<Button
variant="contained"
onClick={() => handleConnect(p)}
disabled={!!loadingUuid}
startIcon={loadingUuid === p.info.uuid ? <CircularProgress size={18} /> : null}
aria-label={`Connect to ${p.info.name}`}
>
{loadingUuid === p.info.uuid ? "Connecting" : "Connect"}
</Button>
</Box>
</Stack>
</CardContent>
</Card>
</Grid>
))}
</Grid>
)}
<Box sx={{ mt: 3 }}>
<Typography variant="h6" gutterBottom>
{auth?.accounts?.length > 0 ? "Wallet Selected" : "No Wallet Selected"}
</Typography>
{auth?.accounts?.length > 0 && auth.providerWithInfo && (
<Card variant="outlined">
<CardContent>
<Stack direction="row" spacing={2} alignItems="center">
<Avatar src={auth.providerWithInfo.info.icon} alt={auth.providerWithInfo.info.name} />
<Box sx={{ flex: 1 }}>
<Typography variant="subtitle1">{auth.providerWithInfo.info.name}</Typography>
<Typography variant="body2" color="text.secondary">
{shortAddress(auth.accounts[0])}
</Typography>
</Box>
<Button
variant="outlined"
onClick={() => setAuth({ providerWithInfo: { ...auth.providerWithInfo }, accounts: [] })}
aria-label="Disconnect wallet"
>
Disconnect
</Button>
</Stack>
</CardContent>
</Card>
)}
</Box>
</Box>
);
};
export default DiscoverWalletProviders;
// ...existing code...

View File

@ -2,9 +2,11 @@ import * as React from 'react';
import ButtonGroup from '@mui/material/ButtonGroup';
import Button from '@mui/material/Button';
import Avatar from '@mui/material/Avatar';
import { Grid, Card, CardContent, CardActions, Typography, Box, Chip } from "@mui/material";
import { Grid, Card, CardContent, CardActions, Typography, Box, Chip, Dialog, DialogTitle, DialogContent, DialogActions, TextField } from "@mui/material";
import ServerIcon from '@mui/icons-material/Dns';
import SignalCellularAltIcon from '@mui/icons-material/SignalCellularAlt';
import AttachMoneyIcon from '@mui/icons-material/AttachMoney';
import Rating from '@mui/material/Rating';
import useAuth from '~/hooks/useAuth';
import { signMessage } from './Metamask/Connections';
import axios from 'axios';
@ -12,10 +14,14 @@ import axios from 'axios';
interface Node {
ip: string;
traffic: number;
price : number;
}
function NodeItem({node}: {node: Node}) {
const { auth } = useAuth();
const [ratingOpen, setRatingOpen] = React.useState(false);
const [ratingValue, setRatingValue] = React.useState<number | null>(null);
const [submittingRating, setSubmittingRating] = React.useState(false);
const connect = async ({ip}: {ip: string}) => {
const res = await signMessage(auth.providerWithInfo.provider, auth.accounts[0]);
try {
@ -28,7 +34,33 @@ function NodeItem({node}: {node: Node}) {
}
const disconnect = ({ip}: {ip: string}) => {
console.log("Disconnect from ", ip);
// TODO check code
setRatingOpen(true);
}
const handleCloseRating = () => {
setRatingOpen(false);
setRatingValue(null);
};
const handleSubmitRating = async () => {
if (ratingValue == null) {
return;
}
setSubmittingRating(true);
try {
// await axios.post('http://' + node.ip + ":8080/rate", {
// rating: ratingValue,
// }, {
// headers: { "Content-Type": "application/json" }
// });
// console.log('Rating submitted');
} catch (err) {
console.error('Failed to submit rating', err);
} finally {
setSubmittingRating(false);
handleCloseRating();
}
}
return (
@ -56,6 +88,19 @@ function NodeItem({node}: {node: Node}) {
variant="outlined"
/>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<AttachMoneyIcon color="action" fontSize="small" />
<Typography variant="body2" color="text.secondary">
Price:
</Typography>
<Chip
label={node.price}
size="small"
color={node.price > 15 ? "warning" : "success"}
variant="outlined"
/>
</Box>
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end', p: 2, pt: 0 }}>
<ButtonGroup
@ -79,12 +124,30 @@ function NodeItem({node}: {node: Node}) {
</ButtonGroup>
</CardActions>
</Card>
<Dialog open={ratingOpen} onClose={handleCloseRating}>
<DialogTitle>Rate this server</DialogTitle>
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, minWidth: 320 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography component="span">Your rating:</Typography>
<Rating
name={`server-rating-${node.ip}`}
value={ratingValue}
onChange={(_, newValue) => setRatingValue(newValue)}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseRating} disabled={submittingRating}>Cancel</Button>
<Button onClick={handleSubmitRating} disabled={submittingRating || ratingValue == null} variant="contained">Submit</Button>
</DialogActions>
</Dialog>
</Grid>
);
}
export default function FolderList() {
const [nodes, setNodes] = React.useState([{ip: "57.158.82.48", traffic: 5},{ip: "10.0.0.3", traffic: 3},{ip: "10.0.0.4", traffic: 7}]);
const [nodes, setNodes] = React.useState([{ip: "57.158.82.48", traffic: 5, price: 10},{ip: "57.158.82.47", traffic: 3, price: 15},{ip: "57.158.82.49", traffic: 7, price: 20}]);
return (
<Box sx={{ flexGrow: 1, p: 3 }}>

View File

@ -1,8 +1,8 @@
import About from "~/Components/About";
export default function About() {
export default function AboutPage() {
return(
<h3>About Page</h3>
<About />
);
}

View File

@ -1,7 +1,10 @@
import { DiscoverWalletProviders } from "~/Components/Metamask/DiscoverWalletProviders";
import NewDiscover from "~/Components/Metamask/NewDiscover";
export default function Wallets() {
return(
<DiscoverWalletProviders />
// <DiscoverWalletProviders />
<NewDiscover />
);
}

15
package-lock.json generated
View File

@ -60,7 +60,6 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@ -566,7 +565,6 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@ -610,7 +608,6 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
@ -1209,7 +1206,6 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
"integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4",
"@mui/core-downloads-tracker": "^7.3.5",
@ -1598,7 +1594,6 @@
"resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.9.6.tgz",
"integrity": "sha512-qIT8hp1RJ0VAHyXpfuwoO31b9evrjPLRhUugqYJ7BZLpyAwhRsJIaQvvj60yZwWBMF2/3LdZu7M39rf0FhL6Iw==",
"license": "MIT",
"peer": true,
"dependencies": {
"@mjackson/node-fetch-server": "^0.2.0",
"@react-router/express": "7.9.6",
@ -2219,7 +2214,6 @@
"integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@ -2241,7 +2235,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz",
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@ -2495,7 +2488,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@ -3134,7 +3126,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@ -4465,7 +4456,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -4629,7 +4619,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -4639,7 +4628,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@ -4668,7 +4656,6 @@
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz",
"integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==",
"license": "MIT",
"peer": true,
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
@ -5317,7 +5304,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -5433,7 +5419,6 @@
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",