Changeset 92fafb3
- Timestamp:
- 02/19/26 20:33:50 (5 months ago)
- Branches:
- main
- Children:
- 2c7b9c8
- Parents:
- c586cbd (diff), 546a194 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)links above to see all the changes relative to each parent. - Files:
-
- 8 edited
-
components/BuildDetailsDialog.tsx (modified) (9 diffs)
-
components/ComponentDetailsDialog.tsx (modified) (9 diffs)
-
components/ComponentDialog.tsx (modified) (2 diffs)
-
components/Navbar.tsx (modified) (1 diff)
-
pages/completed-builds/+Page.tsx (modified) (4 diffs)
-
pages/dashboard/admin/+Page.tsx (modified) (4 diffs)
-
pages/dashboard/user/+Page.tsx (modified) (5 diffs)
-
pages/index/+Page.tsx (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
components/BuildDetailsDialog.tsx
rc586cbd r92fafb3 1 1 import React, {useEffect, useState} from 'react'; 2 2 import { 3 Dialog, DialogTitle, DialogContent, DialogActions, Button, Grid,Box, Typography,3 Dialog, DialogTitle, DialogContent, DialogActions, Button, Box, Typography, 4 4 IconButton, Tab, Tabs, Table, TableBody, TableCell, TableRow, Rating, TextField, Avatar, Chip, Alert 5 5 } from '@mui/material'; … … 34 34 const [ratingVal, setRatingVal] = useState(5); 35 35 36 // Main details fetch37 36 useEffect(() => { 38 if (open && buildId !== null && typeof buildId === 'number') {37 if (open && buildId !== null) { 39 38 setLoading(true); 40 39 setReviewText(""); … … 52 51 53 52 useEffect(() => { 54 if (open && buildId !== null && typeof buildId === 'number') {53 if (open && buildId !== null) { 55 54 onGetBuildState({buildId}) 56 55 .then(state => { … … 63 62 }, [open, buildId]); 64 63 64 useEffect(() => { 65 if (open) { 66 setTabIndex(0); 67 } 68 }, [open, buildId]); 69 70 65 71 const handleFavorite = async () => { 66 72 if (!currentUser || buildId === null) return alert("Please login to favorite builds."); … … 103 109 return ( 104 110 <> 105 <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth scroll=" paper">111 <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth scroll="body"> 106 112 {loading || !details ? ( 107 113 <Box sx={{p: 5, textAlign: 'center'}}>Loading Forge Schematics...</Box> … … 146 152 <Box sx={{p: 3}}> 147 153 {tabIndex === 0 && ( 148 <Grid container spacing={2}> 149 <Grid item xs={12} md={8}> 154 <Box 155 sx={{ 156 display: 'grid', 157 gridTemplateColumns: { 158 xs: '1fr', 159 md: '2fr 1fr' 160 }, 161 gap: 2, 162 width: '100%' 163 }} 164 > 165 <Box> 150 166 <Table size="small"> 151 167 <TableBody> … … 193 209 </TableBody> 194 210 </Table> 195 </ Grid>196 197 < Grid item xs={12} md={4}>211 </Box> 212 213 <Box> 198 214 <Box sx={{bgcolor: '#424343', p: 2, borderRadius: 2, mb: 2}}> 199 <Typography color="primary.main" gutterBottom fontWeight="bold">Builder's 200 Notes</Typography> 215 <Typography color="primary.main" gutterBottom fontWeight="bold"> 216 Builder's Notes 217 </Typography> 201 218 <Typography color="primary.main" variant="body2" 202 219 sx={{fontStyle: 'italic'}}> … … 243 260 </Button> 244 261 </Box> 245 </ Grid>246 </ Grid>262 </Box> 263 </Box> 247 264 )} 248 265 … … 313 330 ))} 314 331 {details.reviews.length === 0 && ( 315 <Typography color="text.secondary" align="center">No reviews yet. Be the 316 first!</Typography> 332 <Typography color="text.secondary" align="center"> 333 No reviews yet. Be the first! 334 </Typography> 317 335 )} 318 336 </Box> -
components/ComponentDetailsDialog.tsx
rc586cbd r92fafb3 2 2 import { 3 3 Dialog, DialogTitle, DialogContent, DialogActions, Button, 4 Typography, Box, Grid,Chip, CircularProgress, IconButton,4 Typography, Box, Chip, CircularProgress, IconButton, 5 5 Table, TableBody, TableCell, TableContainer, TableRow, Paper 6 6 } from '@mui/material'; … … 92 92 open={open} 93 93 onClose={onClose} 94 maxWidth="lg" 95 // fullWidth 94 maxWidth="md" 95 fullWidth 96 scroll="body" 96 97 sx={{zIndex: 1400}} 97 98 > … … 116 117 </DialogTitle> 117 118 118 <DialogContent dividers> 119 <DialogContent 120 sx={{ 121 p: 1, 122 overflow: 'visible' 123 }} 124 > 119 125 {loading ? ( 120 126 <Box sx={{display: 'flex', justifyContent: 'center', p: 5}}> … … 122 128 </Box> 123 129 ) : ( 124 <Grid container spacing={4}> 125 <Grid item xs={12} md={4} sx={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}> 130 <Box 131 sx={{ 132 display: 'grid', 133 gridTemplateColumns: { 134 xs: '1fr', 135 md: '1fr 1fr' 136 }, 137 gap: 4, 138 width: '100%' 139 }} 140 > 141 <Box sx={{display: 'flex', flexDirection: 'column', alignItems: 'center'}}> 126 142 <Box 127 143 component="img" 128 // src={`https://placehold.co/400x400?text=${encodeURIComponent(displayData.name)}`}129 144 src={displayData.imgUrl} 130 145 alt={displayData.name} … … 133 148 maxHeight: 300, 134 149 objectFit: 'contain', 135 mb: 2, 136 // border: '1px solid #eee', 137 // borderRadius: 2, 138 p: 2 150 mb: 1, 151 mt: 7, 152 p: 1 139 153 }} 140 154 /> … … 144 158 sx={{fontWeight: 'bold'}} 145 159 /> 146 </Grid> 147 148 <Grid item xs={12} md={8}> 149 <Typography variant="subtitle1" fontWeight="bold" gutterBottom 150 sx={{borderBottom: '2px solid #ff8201', display: 'inline-block', mb: 0.5}}> 160 </Box> 161 162 <Box sx={{ 163 display: 'flex', 164 flexDirection: 'column', 165 alignItems: 'center', 166 width: '70%' 167 }}> 168 <Typography 169 variant="subtitle1" 170 fontWeight="bold" 171 gutterBottom 172 sx={{ 173 borderBottom: '2px solid #ff8201', 174 mb: 1, 175 mt: 1, 176 textAlign: 'center', 177 width: '100%' 178 }} 179 > 151 180 Technical Specifications 152 181 </Typography> 153 182 154 <TableContainer component={Paper} variant="outlined"> 155 <Table size="small"> 183 <TableContainer 184 component={Paper} 185 variant="outlined" 186 sx={{ 187 maxWidth: '100%', 188 width: '100%' 189 }} 190 > 191 <Table size="medium"> 156 192 <TableBody> 157 193 <TableRow> 158 <TableCell component="th" scope="row" 159 sx={{fontWeight: 'bold', width: '30%', bgcolor: '#1e1e1e'}}> 194 <TableCell 195 component="th" 196 scope="row" 197 sx={{ 198 fontWeight: 'bold', 199 width: '50%', 200 bgcolor: '#1e1e1e', 201 textAlign: 'center', 202 px: 2 203 }} 204 > 160 205 Brand 161 206 </TableCell> 162 <TableCell>{displayData.brand}</TableCell> 207 <TableCell sx={{ 208 textAlign: 'center', 209 px: 2, 210 width: '50%' 211 }}> 212 {displayData.brand} 213 </TableCell> 163 214 </TableRow> 164 215 … … 168 219 return ( 169 220 <TableRow key={key}> 170 <TableCell component="th" scope="row" sx={{ 171 fontWeight: 'bold', 172 width: '30%', 173 bgcolor: '#1e1e1e' 174 }}> 221 <TableCell 222 component="th" 223 scope="row" 224 sx={{ 225 fontWeight: 'bold', 226 width: '50%', 227 bgcolor: '#1e1e1e', 228 textAlign: 'center', 229 px: 2 230 }} 231 > 175 232 {formatKey(key)} 176 233 </TableCell> 177 <TableCell>{renderValue(key, val)}</TableCell> 234 <TableCell sx={{ 235 textAlign: 'center', 236 px: 2, 237 width: '50%' 238 }}> 239 {renderValue(key, val)} 240 </TableCell> 178 241 </TableRow> 179 242 ); … … 182 245 </Table> 183 246 </TableContainer> 184 <Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 0.5}}> 247 248 <Box sx={{ 249 display: 'flex', 250 justifyContent: 'center', 251 alignItems: 'center', 252 mt: 1, 253 width: '100%' 254 }}> 185 255 <Typography variant="h4" color="primary.main" fontWeight="bold"> 186 256 {formatMoney(displayData.price)} 187 257 </Typography> 188 258 </Box> 189 </ Grid>190 </ Grid>259 </Box> 260 </Box> 191 261 )} 192 262 </DialogContent> … … 198 268 color: 'white', 199 269 borderColor: '#ff8201', 200 onHover: {backgroundColor: '#ba5d02', borderColor: '#ba5d02'}270 '&:hover': {backgroundColor: '#ba5d02', borderColor: '#ba5d02'} 201 271 }} 202 272 >Close</Button> -
components/ComponentDialog.tsx
rc586cbd r92fafb3 57 57 return () => clearTimeout(timeoutId); 58 58 }, [tempSearchQuery]); 59 60 useEffect(() => { 61 if (open && category) { 62 setSelectedBrands([]); 63 setSortOrder('price_desc'); 64 setTempSearchQuery(''); 65 setSearchQuery(''); 66 setPriceRange([0, 2000]); 67 } 68 }, [open, category]); 69 59 70 60 71 useEffect(() => { … … 297 308 Browsing: 298 309 </Box> 299 <b> {category === 'gpu' ? 'G raphics Cards' : category?.toUpperCase()}</b>310 <b> {category === 'gpu' ? 'GRAPHICS CARDS' : category === 'memory_card' ? 'STORAGE EXPANSION CARDS' : category?.toUpperCase()}</b> 300 311 {mode === 'forge' && currentBuildId && ( 301 312 <Typography -
components/Navbar.tsx
rc586cbd r92fafb3 64 64 { id: 'sound_card', label: 'Sound Cards', icon: <SpeakerIcon fontSize="small" /> }, 65 65 { id: 'optical_drive', label: 'Optical Drives', icon: <AlbumIcon fontSize="small" /> }, 66 { id: 'memory_card', label: ' MemoryCards', icon: <SdCardIcon fontSize="small" /> },66 { id: 'memory_card', label: 'Storage Cards', icon: <SdCardIcon fontSize="small" /> }, 67 67 { id: 'cables', label: 'Cables', icon: <CableIcon fontSize="small" /> }, 68 68 ]; -
pages/completed-builds/+Page.tsx
rc586cbd r92fafb3 9 9 import BuildCard from '../../components/BuildCard'; 10 10 import BuildDetailsDialog from '../../components/BuildDetailsDialog'; 11 import { onGetApprovedBuilds, on CloneBuild, onGetAuthState } from '../+Layout.telefunc';11 import { onGetApprovedBuilds, onGetAuthState } from '../+Layout.telefunc'; 12 12 13 13 export default function CompletedBuildsPage() { … … 65 65 66 66 useEffect(() => { 67 loadBuilds();67 void loadBuilds(); 68 68 }, [sortBy, searchQuery]); 69 70 const handleClone = async (buildId: number) => {71 if (!userId) return alert("Please login to clone builds!");72 if (confirm(`Clone this build?`)) {73 await onCloneBuild({buildId});74 alert("Build cloned!");75 setSelectedBuildId(null);76 }77 };78 69 79 70 return ( … … 102 93 value={searchQuery} 103 94 onChange={(e) => setSearchQuery(e.target.value)} 104 InputProps={{ 105 startAdornment: <InputAdornment position="start"><SearchIcon /></InputAdornment>, 95 slotProps={{ 96 input: { 97 startAdornment: <InputAdornment position="start"><SearchIcon /></InputAdornment>, 98 } 106 99 }} 107 100 sx={{ mb: 3 }} … … 193 186 currentUser={userId} 194 187 onClose={() => setSelectedBuildId(null)} 195 onClone={handleClone}196 188 /> 197 189 </Container> -
pages/dashboard/admin/+Page.tsx
rc586cbd r92fafb3 1 1 import React, {useEffect, useState} from 'react'; 2 2 import { 3 Container, Grid,Paper, Typography, Box, Avatar, Divider, Button,3 Container, Paper, Typography, Box, Avatar, Divider, Button, 4 4 CircularProgress, Table, TableBody, TableCell, TableHead, TableRow, 5 5 Chip, IconButton, TextField, Dialog, DialogTitle, DialogContent, DialogActions … … 173 173 return ( 174 174 <Container maxWidth="xl" sx={{mt: 4, mb: 10}}> 175 <Grid container spacing={4}> 176 <Grid item xs={12} md={3}> 175 <Box sx={{ 176 display: 'grid', 177 gridTemplateColumns: { 178 xs: '1fr', 179 md: '300px 1fr' 180 }, 181 gap: 4 182 }}> 183 <Box> 177 184 <Paper elevation={3} sx={{ 178 185 p: 4, … … 216 223 </Box> 217 224 </Paper> 218 </Grid> 219 220 <Grid item xs={12} md={9}> 221 <Grid container spacing={4} direction="column"> 222 <Grid item xs={12}> 223 <Paper sx={{p: 3, borderLeft: '6px solid #9c27b0'}}> 224 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}> 225 <MemoryIcon color="secondary" sx={{mr: 1}}/> 226 <Typography variant="h6" fontWeight="bold"> 227 Component Suggestions ({data.componentSuggestions?.length || 0}) 228 </Typography> 229 </Box> 230 231 {data.componentSuggestions?.length === 0 ? ( 232 <Typography color="text.secondary">No suggestions.</Typography> 233 ) : ( 234 <Table size="small"> 235 <TableHead> 236 <TableRow> 237 <TableCell>Link/Description</TableCell> 238 <TableCell>Type</TableCell> 239 <TableCell>User</TableCell> 240 <TableCell>Status</TableCell> 241 <TableCell align="right">Actions</TableCell> 242 </TableRow> 243 </TableHead> 244 <TableBody> 245 {data.componentSuggestions?.map((sug: any) => ( 246 <TableRow key={sug.id}> 247 <TableCell sx={{maxWidth: 300}}> 248 <a 249 href={sug.link} 250 target="_blank" 251 rel="noopener noreferrer" 252 title={sug.link} 253 style={{textDecoration: 'none', color: 'inherit'}} 254 > 255 {sug.description || sug.link} 256 </a> 257 </TableCell> 258 <TableCell>{sug.componentType?.toUpperCase()}</TableCell> 259 <TableCell>{sug.userId}</TableCell> 260 <TableCell> 261 <Chip 262 label={sug.status || 'pending'} 225 </Box> 226 227 <Box sx={{ 228 display: 'flex', 229 flexDirection: 'column', 230 gap: 4 231 }}> 232 <Paper sx={{p: 3, borderLeft: '6px solid #9c27b0'}}> 233 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}> 234 <MemoryIcon color="secondary" sx={{mr: 1}}/> 235 <Typography variant="h6" fontWeight="bold"> 236 Component Suggestions ({data.componentSuggestions?.length || 0}) 237 </Typography> 238 </Box> 239 240 {data.componentSuggestions?.length === 0 ? ( 241 <Typography color="text.secondary">No suggestions.</Typography> 242 ) : ( 243 <Box sx={{width: '100%', overflowX: 'auto'}}> 244 <Table size="small"> 245 <TableHead> 246 <TableRow> 247 <TableCell>Link/Description</TableCell> 248 <TableCell>Type</TableCell> 249 <TableCell>User</TableCell> 250 <TableCell>Status</TableCell> 251 <TableCell align="right">Actions</TableCell> 252 </TableRow> 253 </TableHead> 254 <TableBody> 255 {data.componentSuggestions?.map((sug: any) => ( 256 <TableRow key={sug.id}> 257 <TableCell sx={{minWidth: 200, maxWidth: 300}}> 258 <a 259 href={sug.link} 260 target="_blank" 261 rel="noopener noreferrer" 262 title={sug.link} 263 style={{textDecoration: 'none', color: 'inherit'}} 264 > 265 {sug.description || sug.link} 266 </a> 267 </TableCell> 268 <TableCell sx={{minWidth: 80}}> 269 {sug.componentType?.toUpperCase()} 270 </TableCell> 271 <TableCell sx={{minWidth: 100}}> 272 {sug.user?.username || `${sug.userId}`} 273 </TableCell> 274 <TableCell sx={{minWidth: 100}}> 275 <Chip 276 label={sug.status || 'pending'} 277 size="small" 278 color={ 279 sug.status === 'approved' ? 'success' : 280 sug.status === 'rejected' ? 'error' : 281 'default' 282 } 283 /> 284 </TableCell> 285 <TableCell align="right" sx={{minWidth: 150}}> 286 {sug.status === 'pending' ? ( 287 <> 288 <IconButton 289 size="small" 290 color="success" 291 onClick={() => openSuggestionReview(sug.id, 'approved')} 292 > 293 <CheckCircleIcon/> 294 </IconButton> 295 <IconButton 296 size="small" 297 color="error" 298 onClick={() => openSuggestionReview(sug.id, 'rejected')} 299 > 300 <CancelIcon/> 301 </IconButton> 302 </> 303 ) : sug.status === 'approved' ? ( 304 <Button 263 305 size="small" 264 color={265 sug.status === 'approved' ? 'success' :266 sug.status === 'rejected' ? 'error' :267 'default'268 }269 />270 </TableCell>271 <TableCell align="right">272 {sug.status === 'pending' ? (273 <>274 <IconButton275 size="small"276 color="success"277 onClick={() => openSuggestionReview(sug.id, 'approved')}278 >279 <CheckCircleIcon/>280 </IconButton>281 <IconButton282 size="small"283 color="error"284 onClick={() => openSuggestionReview(sug.id, 'rejected')}285 >286 <CancelIcon/>287 </IconButton>288 </>289 ) : sug.status === 'approved' ? (290 <Button291 size="small"292 variant="contained"293 color="warning"294 startIcon={<AddIcon/>}295 onClick={() => setCreateComponentDialog({296 open: true,297 suggestion: sug298 })}299 >300 Create301 </Button>302 ) : (303 <Typography variant="caption" color="text.secondary">304 {sug.adminComment || 'Rejected'}305 </Typography>306 )}307 </TableCell>308 </TableRow>309 ))}310 </TableBody>311 </Table>312 )}313 </Paper>314 </Grid>315 316 <Grid item xs={12}>317 <Paper sx={{p: 3, borderLeft: '6px solid #ed6c02', bgcolor: '#121212'}}>318 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}>319 <BuildIcon color="warning" sx={{mr: 1}}/>320 <Typography variant="h6" fontWeight="bold">321 Builds Waiting for Approval ({data.pendingBuilds?.length || 0})322 </Typography>323 </Box>324 325 {data.pendingBuilds?.length === 0 ? (326 <Typography color="text.secondary">No pending builds.</Typography>327 ) : (328 <Grid container spacing={2}>329 {data.pendingBuilds.map((build: any) => (330 <Grid item xs={12} sm={6} md={4} lg={3} key={build.id}>331 <Box sx={{position: 'relative'}}>332 <BuildCard333 build={build}334 onClick={() => setSelectedBuildId(build.id)}335 />336 <Box sx={{337 mt: 1,338 display: 'flex',339 gap: 1,340 justifyContent: 'center'341 }}>342 <Button343 306 variant="contained" 344 307 color="warning" 345 size="small" 346 startIcon={<BuildIcon/>} 347 onClick={(e) => { 348 e.stopPropagation(); 349 openBuildApproval(build.id, build.name); 350 }} 308 startIcon={<AddIcon/>} 309 onClick={() => setCreateComponentDialog({ 310 open: true, 311 suggestion: sug 312 })} 351 313 > 352 Review314 Create 353 315 </Button> 354 </Box> 355 </Box> 356 </Grid> 316 ) : ( 317 <Typography variant="caption" color="text.secondary"> 318 {sug.adminComment || 'Rejected'} 319 </Typography> 320 )} 321 </TableCell> 322 </TableRow> 357 323 ))} 358 </Grid> 359 )} 360 </Paper> 361 </Grid> 362 363 <Grid item xs={12}> 364 <Paper sx={{p: 3, borderLeft: '6px solid #2e7d32'}}> 365 <Typography variant="h6" fontWeight="bold" gutterBottom> 366 My Builds / Sandbox 367 </Typography> 368 {data.userBuilds?.length === 0 ? ( 369 <Typography color="text.secondary">No builds created by admin.</Typography> 370 ) : ( 371 <Grid container spacing={2}> 372 {data.userBuilds.slice(0, 4).map((build: any) => ( 373 <Grid item xs={12} sm={6} md={4} lg={3} key={build.id}> 374 <Box sx={{position: 'relative'}}> 375 <BuildCard 376 build={build} 377 onClick={() => setSelectedBuildId(build.id)} 378 /> 379 <Box sx={{mt: 1, display: 'flex', justifyContent: 'center'}}> 380 <Button 381 variant="outlined" 382 color="error" 383 size="small" 384 startIcon={<DeleteIcon/>} 385 onClick={(e) => { 386 e.stopPropagation(); 387 openDeleteDialog(build.id, build.name); 388 }} 389 fullWidth 390 > 391 Delete 392 </Button> 393 </Box> 394 </Box> 395 </Grid> 396 ))} 397 </Grid> 398 )} 399 </Paper> 400 </Grid> 401 </Grid> 402 </Grid> 403 </Grid> 324 </TableBody> 325 </Table> 326 </Box> 327 )} 328 </Paper> 329 330 <Paper sx={{p: 3, borderLeft: '6px solid #ed6c02', bgcolor: '#121212'}}> 331 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}> 332 <BuildIcon color="warning" sx={{mr: 1}}/> 333 <Typography variant="h6" fontWeight="bold"> 334 Builds Waiting for Approval ({data.pendingBuilds?.length || 0}) 335 </Typography> 336 </Box> 337 338 {data.pendingBuilds?.length === 0 ? ( 339 <Typography color="text.secondary">No pending builds.</Typography> 340 ) : ( 341 <Box sx={{ 342 display: 'grid', 343 gridTemplateColumns: { 344 xs: '1fr', 345 sm: 'repeat(2, 1fr)', 346 lg: 'repeat(3, 1fr)', 347 xl: 'repeat(4, 1fr)' 348 }, 349 gap: 2 350 }}> 351 {data.pendingBuilds.map((build: any) => ( 352 <Box key={build.id}> 353 <BuildCard 354 build={build} 355 onClick={() => setSelectedBuildId(build.id)} 356 /> 357 <Box sx={{mt: 1, display: 'flex', gap: 1, justifyContent: 'center'}}> 358 <Button 359 variant="contained" 360 color="warning" 361 size="small" 362 startIcon={<BuildIcon/>} 363 onClick={(e) => { 364 e.stopPropagation(); 365 openBuildApproval(build.id, build.name); 366 }} 367 > 368 Review 369 </Button> 370 </Box> 371 </Box> 372 ))} 373 </Box> 374 )} 375 </Paper> 376 377 <Paper sx={{p: 3, borderLeft: '6px solid #2e7d32'}}> 378 <Typography variant="h6" fontWeight="bold" gutterBottom> 379 My Builds / Sandbox 380 </Typography> 381 {data.userBuilds?.length === 0 ? ( 382 <Typography color="text.secondary">No builds created by admin.</Typography> 383 ) : ( 384 <Box sx={{ 385 display: 'grid', 386 gridTemplateColumns: { 387 xs: '1fr', 388 sm: 'repeat(2, 1fr)', 389 lg: 'repeat(3, 1fr)', 390 xl: 'repeat(4, 1fr)' 391 }, 392 gap: 2 393 }}> 394 {data.userBuilds.slice(0, 4).map((build: any) => ( 395 <Box key={build.id}> 396 <BuildCard 397 build={build} 398 onClick={() => setSelectedBuildId(build.id)} 399 /> 400 <Box sx={{mt: 1, display: 'flex', justifyContent: 'center'}}> 401 <Button 402 variant="outlined" 403 color="error" 404 size="small" 405 startIcon={<DeleteIcon/>} 406 onClick={(e) => { 407 e.stopPropagation(); 408 openDeleteDialog(build.id, build.name); 409 }} 410 fullWidth 411 > 412 Delete 413 </Button> 414 </Box> 415 </Box> 416 ))} 417 </Box> 418 )} 419 </Paper> 420 </Box> 421 </Box> 404 422 405 423 <Dialog open={suggestionDialog.open} … … 520 538 <AddComponentDialog 521 539 open={createComponentDialog.open} 522 onClose={() => setCreateComponentDialog({ open: false, suggestion: null})}540 onClose={() => setCreateComponentDialog({open: false, suggestion: null})} 523 541 onSuccess={() => { 524 542 loadData(); 525 setCreateComponentDialog({ open: false, suggestion: null});543 setCreateComponentDialog({open: false, suggestion: null}); 526 544 }} 527 545 prefillData={createComponentDialog.suggestion ? { -
pages/dashboard/user/+Page.tsx
rc586cbd r92fafb3 10 10 CircularProgress, 11 11 IconButton, 12 Grid,13 12 Dialog, 14 13 DialogTitle, … … 24 23 25 24 import {getUserInfoAndData, onDeleteBuild} from "./userDashboard.telefunc"; 26 import {onCloneBuild} from "../../+Layout.telefunc";27 25 28 26 import BuildCard from "../../../components/BuildCard"; … … 72 70 await onDeleteBuild({buildId: deleteDialog.buildId}); 73 71 setDeleteDialog({ open: false, buildId: null, buildName: '' }); 74 loadData(); // refresh na user i favorite builds72 loadData(); 75 73 setSelectedBuildId(null); 76 74 } catch (e) { … … 81 79 }; 82 80 83 const handleCloneWrapper = async (buildId: number) => {84 if (!data?.user) return alert("Please login to clone builds!");85 if (confirm(`Clone this build to your dashboard?`)) {86 await onCloneBuild({buildId});87 alert("Build cloned! Check your dashboard.");88 setSelectedBuildId(null);89 loadData();90 }91 };92 93 81 if (loading) return <Box sx={{display: 'flex', justifyContent: 'center', mt: 10}}><CircularProgress/></Box>; 94 if (error || !data) return <Container sx={{mt: 5, textAlign: 'center'}}><Typography color="error" 95 variant="h6">{error}</Typography><Button 96 href="/auth/login" variant="contained">Login</Button></Container>; 82 if (error || !data) return <Container sx={{mt: 5, textAlign: 'center'}}><Typography color="error" variant="h6">{error}</Typography><Button href="/auth/login" variant="contained">Login</Button></Container>; 97 83 98 84 return ( 99 <Container maxWidth="xl" sx={{mt: 4, mb: 4, color: 'white'}}> 100 <Grid container spacing={3}> 101 <Grid item xs={12} md={3}> 102 <Paper elevation={3} 103 sx={{p: 3, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%'}}> 104 <Avatar sx={{width: 100, height: 100, mb: 2, bgcolor: 'primary.main'}}><PersonIcon 105 sx={{fontSize: 60}}/></Avatar> 85 <Container maxWidth="xl" sx={{mb: 4, color: 'white'}}> 86 <Box sx={{ 87 display: 'grid', 88 gridTemplateColumns: { 89 xs: '1fr', 90 md: '280px 1fr' 91 }, 92 gap: 2 93 }}> 94 <Box> 95 <Paper elevation={3} sx={{p: 3, display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100%'}}> 96 <Avatar sx={{width: 100, height: 100, mb: 2, bgcolor: 'primary.main'}}> 97 <PersonIcon sx={{fontSize: 60}}/> 98 </Avatar> 106 99 <Typography variant="h5" fontWeight="bold" gutterBottom>{data.user.username}</Typography> 107 100 <Typography variant="body2" color="text.secondary" gutterBottom>{data.user.email}</Typography> 108 101 <Divider sx={{width: '100%', my: 2}}/> 109 102 </Paper> 110 </Grid> 111 112 <Grid item xs={12} md={9}> 113 <Grid container spacing={3} direction="column"> 114 115 <Grid item xs={12}> 116 <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}> 117 <Box sx={{ 118 display: 'flex', 119 alignItems: 'center', 120 justifyContent: 'space-between', 121 mb: 2 122 }}> 123 <Box sx={{display: 'flex', alignItems: 'center'}}> 124 <FavoriteIcon color="error" sx={{mr: 1}}/> 125 <Typography variant="h6" fontWeight="bold">Favorited Builds</Typography> 126 </Box> 127 128 {data.favoriteBuilds.length > 4 && ( 103 </Box> 104 105 <Box sx={{ 106 display: 'flex', 107 flexDirection: 'column', 108 gap: 3 109 }}> 110 <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}> 111 <Box sx={{ 112 display: 'flex', 113 alignItems: 'center', 114 justifyContent: 'space-between', 115 mb: 2 116 }}> 117 <Box sx={{display: 'flex', alignItems: 'center'}}> 118 <FavoriteIcon color="error" sx={{mr: 1}}/> 119 <Typography variant="h6" fontWeight="bold">Favorited Builds</Typography> 120 </Box> 121 122 {data.favoriteBuilds.length > 6 && ( 123 <Button 124 variant="outlined" 125 size="small" 126 onClick={() => setOpenFavoritesDialog(true)} 127 > 128 See All ({data.favoriteBuilds.length}) 129 </Button> 130 )} 131 </Box> 132 <Divider sx={{mb: 2}}/> 133 134 {data.favoriteBuilds.length === 0 ? ( 135 <Typography color="text.secondary" align="center" sx={{mt: 4}}> 136 You haven't favorited any builds yet. 137 </Typography> 138 ) : ( 139 <Box sx={{ 140 display: 'grid', 141 gridTemplateColumns: { 142 xs: '1fr', 143 sm: 'repeat(2, 1fr)', 144 md: 'repeat(3, 1fr)', 145 lg: 'repeat(4, 1fr)', 146 xl: 'repeat(6, 1fr)' 147 }, 148 gap: 2 149 }}> 150 {data.favoriteBuilds.slice(0, 6).map((build: any) => ( 151 <BuildCard 152 key={build.id} 153 build={build} 154 onClick={() => setSelectedBuildId(build.id)} 155 /> 156 ))} 157 </Box> 158 )} 159 </Paper> 160 161 <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}> 162 <Box sx={{ 163 display: 'flex', 164 alignItems: 'center', 165 justifyContent: 'space-between', 166 mb: 2 167 }}> 168 <Box sx={{display: 'flex', alignItems: 'center'}}> 169 <ComputerIcon color="primary" sx={{mr: 1}}/> 170 <Typography variant="h6" fontWeight="bold">My Builds</Typography> 171 </Box> 172 173 {data.userBuilds.length > 6 && ( 174 <Button 175 variant="outlined" 176 size="small" 177 onClick={() => setOpenMyBuildsDialog(true)} 178 > 179 See All ({data.userBuilds.length}) 180 </Button> 181 )} 182 </Box> 183 <Divider sx={{mb: 2}}/> 184 185 {data.userBuilds.length === 0 ? ( 186 <Box sx={{textAlign: 'center', mt: 4}}> 187 <Typography color="text.secondary" gutterBottom> 188 You haven't created any builds yet. 189 </Typography> 190 <Button variant="contained" href="/forge" sx={{color: 'white', fontWeight: 'bolder'}}> 191 Start Forging 192 </Button> 193 </Box> 194 ) : ( 195 <Box sx={{ 196 display: 'grid', 197 gridTemplateColumns: { 198 xs: '1fr', 199 sm: 'repeat(2, 1fr)', 200 md: 'repeat(3, 1fr)', 201 lg: 'repeat(4, 1fr)', 202 xl: 'repeat(6, 1fr)' 203 }, 204 gap: 2 205 }}> 206 {data.userBuilds.slice(0, 6).map((build: any) => ( 207 <Box key={build.id} sx={{ display: 'flex', flexDirection: 'column' }}> 208 <Box sx={{ flexGrow: 1 }}> 209 <BuildCard 210 build={build} 211 onClick={() => setSelectedBuildId(build.id)} 212 /> 213 </Box> 129 214 <Button 130 215 variant="outlined" 216 color="error" 131 217 size="small" 132 onClick={() => setOpenFavoritesDialog(true)} 218 startIcon={<DeleteIcon/>} 219 onClick={(e) => { 220 e.stopPropagation(); 221 openDeleteDialog(build.id, build.name); 222 }} 223 sx={{ mt: 1, width: '100%' }} 133 224 > 134 See All ({data.favoriteBuilds.length})225 Delete 135 226 </Button> 136 )} 227 </Box> 228 ))} 229 </Box> 230 )} 231 </Paper> 232 </Box> 233 </Box> 234 235 <Dialog open={openMyBuildsDialog} onClose={() => setOpenMyBuildsDialog(false)} maxWidth="xl" fullWidth> 236 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> 237 All My Builds 238 <IconButton onClick={() => setOpenMyBuildsDialog(false)}> 239 <CloseIcon/> 240 </IconButton> 241 </DialogTitle> 242 <DialogContent dividers> 243 <Box sx={{ 244 display: 'grid', 245 gridTemplateColumns: { 246 xs: '1fr', 247 sm: 'repeat(2, 1fr)', 248 md: 'repeat(3, 1fr)', 249 lg: 'repeat(4, 1fr)', 250 xl: 'repeat(5, 1fr)' 251 }, 252 gap: 2, 253 mt: 1 254 }}> 255 {data.userBuilds.map((build: any) => ( 256 <Box key={build.id}> 257 <BuildCard 258 build={build} 259 onClick={() => { 260 setOpenMyBuildsDialog(false); 261 setSelectedBuildId(build.id); 262 }} 263 /> 264 <Box sx={{mt: 1, display: 'flex', justifyContent: 'center'}}> 265 <Button 266 variant="outlined" 267 color="error" 268 size="small" 269 startIcon={<DeleteIcon/>} 270 onClick={(e) => { 271 e.stopPropagation(); 272 openDeleteDialog(build.id, build.name); 273 }} 274 sx={{width: '100%'}} 275 > 276 Delete 277 </Button> 137 278 </Box> 138 <Divider sx={{mb: 2}}/> 139 140 {data.favoriteBuilds.length === 0 ? ( 141 <Typography color="text.secondary" align="center" sx={{mt: 4}}>You haven't favorited 142 any builds yet.</Typography> 143 ) : ( 144 <Grid container spacing={2}> 145 {data.favoriteBuilds.slice(0, 4).map((build: any) => ( 146 <Grid item xs={12} sm={6} md={4} key={build.id}> 147 <BuildCard 148 build={build} 149 onClick={() => setSelectedBuildId(build.id)} 150 /> 151 </Grid> 152 ))} 153 </Grid> 154 )} 155 </Paper> 156 </Grid> 157 158 <Grid item xs={12}> 159 <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}> 160 <Box sx={{ 161 display: 'flex', 162 alignItems: 'center', 163 justifyContent: 'space-between', 164 mb: 2 165 }}> 166 <Box sx={{display: 'flex', alignItems: 'center'}}> 167 <ComputerIcon color="primary" sx={{mr: 1}}/> 168 <Typography variant="h6" fontWeight="bold">My Builds</Typography> 169 </Box> 170 171 {data.userBuilds.length > 4 && ( 172 <Button 173 variant="outlined" 174 size="small" 175 onClick={() => setOpenMyBuildsDialog(true)} 176 > 177 See All ({data.userBuilds.length}) 178 </Button> 179 )} 180 </Box> 181 <Divider sx={{mb: 2}}/> 182 183 {data.userBuilds.length === 0 ? ( 184 <Box sx={{textAlign: 'center', mt: 4}}> 185 <Typography color="text.secondary" gutterBottom>You haven't created any builds 186 yet.</Typography> 187 <Button variant="contained" href="/forge" 188 sx={{color: 'white', fontWeight: 'bolder'}}>Start Forging</Button> 189 </Box> 190 ) : ( 191 <Grid container spacing={2}> 192 {data.userBuilds.slice(0, 4).map((build: any) => ( 193 <Grid item xs={12} sm={6} md={4} key={build.id}> 194 <Box sx={{ position: 'relative' }}> 195 <BuildCard 196 build={build} 197 onClick={() => setSelectedBuildId(build.id)} 198 /> 199 <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}> 200 <Button 201 variant="outlined" 202 color="error" 203 size="small" 204 startIcon={<DeleteIcon />} 205 onClick={(e) => { 206 e.stopPropagation(); 207 openDeleteDialog(build.id, build.name); 208 }} 209 sx={{ width: '100%' }} 210 > 211 Delete 212 </Button> 213 </Box> 214 </Box> 215 </Grid> 216 ))} 217 </Grid> 218 )} 219 </Paper> 220 </Grid> 221 222 <Dialog open={openMyBuildsDialog} onClose={() => setOpenMyBuildsDialog(false)} maxWidth="lg" 223 fullWidth> 224 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> 225 All My Builds 226 <IconButton onClick={() => setOpenMyBuildsDialog(false)}> 227 <CloseIcon/> 228 </IconButton> 229 </DialogTitle> 230 <DialogContent dividers> 231 <Grid container spacing={2} sx={{mt: 1}}> 232 {data.userBuilds.map((build: any) => ( 233 <Grid item xs={12} sm={6} md={4} key={build.id}> 234 <Box sx={{ position: 'relative' }}> 235 <BuildCard 236 build={build} 237 onClick={() => { 238 setOpenMyBuildsDialog(false); 239 setSelectedBuildId(build.id); 240 }} 241 /> 242 <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}> 243 <Button 244 variant="outlined" 245 color="error" 246 size="small" 247 startIcon={<DeleteIcon />} 248 onClick={(e) => { 249 e.stopPropagation(); 250 openDeleteDialog(build.id, build.name); 251 }} 252 sx={{ width: '100%' }} 253 > 254 Delete 255 </Button> 256 </Box> 257 </Box> 258 </Grid> 259 ))} 260 </Grid> 261 </DialogContent> 262 <DialogActions> 263 <Button onClick={() => setOpenMyBuildsDialog(false)}>Close</Button> 264 </DialogActions> 265 </Dialog> 266 267 <Dialog open={openFavoritesDialog} onClose={() => setOpenFavoritesDialog(false)} maxWidth="lg" 268 fullWidth> 269 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> 270 All Favorited Builds 271 <IconButton onClick={() => setOpenFavoritesDialog(false)}> 272 <CloseIcon/> 273 </IconButton> 274 </DialogTitle> 275 <DialogContent dividers> 276 <Grid container spacing={2} sx={{mt: 1}}> 277 {data.favoriteBuilds.map((build: any) => ( 278 <Grid item xs={12} sm={6} md={4} key={build.id}> 279 <BuildCard 280 build={build} 281 onClick={() => { 282 setOpenFavoritesDialog(false); 283 setSelectedBuildId(build.id); 284 }} 285 /> 286 </Grid> 287 ))} 288 </Grid> 289 </DialogContent> 290 <DialogActions> 291 <Button onClick={() => setOpenFavoritesDialog(false)}>Close</Button> 292 </DialogActions> 293 </Dialog> 294 295 <Dialog open={deleteDialog.open} onClose={() => setDeleteDialog({ open: false, buildId: null, buildName: '' })}> 296 <DialogTitle>Delete Build</DialogTitle> 297 <DialogContent> 298 <Typography variant="h6" gutterBottom>{deleteDialog.buildName}</Typography> 299 <Typography color="text.secondary"> 300 Are you sure you want to permanently delete this build? This action cannot be undone. 301 </Typography> 302 </DialogContent> 303 <DialogActions> 304 <Button 305 onClick={() => setDeleteDialog({ open: false, buildId: null, buildName: '' })} 306 disabled={deleteLoading} 307 > 308 Cancel 309 </Button> 310 <Button 311 onClick={handleDelete} 312 variant="contained" 313 color="error" 314 disabled={deleteLoading} 315 startIcon={deleteLoading ? <CircularProgress size={20} /> : <DeleteIcon />} 316 > 317 {deleteLoading ? 'Deleting...' : 'Delete Build'} 318 </Button> 319 </DialogActions> 320 </Dialog> 321 322 </Grid> 323 </Grid> 324 </Grid> 279 </Box> 280 ))} 281 </Box> 282 </DialogContent> 283 <DialogActions> 284 <Button onClick={() => setOpenMyBuildsDialog(false)}>Close</Button> 285 </DialogActions> 286 </Dialog> 287 288 <Dialog open={openFavoritesDialog} onClose={() => setOpenFavoritesDialog(false)} maxWidth="xl" fullWidth> 289 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> 290 All Favorited Builds 291 <IconButton onClick={() => setOpenFavoritesDialog(false)}> 292 <CloseIcon/> 293 </IconButton> 294 </DialogTitle> 295 <DialogContent dividers> 296 <Box sx={{ 297 display: 'grid', 298 gridTemplateColumns: { 299 xs: '1fr', 300 sm: 'repeat(2, 1fr)', 301 md: 'repeat(3, 1fr)', 302 lg: 'repeat(4, 1fr)', 303 xl: 'repeat(5, 1fr)' 304 }, 305 gap: 2, 306 mt: 1 307 }}> 308 {data.favoriteBuilds.map((build: any) => ( 309 <BuildCard 310 key={build.id} 311 build={build} 312 onClick={() => { 313 setOpenFavoritesDialog(false); 314 setSelectedBuildId(build.id); 315 }} 316 /> 317 ))} 318 </Box> 319 </DialogContent> 320 <DialogActions> 321 <Button onClick={() => setOpenFavoritesDialog(false)}>Close</Button> 322 </DialogActions> 323 </Dialog> 324 325 <Dialog open={deleteDialog.open} onClose={() => setDeleteDialog({ open: false, buildId: null, buildName: '' })}> 326 <DialogTitle>Delete Build</DialogTitle> 327 <DialogContent> 328 <Typography variant="h6" gutterBottom>{deleteDialog.buildName}</Typography> 329 <Typography color="text.secondary"> 330 Are you sure you want to permanently delete this build? This action cannot be undone. 331 </Typography> 332 </DialogContent> 333 <DialogActions> 334 <Button 335 onClick={() => setDeleteDialog({ open: false, buildId: null, buildName: '' })} 336 disabled={deleteLoading} 337 > 338 Cancel 339 </Button> 340 <Button 341 onClick={handleDelete} 342 variant="contained" 343 color="error" 344 disabled={deleteLoading} 345 startIcon={deleteLoading ? <CircularProgress size={20} /> : <DeleteIcon />} 346 > 347 {deleteLoading ? 'Deleting...' : 'Delete Build'} 348 </Button> 349 </DialogActions> 350 </Dialog> 325 351 326 352 <BuildDetailsDialog … … 332 358 loadData(); 333 359 }} 334 onClone={handleCloneWrapper}335 360 isDashboardView={true} 336 361 /> -
pages/index/+Page.tsx
rc586cbd r92fafb3 10 10 import BuildCard from '../../components/BuildCard'; 11 11 12 import {onGetApprovedBuilds, onGetAuthState , onCloneBuild} from '../+Layout.telefunc';12 import {onGetApprovedBuilds, onGetAuthState} from '../+Layout.telefunc'; 13 13 14 14 export default function HomePage() { … … 46 46 } 47 47 48 loadSite();48 void loadSite(); 49 49 }, []); 50 51 const handleCloneWrapper = async (buildId: number) => {52 if (!data?.isLoggedIn) return alert("Please login to clone builds!");53 if (confirm(`Clone this build to your dashboard?`)) {54 await onCloneBuild({buildId});55 alert("Build cloned! Check your dashboard.");56 setSelectedBuildId(null);57 }58 };59 50 60 51 if (!data) return <Box sx={{p: 10, textAlign: 'center'}}>Loading Forge...</Box>; … … 160 151 currentUser={data.userId} 161 152 onClose={() => setSelectedBuildId(null)} 162 onClone={handleCloneWrapper}163 153 /> 164 154
Note:
See TracChangeset
for help on using the changeset viewer.
