- Timestamp:
- 12/29/25 01:16:09 (6 months ago)
- Branches:
- main
- Children:
- 915ce0f
- Parents:
- 8a7f936
- Location:
- pages
- Files:
-
- 2 edited
-
dashboard/admin/+Page.tsx (modified) (13 diffs)
-
index/+Page.tsx (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
-
pages/dashboard/admin/+Page.tsx
r8a7f936 r5af32f0 1 import React, { useEffect, useState} from 'react';1 import React, {useEffect, useState} from 'react'; 2 2 import { 3 3 Container, Grid, Paper, Typography, Box, Avatar, Divider, Button, … … 25 25 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null); 26 26 27 const [suggestionDialog, setSuggestionDialog] = useState<{ open: boolean, id: number | null, action: 'approved' | 'rejected' }>({ open: false, id: null, action: 'approved' }); 27 // Build approval dialog state 28 const [buildApprovalDialog, setBuildApprovalDialog] = useState<{ 29 open: boolean, 30 buildId: number | null, 31 buildName: string 32 }>({open: false, buildId: null, buildName: ''}); 33 const [rejectReason, setRejectReason] = useState(''); 34 const [approvalLoading, setApprovalLoading] = useState(false); 35 36 const [suggestionDialog, setSuggestionDialog] = useState<{ 37 open: boolean, 38 id: number | null, 39 action: 'approved' | 'rejected' 40 }>({open: false, id: null, action: 'approved'}); 28 41 const [adminComment, setAdminComment] = useState(""); 29 42 30 43 const loadData = () => { 31 44 getAdminInfoAndData() 32 .then(res => { setData(res); setLoading(false); }) 45 .then(res => { 46 setData(res); 47 setLoading(false); 48 }) 33 49 .catch(err => { 34 50 console.error(err); … … 37 53 }; 38 54 39 useEffect(() => { loadData(); }, []); 40 41 const handleBuildReview = async (buildId: number, isApproved: boolean) => { 42 if (!confirm(`Are you sure you want to ${isApproved ? 'APPROVE' : 'REJECT'} this build?`)) return; 55 useEffect(() => { 56 loadData(); 57 }, []); 58 59 const openBuildApproval = (buildId: number, buildName: string) => { 60 setBuildApprovalDialog({open: true, buildId, buildName}); 61 setRejectReason(''); 62 }; 63 64 const handleApproveBuild = async () => { 65 if (!buildApprovalDialog.buildId) return; 66 setApprovalLoading(true); 43 67 try { 44 await onSetBuildApprovalStatus({ buildId, isApproved }); 68 await onSetBuildApprovalStatus({buildId: buildApprovalDialog.buildId, isApproved: true}); 69 setBuildApprovalDialog({open: false, buildId: null, buildName: ''}); 45 70 loadData(); 46 } catch (e) { alert("Action failed"); } 71 } catch (e) { 72 alert("Approve failed"); 73 } finally { 74 setApprovalLoading(false); 75 } 76 }; 77 78 const handleRejectBuild = async () => { 79 if (!buildApprovalDialog.buildId || !rejectReason.trim()) return; 80 setApprovalLoading(true); 81 try { 82 await onSetBuildApprovalStatus({buildId: buildApprovalDialog.buildId, isApproved: false}); 83 setBuildApprovalDialog({open: false, buildId: null, buildName: ''}); 84 loadData(); 85 } catch (e) { 86 alert("Reject failed"); 87 } finally { 88 setApprovalLoading(false); 89 } 47 90 }; 48 91 49 92 const openSuggestionReview = (id: number, action: 'approved' | 'rejected') => { 50 setSuggestionDialog({ open: true, id, action});93 setSuggestionDialog({open: true, id, action}); 51 94 setAdminComment(action === 'approved' ? "" : ""); 52 95 }; … … 60 103 adminComment: adminComment 61 104 }); 62 // console.error("Test za admincomment: ", adminComment); 63 setSuggestionDialog({ ...suggestionDialog, open: false }); 105 setSuggestionDialog({...suggestionDialog, open: false}); 64 106 loadData(); 65 } catch (e) { alert("Failed to update suggestion"); } 66 }; 67 68 if (loading) return <Box sx={{ p: 5, textAlign: 'center' }}><CircularProgress /></Box>; 69 if (!data) return <Box sx={{ p: 5, textAlign: 'center' }}>Access Denied</Box>; 107 } catch (e) { 108 alert("Failed to update suggestion"); 109 } 110 }; 111 112 if (loading) return <Box sx={{p: 5, textAlign: 'center'}}><CircularProgress/></Box>; 113 if (!data) return <Box sx={{p: 5, textAlign: 'center'}}>Access Denied</Box>; 70 114 71 115 return ( 72 <Container maxWidth="xl" sx={{ mt: 4, mb: 10}}>116 <Container maxWidth="xl" sx={{mt: 4, mb: 10}}> 73 117 <Grid container spacing={4}> 74 118 <Grid item xs={12} md={3}> 75 <Paper elevation={3} sx={{ p: 4, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%', bgcolor: '#1e1e1e', color: 'white' }}> 76 <Avatar sx={{ width: 120, height: 120, mb: 2, bgcolor: 'error.main' }}> 77 <AdminPanelSettingsIcon sx={{ fontSize: 70 }} /> 119 <Paper elevation={3} sx={{ 120 p: 4, 121 display: 'flex', 122 flexDirection: 'column', 123 alignItems: 'center', 124 height: '100%', 125 bgcolor: '#1e1e1e', 126 color: 'white' 127 }}> 128 <Avatar sx={{width: 120, height: 120, mb: 2, bgcolor: 'error.main'}}> 129 <AdminPanelSettingsIcon sx={{fontSize: 70}}/> 78 130 </Avatar> 79 131 <Typography variant="h5" fontWeight="bold">{data.admin.username}</Typography> 80 <Typography variant="body2" sx={{ opacity: 0.7, mb: 3}}>{data.admin.email}</Typography>81 82 <Chip label="ADMINISTRATOR" color="error" variant="outlined" sx={{ fontWeight: 'bold' }}/>83 <Divider sx={{ width: '100%', my: 3, bgcolor: 'rgba(255,255,255,0.1)' }}/>84 85 <Box sx={{ width: '100%', textAlign: 'center'}}>132 <Typography variant="body2" sx={{opacity: 0.7, mb: 3}}>{data.admin.email}</Typography> 133 134 <Chip label="ADMINISTRATOR" color="error" variant="outlined" sx={{fontWeight: 'bold'}}/> 135 <Divider sx={{width: '100%', my: 3, bgcolor: 'rgba(255,255,255,0.1)'}}/> 136 137 <Box sx={{width: '100%', textAlign: 'center'}}> 86 138 <Typography variant="h3" fontWeight="bold" color="primary.main"> 87 139 {data.pendingBuilds?.length || 0} … … 95 147 <Grid container spacing={4} direction="column"> 96 148 <Grid item xs={12}> 97 <Paper sx={{ p: 3, borderLeft: '6px solid #9c27b0'}}>98 <Box sx={{ display: 'flex', alignItems: 'center', mb: 2}}>99 <MemoryIcon color="secondary" sx={{ mr: 1 }}/>149 <Paper sx={{p: 3, borderLeft: '6px solid #9c27b0'}}> 150 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}> 151 <MemoryIcon color="secondary" sx={{mr: 1}}/> 100 152 <Typography variant="h6" fontWeight="bold"> 101 153 Suggested Components ({data.componentSuggestions?.length || 0}) … … 118 170 {data.componentSuggestions.map((sug: any) => ( 119 171 <TableRow key={sug.id}> 120 <TableCell sx={{ maxWidth: 300 }}> 121 <a href={sug.link} title={sug.link} style={{textDecoration: 'none', color: 'inherit'}}> 172 <TableCell sx={{maxWidth: 300}}> 173 <a href={sug.link} title={sug.link} 174 style={{textDecoration: 'none', color: 'inherit'}}> 122 175 {sug.description || sug.link} 123 176 </a> … … 126 179 <TableCell>{sug.userId}</TableCell> 127 180 <TableCell align="right"> 128 <IconButton size="small" color="success" onClick={() => openSuggestionReview(sug.id, 'approved')}> 129 <CheckCircleIcon /> 181 <IconButton size="small" color="success" 182 onClick={() => openSuggestionReview(sug.id, 'approved')}> 183 <CheckCircleIcon/> 130 184 </IconButton> 131 <IconButton size="small" color="error" onClick={() => openSuggestionReview(sug.id, 'rejected')}> 132 <CancelIcon /> 185 <IconButton size="small" color="error" 186 onClick={() => openSuggestionReview(sug.id, 'rejected')}> 187 <CancelIcon/> 133 188 </IconButton> 134 189 </TableCell> … … 142 197 143 198 <Grid item xs={12}> 144 <Paper sx={{ p: 3, borderLeft: '6px solid #ed6c02', bgcolor: '#121212'}}>145 <Box sx={{ display: 'flex', alignItems: 'center', mb: 2}}>146 <BuildIcon color="warning" sx={{ mr: 1 }}/>199 <Paper sx={{p: 3, borderLeft: '6px solid #ed6c02', bgcolor: '#121212'}}> 200 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}> 201 <BuildIcon color="warning" sx={{mr: 1}}/> 147 202 <Typography variant="h6" fontWeight="bold"> 148 203 Builds Waiting for Approval ({data.pendingBuilds?.length || 0}) … … 156 211 {data.pendingBuilds.map((build: any) => ( 157 212 <Grid item xs={12} md={4} key={build.id}> 158 <Box sx={{ position: 'relative'}}>213 <Box sx={{position: 'relative'}}> 159 214 <BuildCard 160 215 build={build} … … 162 217 /> 163 218 <Box sx={{ 164 mt: 1, display: 'flex', gap: 1, 165 justifyContent: 'center', bgcolor: '#121212', p: 1, borderRadius: 1 219 mt: 1, 220 display: 'flex', 221 gap: 1, 222 justifyContent: 'center', 223 bgcolor: '#292929', 224 p: 1, 225 borderRadius: 1 166 226 }}> 167 <Button variant="contained" color="success" size="small" onClick={() => handleBuildReview(build.id, true)}> 168 Approve 169 </Button> 170 <Button variant="contained" color="error" size="small" onClick={() => handleBuildReview(build.id, false)}> 171 Reject 227 <Button 228 variant="contained" 229 color="warning" 230 size="small" 231 startIcon={<BuildIcon/>} 232 onClick={() => openBuildApproval(build.id, build.name)} 233 > 234 Review 172 235 </Button> 173 236 </Box> … … 181 244 182 245 <Grid item xs={12}> 183 <Paper sx={{ p: 3, borderLeft: '6px solid #2e7d32'}}>246 <Paper sx={{p: 3, borderLeft: '6px solid #2e7d32'}}> 184 247 <Typography variant="h6" fontWeight="bold" gutterBottom> 185 248 My Builds / Sandbox … … 201 264 </Paper> 202 265 </Grid> 203 204 266 </Grid> 205 267 </Grid> 206 268 </Grid> 207 269 208 <Dialog open={suggestionDialog.open} onClose={() => setSuggestionDialog({...suggestionDialog, open: false})}> 270 <Dialog open={suggestionDialog.open} 271 onClose={() => setSuggestionDialog({...suggestionDialog, open: false})}> 209 272 <DialogTitle>Review Component Suggestion</DialogTitle> 210 273 <DialogContent> … … 221 284 </Dialog> 222 285 286 <Dialog open={buildApprovalDialog.open} 287 onClose={() => setBuildApprovalDialog({open: false, buildId: null, buildName: ''})}> 288 <DialogTitle>Review Build</DialogTitle> 289 <DialogContent> 290 <Typography variant="h6" gutterBottom>{buildApprovalDialog.buildName}</Typography> 291 <Typography color="text.secondary" sx={{mb: 2}}> 292 Build ID: {buildApprovalDialog.buildId} 293 </Typography> 294 <TextField 295 fullWidth 296 multiline 297 rows={2} 298 label="Reject reason (optional)" 299 value={rejectReason} 300 onChange={(e) => setRejectReason(e.target.value)} 301 placeholder="Why reject this build?" 302 sx={{mt: 1}} 303 /> 304 </DialogContent> 305 <DialogActions> 306 <Button 307 onClick={() => setBuildApprovalDialog({open: false, buildId: null, buildName: ''})} 308 disabled={approvalLoading} 309 > 310 Cancel 311 </Button> 312 <Button 313 onClick={handleRejectBuild} 314 variant="outlined" 315 color="error" 316 disabled={approvalLoading || !rejectReason.trim()} 317 > 318 {approvalLoading ? <CircularProgress size={20}/> : 'Reject'} 319 </Button> 320 <Button 321 onClick={handleApproveBuild} 322 variant="contained" 323 color="success" 324 disabled={approvalLoading} 325 > 326 {approvalLoading ? <CircularProgress size={20}/> : 'Approve'} 327 </Button> 328 </DialogActions> 329 </Dialog> 330 223 331 <BuildDetailsDialog 224 332 open={!!selectedBuildId} -
pages/index/+Page.tsx
r8a7f936 r5af32f0 1 1 import React, {useEffect, useState} from 'react'; 2 2 import { 3 Container, Box, Typography, Button, Grid,IconButton, Dialog, DialogTitle, DialogContent3 Container, Box, Typography, Button, IconButton, Dialog, DialogTitle, DialogContent 4 4 } from '@mui/material'; 5 5 import CloseIcon from '@mui/icons-material/Close'; … … 108 108 }, 109 109 gap: 3, 110 width: '90%', 110 width: '100%', 111 mb: 8 111 112 }} 112 113 > … … 124 125 }}> 125 126 <Typography variant="h4" fontWeight="bold" color="text.primary">Community Forge</Typography> 126 <Typography variant="subtitle1" color="text.secondary" sx={{mb: 2}}>Fresh builds from users around 127 the world.</Typography> 127 <Typography variant="subtitle1" color="text.secondary" sx={{mb: 2}}>Fresh builds from users around the world.</Typography> 128 128 </Box> 129 129 <Box … … 138 138 }, 139 139 gap: 3, 140 width: ' 90%',140 width: '100%', 141 141 }} 142 142 > … … 163 163 /> 164 164 165 <Dialog open={openRankedPopup} onClose={() => setOpenRankedPopup(false)} maxWidth="xl" fullWidth 166 scroll="paper"> 165 <Dialog open={openRankedPopup} onClose={() => setOpenRankedPopup(false)} maxWidth="xl" fullWidth scroll="paper"> 167 166 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> 168 167 Hall of Fame (Top Rated) 169 168 <IconButton onClick={() => setOpenRankedPopup(false)}><CloseIcon/></IconButton> 170 169 </DialogTitle> 171 <DialogContent dividers> 172 <Grid container spacing={3} sx={{p: 1}}> 170 <DialogContent dividers sx={{p: 3}}> 171 <Box 172 sx={{ 173 display: 'grid', 174 gridTemplateColumns: { 175 xs: '1fr', 176 sm: 'repeat(2, 1fr)', 177 md: 'repeat(3, 1fr)', 178 lg: 'repeat(4, 1fr)', 179 xl: 'repeat(5, 1fr)', 180 }, 181 gap: 3, 182 width: '100%' 183 }} 184 > 173 185 {allRanked.map((build: any, index: number) => ( 174 <Grid 175 item 176 xs={12} 177 sm={6} 178 md={3} 179 key={`ranked-${build.id}`} 180 sx={{display: 'flex'}} 181 > 182 <Box sx={{position: 'relative', width: '100%', display: 'flex'}}> 183 <Box sx={{ 184 position: 'absolute', top: -10, left: -10, zIndex: 1, 185 width: 35, height: 35, borderRadius: '50%', 186 bgcolor: index < 3 ? '#ff8201' : 'grey.800', color: 'white', 187 display: 'flex', alignItems: 'center', justifyContent: 'center', 188 fontWeight: 'bold', border: '2px solid white', boxShadow: 2 189 }}> 190 #{index + 1} 191 </Box> 192 <BuildCard 193 build={build} 194 onClick={() => { 195 setOpenRankedPopup(false); 196 setSelectedBuildId(build.id); 197 }} 198 /> 186 <Box key={`ranked-${build.id}`} sx={{position: 'relative', width: '100%'}}> 187 <Box sx={{ 188 position: 'absolute', 189 top: -10, 190 left: -10, 191 zIndex: 1, 192 width: 35, 193 height: 35, 194 borderRadius: '50%', 195 bgcolor: index < 3 ? '#ff8201' : 'grey.800', 196 color: 'white', 197 display: 'flex', 198 alignItems: 'center', 199 justifyContent: 'center', 200 fontWeight: 'bold', 201 border: '2px solid white', 202 boxShadow: 2, 203 }}> 204 #{index + 1} 199 205 </Box> 200 </Grid> 206 <BuildCard 207 build={build} 208 onClick={() => { 209 setOpenRankedPopup(false); 210 setSelectedBuildId(build.id); 211 }} 212 /> 213 </Box> 201 214 ))} 202 </ Grid>215 </Box> 203 216 </DialogContent> 204 217 </Dialog>
Note:
See TracChangeset
for help on using the changeset viewer.
