| [40ac7a9] | 1 | import React, {useEffect, useState} from 'react';
|
|---|
| 2 | import {
|
|---|
| 3 | Container,
|
|---|
| 4 | Paper,
|
|---|
| 5 | Typography,
|
|---|
| 6 | Box,
|
|---|
| 7 | Avatar,
|
|---|
| 8 | Divider,
|
|---|
| 9 | Button,
|
|---|
| 10 | CircularProgress,
|
|---|
| 11 | Card,
|
|---|
| 12 | CardContent,
|
|---|
| 13 | CardActionArea,
|
|---|
| 14 | IconButton
|
|---|
| 15 | } from '@mui/material';
|
|---|
| 16 | import Grid from '@mui/material/Grid';
|
|---|
| 17 | import {Dialog, DialogTitle, DialogContent, DialogActions} from '@mui/material';
|
|---|
| 18 | import CloseIcon from '@mui/icons-material/Close';
|
|---|
| 19 |
|
|---|
| 20 | import PersonIcon from '@mui/icons-material/Person';
|
|---|
| 21 | import ComputerIcon from '@mui/icons-material/Computer';
|
|---|
| 22 | import FavoriteIcon from '@mui/icons-material/Favorite';
|
|---|
| 23 | import DeleteIcon from '@mui/icons-material/Delete';
|
|---|
| 24 | import EditIcon from '@mui/icons-material/Edit';
|
|---|
| 25 |
|
|---|
| 26 | import {getUserInfoAndData, onDeleteBuild} from "./userDashboard.telefunc";
|
|---|
| 27 |
|
|---|
| 28 | type DashboardData = {
|
|---|
| 29 | user: { username: string; email?: string; [key: string]: any };
|
|---|
| 30 | userBuilds: any[];
|
|---|
| 31 | favoriteBuilds: any[];
|
|---|
| 32 | };
|
|---|
| 33 |
|
|---|
| 34 | export default function UserDashboard() {
|
|---|
| 35 | const [data, setData] = useState<DashboardData | null>(null);
|
|---|
| 36 | const [loading, setLoading] = useState(true);
|
|---|
| 37 | const [error, setError] = useState<string | null>(null);
|
|---|
| 38 | const [openMyBuildsDialog, setOpenMyBuildsDialog] = useState(false);
|
|---|
| 39 |
|
|---|
| 40 | const loadData = () => {
|
|---|
| 41 | getUserInfoAndData()
|
|---|
| 42 | .then((result) => {
|
|---|
| 43 | setData(result);
|
|---|
| 44 | setLoading(false);
|
|---|
| 45 | })
|
|---|
| 46 | .catch((err) => {
|
|---|
| 47 | console.error(err);
|
|---|
| 48 | setError("Failed to load dashboard. Please log in.");
|
|---|
| 49 | setLoading(false);
|
|---|
| 50 | });
|
|---|
| 51 | };
|
|---|
| 52 |
|
|---|
| 53 | useEffect(() => {
|
|---|
| 54 | loadData();
|
|---|
| 55 | }, []);
|
|---|
| 56 |
|
|---|
| 57 | const handleDelete = async (buildId: number) => {
|
|---|
| 58 | if (!confirm("Are you sure you want to delete this build?")) return;
|
|---|
| 59 | try {
|
|---|
| 60 | await onDeleteBuild({buildId});
|
|---|
| 61 | loadData();
|
|---|
| 62 | } catch (e) {
|
|---|
| 63 | alert("Failed to delete build");
|
|---|
| 64 | }
|
|---|
| 65 | };
|
|---|
| 66 |
|
|---|
| 67 | if (loading) return <Box sx={{display: 'flex', justifyContent: 'center', mt: 10}}><CircularProgress/></Box>;
|
|---|
| 68 | if (error || !data) return <Container sx={{mt: 5, textAlign: 'center'}}><Typography color="error"
|
|---|
| 69 | variant="h6">{error}</Typography><Button
|
|---|
| 70 | href="/auth/login" variant="contained">Login</Button></Container>;
|
|---|
| 71 |
|
|---|
| 72 |
|
|---|
| 73 | // @ts-ignore
|
|---|
| 74 | return (
|
|---|
| 75 | <Container maxWidth="xl" sx={{mt: 4, mb: 4, color: 'white'}}>
|
|---|
| 76 | <Grid container spacing={3}>
|
|---|
| 77 |
|
|---|
| 78 | <Grid item xs={12} md={3}>
|
|---|
| 79 | <Paper elevation={3}
|
|---|
| 80 | sx={{p: 3, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%'}}>
|
|---|
| 81 | <Avatar sx={{width: 100, height: 100, mb: 2, bgcolor: 'primary.main'}}><PersonIcon
|
|---|
| 82 | sx={{fontSize: 60}}/></Avatar>
|
|---|
| 83 | <Typography variant="h5" fontWeight="bold" gutterBottom>{data.user.username}</Typography>
|
|---|
| 84 | <Typography variant="body2" color="text.secondary" gutterBottom>{data.user.email}</Typography>
|
|---|
| 85 | <Divider sx={{width: '100%', my: 2}}/>
|
|---|
| 86 | {/*<Button variant="outlined" fullWidth color="primary" sx={{mb: 1}}>Edit Profile</Button>*/}
|
|---|
| 87 | </Paper>
|
|---|
| 88 | </Grid>
|
|---|
| 89 |
|
|---|
| 90 | <Grid item xs={12} md={9}>
|
|---|
| 91 | <Grid container spacing={3} direction="column">
|
|---|
| 92 |
|
|---|
| 93 | <Grid item xs={12}>
|
|---|
| 94 | <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}>
|
|---|
| 95 | <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}>
|
|---|
| 96 | <FavoriteIcon color="error" sx={{mr: 1}}/>
|
|---|
| 97 | <Typography variant="h6" fontWeight="bold">Favorited Builds</Typography>
|
|---|
| 98 | </Box>
|
|---|
| 99 | <Divider sx={{mb: 2}}/>
|
|---|
| 100 |
|
|---|
| 101 | {data.favoriteBuilds.length === 0 ? (
|
|---|
| 102 | <Typography color="text.secondary" align="center" sx={{mt: 4}}>You haven't favorited
|
|---|
| 103 | any builds yet.</Typography>
|
|---|
| 104 | ) : (
|
|---|
| 105 | <Grid container spacing={2}>
|
|---|
| 106 | {data.favoriteBuilds.map((build: any) => (
|
|---|
| 107 | <Grid item xs={12} sm={6} md={4} key={build.id}>
|
|---|
| 108 | <BuildCard build={build}/>
|
|---|
| 109 | </Grid>
|
|---|
| 110 | ))}
|
|---|
| 111 | </Grid>
|
|---|
| 112 | )}
|
|---|
| 113 | </Paper>
|
|---|
| 114 | </Grid>
|
|---|
| 115 |
|
|---|
| 116 | <Grid item xs={12}>
|
|---|
| 117 | <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}>
|
|---|
| 118 |
|
|---|
| 119 | <Box sx={{
|
|---|
| 120 | display: 'flex',
|
|---|
| 121 | alignItems: 'center',
|
|---|
| 122 | justifyContent: 'space-between',
|
|---|
| 123 | mb: 2
|
|---|
| 124 | }}>
|
|---|
| 125 | <Box sx={{display: 'flex', alignItems: 'center'}}>
|
|---|
| 126 | <ComputerIcon color="primary" sx={{mr: 1}}/>
|
|---|
| 127 | <Typography variant="h6" fontWeight="bold">My Builds</Typography>
|
|---|
| 128 | </Box>
|
|---|
| 129 |
|
|---|
| 130 | {data.userBuilds.length > 4 && (
|
|---|
| 131 | <Button
|
|---|
| 132 | variant="outlined"
|
|---|
| 133 | size="small"
|
|---|
| 134 | onClick={() => setOpenMyBuildsDialog(true)}
|
|---|
| 135 | >
|
|---|
| 136 | See All ({data.userBuilds.length})
|
|---|
| 137 | </Button>
|
|---|
| 138 | )}
|
|---|
| 139 | </Box>
|
|---|
| 140 | <Divider sx={{mb: 2}}/>
|
|---|
| 141 |
|
|---|
| 142 | {data.userBuilds.length === 0 ? (
|
|---|
| 143 | <Box sx={{textAlign: 'center', mt: 4}}>
|
|---|
| 144 | <Typography color="text.secondary" gutterBottom>You haven't created any builds
|
|---|
| 145 | yet.</Typography>
|
|---|
| 146 | <Button variant="contained" href="/forge"
|
|---|
| 147 | sx={{color: 'white', fontWeight: 'bolder'}}>Start Forging</Button>
|
|---|
| 148 | </Box>
|
|---|
| 149 | ) : (
|
|---|
| 150 | <Grid container spacing={2}>
|
|---|
| 151 | {data.userBuilds.slice(0, 3).map((build: any) => (
|
|---|
| 152 | <Grid item xs={12} sm={6} md={3} key={build.id}>
|
|---|
| 153 | <BuildCard
|
|---|
| 154 | build={build}
|
|---|
| 155 | onDelete={() => handleDelete(build.id)}
|
|---|
| 156 | isOwner={true}
|
|---|
| 157 | />
|
|---|
| 158 | </Grid>
|
|---|
| 159 | ))}
|
|---|
| 160 | </Grid>
|
|---|
| 161 | )}
|
|---|
| 162 | </Paper>
|
|---|
| 163 | </Grid>
|
|---|
| 164 |
|
|---|
| 165 | <Dialog open={openMyBuildsDialog} onClose={() => setOpenMyBuildsDialog(false)} maxWidth="md"
|
|---|
| 166 | fullWidth>
|
|---|
| 167 | <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>All
|
|---|
| 168 | My Builds
|
|---|
| 169 | <IconButton onClick={() => setOpenMyBuildsDialog(false)}>
|
|---|
| 170 | <CloseIcon/>
|
|---|
| 171 | </IconButton>
|
|---|
| 172 | </DialogTitle>
|
|---|
| 173 | <DialogContent dividers>
|
|---|
| 174 | <Grid container spacing={2} sx={{mt: 1}}>
|
|---|
| 175 | {data.userBuilds.map((build: any) => (
|
|---|
| 176 | <Grid item xs={12} sm={6} md={4} key={build.id}>
|
|---|
| 177 | <BuildCard
|
|---|
| 178 | build={build}
|
|---|
| 179 | onDelete={() => handleDelete(build.id)}
|
|---|
| 180 | isOwner={true}
|
|---|
| 181 | />
|
|---|
| 182 | </Grid>
|
|---|
| 183 | ))}
|
|---|
| 184 | </Grid>
|
|---|
| 185 | </DialogContent>
|
|---|
| 186 |
|
|---|
| 187 | <DialogActions>
|
|---|
| 188 | <Button onClick={() => setOpenMyBuildsDialog(false)}>Close</Button>
|
|---|
| 189 | </DialogActions>
|
|---|
| 190 | </Dialog>
|
|---|
| 191 |
|
|---|
| 192 | </Grid>
|
|---|
| 193 | </Grid>
|
|---|
| 194 | </Grid>
|
|---|
| 195 | </Container>
|
|---|
| 196 | );
|
|---|
| 197 | }
|
|---|
| 198 |
|
|---|
| 199 | function BuildCard({build, onDelete, isOwner}: { build: any, onDelete?: () => void, isOwner?: boolean }) {
|
|---|
| 200 | return (
|
|---|
| 201 | <Card sx={{height: '100%', display: 'flex', flexDirection: 'column'}}>
|
|---|
| 202 | <CardActionArea href={`/build/${build.id}`} sx={{flexGrow: 1}}>
|
|---|
| 203 | <CardContent>
|
|---|
| 204 | <Typography variant="subtitle1" fontWeight="bold" noWrap>
|
|---|
| 205 | {build.name || `Build #${build.id}`}
|
|---|
| 206 | </Typography>
|
|---|
| 207 | <Typography variant="body2" color="text.secondary">
|
|---|
| 208 | Created: {build.createdAt ? new Date(build.createdAt).toLocaleDateString() : 'Unknown'}
|
|---|
| 209 | </Typography>
|
|---|
| 210 | </CardContent>
|
|---|
| 211 | </CardActionArea>
|
|---|
| 212 |
|
|---|
| 213 | {isOwner && (
|
|---|
| 214 | <Box sx={{display: 'flex', justifyContent: 'flex-end', p: 1, borderTop: '1px solid #eee'}}>
|
|---|
| 215 | <IconButton size="small" href={`/build/edit/${build.id}`} color="primary">
|
|---|
| 216 | <EditIcon fontSize="small"/>
|
|---|
| 217 | </IconButton>
|
|---|
| 218 | <IconButton size="small" onClick={onDelete} color="error">
|
|---|
| 219 | <DeleteIcon fontSize="small"/>
|
|---|
| 220 | </IconButton>
|
|---|
| 221 | </Box>
|
|---|
| 222 | )}
|
|---|
| 223 | </Card>
|
|---|
| 224 | );
|
|---|
| 225 | } |
|---|