Changes in / [dda6f51:f46bf5c]
- Files:
-
- 4 edited
-
components/BuildDetailsDialog.tsx (modified) (15 diffs)
-
pages/dashboard/admin/+Page.tsx (modified) (6 diffs)
-
pages/dashboard/user/+Page.tsx (modified) (9 diffs)
-
pages/forge/+Page.tsx (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
components/BuildDetailsDialog.tsx
rdda6f51 rf46bf5c 10 10 import PersonIcon from "@mui/icons-material/Person"; 11 11 import {onGetBuildDetails, onSetReview, onToggleFavorite, onCloneBuild, onSetRating} from '../pages/+Layout.telefunc'; 12 13 const formatPrice = (price: any) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(Number(price) || 0); 14 15 export default function BuildDetailsDialog({ open, buildId, onClose, currentUser }: any) { 12 import {onGetBuildState} from '../pages/forge/forge.telefunc'; 13 14 const formatPrice = (price: any) => new Intl.NumberFormat('en-US', { 15 style: 'currency', 16 currency: 'USD' 17 }).format(Number(price) || 0); 18 19 export default function BuildDetailsDialog({open, buildId, onClose, currentUser, isDashboardView = false}: { 20 open: boolean; 21 buildId: number | null; 22 onClose: () => void; 23 currentUser: any; 24 isDashboardView?: boolean; 25 }) { 16 26 const [details, setDetails] = useState<any>(null); 17 27 const [loading, setLoading] = useState(false); … … 19 29 const [cloneDialogOpen, setCloneDialogOpen] = useState(false); 20 30 const [cloningBuildId, setCloningBuildId] = useState<number | null>(null); 31 const [isOwner, setIsOwner] = useState(false); 21 32 22 33 const [reviewText, setReviewText] = useState(""); 23 34 const [ratingVal, setRatingVal] = useState(5); 24 35 36 // Main details fetch 25 37 useEffect(() => { 26 if (open && buildId ) {38 if (open && buildId !== null && typeof buildId === 'number') { 27 39 setLoading(true); 28 40 setReviewText(""); 29 41 setRatingVal(5); 30 42 31 onGetBuildDetails({ buildId})43 onGetBuildDetails({buildId}) 32 44 .then(data => { 33 45 setDetails(data); … … 39 51 }, [open, buildId]); 40 52 53 // Ownership check for edit button 54 useEffect(() => { 55 if (open && buildId !== null && typeof buildId === 'number') { 56 onGetBuildState({ buildId }) // ← Only buildId, no userId! 57 .then(state => { 58 setIsOwner(!!state); 59 }) 60 .catch(() => setIsOwner(false)); 61 } else { 62 setIsOwner(false); 63 } 64 }, [open, buildId]); 65 41 66 const handleFavorite = async () => { 42 if (!currentUser ) return alert("Please login to favorite builds.");43 const res = await onToggleFavorite({ buildId});44 setDetails((prev: any) => ({ ...prev, isFavorite: res}));67 if (!currentUser || buildId === null) return alert("Please login to favorite builds."); 68 const res = await onToggleFavorite({buildId}); 69 setDetails((prev: any) => ({...prev, isFavorite: res})); 45 70 }; 46 71 47 72 const handleSubmitReview = async () => { 48 if (!currentUser ) return alert("Please login to review.");73 if (!currentUser || buildId === null) return alert("Please login to review."); 49 74 50 75 await onSetReview({ 51 76 buildId, 52 77 content: reviewText, 53 // rating: ratingVal54 78 }); 55 79 … … 57 81 buildId, 58 82 value: ratingVal 59 }) 60 61 const refreshed = await onGetBuildDetails({ buildId});83 }); 84 85 const refreshed = await onGetBuildDetails({buildId}); 62 86 setDetails(refreshed); 63 87 }; … … 68 92 try { 69 93 const newBuildId = await onCloneBuild({ buildId: cloningBuildId }); 70 71 94 window.location.href = `/forge?buildId=${newBuildId}`; 72 73 95 setCloneDialogOpen(false); 74 96 setCloningBuildId(null); … … 78 100 }; 79 101 80 81 102 if (!open) return null; 82 103 … … 85 106 <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth scroll="paper"> 86 107 {loading || !details ? ( 87 <Box sx={{ p: 5, textAlign: 'center'}}>Loading Forge Schematics...</Box>108 <Box sx={{p: 5, textAlign: 'center'}}>Loading Forge Schematics...</Box> 88 109 ) : ( 89 110 <> 90 <DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', bgcolor: '#ff8201' }}> 111 <DialogTitle sx={{ 112 display: 'flex', 113 justifyContent: 'space-between', 114 alignItems: 'center', 115 bgcolor: '#ff8201' 116 }}> 91 117 <Box> 92 118 <Typography variant="h5" fontWeight="bold">{details.name}</Typography> 93 <Box sx={{ display: 'flex', alignItems: 'center', gap: 1}}>94 <PersonIcon sx={{ fontSize: 16 }}/>119 <Box sx={{display: 'flex', alignItems: 'center', gap: 1}}> 120 <PersonIcon sx={{fontSize: 16}}/> 95 121 <Typography variant="subtitle2" color="text.secondary" fontWeight="bold"> 96 122 by {details.creator} 97 123 </Typography> 98 <Chip label={formatPrice(details.totalPrice)} size="small" color="primary" variant="outlined" /> 124 <Chip label={formatPrice(details.totalPrice)} size="small" color="primary" 125 variant="outlined"/> 99 126 </Box> 100 127 </Box> 101 <IconButton onClick={onClose}><CloseIcon /></IconButton>128 <IconButton onClick={onClose}><CloseIcon/></IconButton> 102 129 </DialogTitle> 103 130 104 <DialogContent sx={{ p: 0 }}> 105 <Box sx={{ borderBottom: 1, borderColor: 'divider', px: 2, bgcolor: 'primary', position: 'sticky', top: 0, zIndex: 1 }}> 131 <DialogContent sx={{p: 0}}> 132 <Box sx={{ 133 borderBottom: 1, 134 borderColor: 'divider', 135 px: 2, 136 bgcolor: 'primary', 137 position: 'sticky', 138 top: 0, 139 zIndex: 1 140 }}> 106 141 <Tabs value={tabIndex} onChange={(_, v) => setTabIndex(v)}> 107 <Tab label="Specs" />108 <Tab label={`Reviews (${details.ratingStatistics.ratingCount})`} />142 <Tab label="Specs"/> 143 <Tab label={`Reviews (${details.ratingStatistics.ratingCount})`}/> 109 144 </Tabs> 110 145 </Box> 111 146 112 <Box sx={{ p: 3}}>147 <Box sx={{p: 3}}> 113 148 {tabIndex === 0 && ( 114 149 <Grid container spacing={2}> … … 118 153 {details.components.map((comp: any) => ( 119 154 <TableRow key={comp.id}> 120 <TableCell sx={{ width: 50}}>155 <TableCell sx={{width: 50}}> 121 156 <Avatar 122 157 src={comp.img_url || undefined} 123 158 variant="rounded" 124 sx={{ width: 45, height: 45, bgcolor: '#ff8201'}}159 sx={{width: 45, height: 45, bgcolor: '#ff8201'}} 125 160 > 126 161 {comp.type?.substring(0, 3)?.toUpperCase()} … … 128 163 </TableCell> 129 164 <TableCell> 130 <Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.75rem', textTransform: 'uppercase' }}> 165 <Typography variant="body2" color="text.secondary" sx={{ 166 fontSize: '0.75rem', 167 textTransform: 'uppercase' 168 }}> 131 169 {comp.type} 132 170 </Typography> … … 135 173 </Typography> 136 174 </TableCell> 137 <TableCell align="right" sx={{ fontWeight: 'bold', color: '#ff8201' }}> 175 <TableCell align="right" 176 sx={{fontWeight: 'bold', color: '#ff8201'}}> 138 177 {formatPrice(comp.price)} 139 178 </TableCell> 140 179 </TableRow> 141 180 ))} 142 <TableRow sx={{ bgcolor: '#424343' }}> 143 <TableCell colSpan={2} sx={{ fontWeight: 'bold', color: '#ff8201' }}>TOTAL</TableCell> 144 <TableCell align="right" sx={{ fontWeight: 'bold', fontSize: '1.1rem', color: 'primary.main' }}> 181 <TableRow sx={{bgcolor: '#424343'}}> 182 <TableCell colSpan={2} sx={{ 183 fontWeight: 'bold', 184 color: '#ff8201' 185 }}>TOTAL</TableCell> 186 <TableCell align="right" sx={{ 187 fontWeight: 'bold', 188 fontSize: '1.1rem', 189 color: 'primary.main' 190 }}> 145 191 {formatPrice(details.totalPrice)} 146 192 </TableCell> … … 151 197 152 198 <Grid item xs={12} md={4}> 153 <Box sx={{ bgcolor: '#424343', p: 2, borderRadius: 2, mb: 2 }}> 154 <Typography color="primary.main" gutterBottom fontWeight="bold">Builder's Notes</Typography> 155 <Typography color="primary.main" variant="body2" sx={{ fontStyle: 'italic' }}> 199 <Box sx={{bgcolor: '#424343', p: 2, borderRadius: 2, mb: 2}}> 200 <Typography color="primary.main" gutterBottom fontWeight="bold">Builder's 201 Notes</Typography> 202 <Typography color="primary.main" variant="body2" 203 sx={{fontStyle: 'italic'}}> 156 204 "{details.description || "No notes provided."}" 157 205 </Typography> 158 206 </Box> 159 207 160 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}> 161 <Button 162 variant="contained" 163 color="primary" 164 size="large" 165 startIcon={<AutoFixHighIcon />} 166 onClick={() => { 167 setCloningBuildId(details.id); 168 setCloneDialogOpen(true); 169 }} 170 > 171 Clone & Edit 172 </Button> 208 <Box sx={{display: 'flex', flexDirection: 'column', gap: 1}}> 209 {isDashboardView && isOwner ? ( 210 <Button 211 variant="contained" 212 color="primary" 213 size="large" 214 startIcon={<AutoFixHighIcon/>} 215 onClick={() => { 216 window.location.href = `/forge?buildId=${details.id}`; 217 onClose(); 218 }} 219 > 220 Edit Build 221 </Button> 222 ) : ( 223 <Button 224 variant="contained" 225 color="primary" 226 size="large" 227 startIcon={<AutoFixHighIcon/>} 228 onClick={() => { 229 setCloningBuildId(details.id); 230 setCloneDialogOpen(true); 231 }} 232 > 233 Clone & Edit 234 </Button> 235 )} 173 236 <Button 174 237 variant={details.isFavorite ? "contained" : "outlined"} 175 238 color={details.isFavorite ? "error" : "primary"} 176 startIcon={details.isFavorite ? <FavoriteIcon /> : <FavoriteBorderIcon />} 239 startIcon={details.isFavorite ? <FavoriteIcon/> : 240 <FavoriteBorderIcon/>} 177 241 onClick={handleFavorite} 178 242 > … … 186 250 {tabIndex === 1 && ( 187 251 <Box> 188 <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4, p: 2, bgcolor: '#5e5e5e', borderRadius: 2 }}> 189 <Typography variant="h3" fontWeight="bold">{details.ratingStatistics.averageRating.toFixed(1)}</Typography> 252 <Box sx={{ 253 display: 'flex', 254 alignItems: 'center', 255 gap: 2, 256 mb: 4, 257 p: 2, 258 bgcolor: '#5e5e5e', 259 borderRadius: 2 260 }}> 261 <Typography variant="h3" 262 fontWeight="bold">{details.ratingStatistics.averageRating.toFixed(1)}</Typography> 190 263 <Box> 191 <Rating value={details.ratingStatistics.averageRating} readOnly precision={0.5} /> 192 <Typography variant="body2" color="text.secondary">{details.ratingStatistics.ratingCount} ratings</Typography> 264 <Rating value={details.ratingStatistics.averageRating} readOnly 265 precision={0.5}/> 266 <Typography variant="body2" 267 color="text.secondary">{details.ratingStatistics.ratingCount} ratings</Typography> 193 268 </Box> 194 269 </Box> 195 270 196 271 {currentUser && details.userId !== currentUser.id && ( 197 <Box sx={{ mb: 4, p: 2, border: '1px solid #ddd', borderRadius: 2}}>272 <Box sx={{mb: 4, p: 2, border: '1px solid #ddd', borderRadius: 2}}> 198 273 <Typography variant="subtitle2" gutterBottom>Your Review</Typography> 199 <Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}> 200 <Rating value={ratingVal} onChange={(_, v) => setRatingVal(v || 5)} /> 274 <Box sx={{display: 'flex', alignItems: 'center', mb: 1}}> 275 <Rating value={ratingVal} 276 onChange={(_, v) => setRatingVal(v || 5)}/> 201 277 </Box> 202 278 <TextField … … 207 283 value={reviewText} 208 284 onChange={(e) => setReviewText(e.target.value)} 209 sx={{ mb: 1}}285 sx={{mb: 1}} 210 286 /> 211 287 <Button size="small" variant="contained" onClick={handleSubmitReview}> … … 216 292 217 293 {currentUser && details.userId === currentUser.id && ( 218 <Alert severity="info" sx={{ mb: 4}}>294 <Alert severity="info" sx={{mb: 4}}> 219 295 You cannot rate your own builds. 220 296 </Alert> 221 297 )} 222 298 223 <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2}}>299 <Box sx={{display: 'flex', flexDirection: 'column', gap: 2}}> 224 300 {details.reviews.map((rev: any, i: number) => ( 225 <Box key={i} sx={{ pb: 2, borderBottom: '1px solid #eee' }}> 226 <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}> 227 <Typography fontWeight="bold" variant="body2">{rev.username}</Typography> 228 <Typography variant="caption" color="text.secondary">{rev.createdAt}</Typography> 301 <Box key={i} sx={{pb: 2, borderBottom: '1px solid #eee'}}> 302 <Box sx={{ 303 display: 'flex', 304 justifyContent: 'space-between', 305 mb: 0.5 306 }}> 307 <Typography fontWeight="bold" 308 variant="body2">{rev.username}</Typography> 309 <Typography variant="caption" 310 color="text.secondary">{rev.createdAt}</Typography> 229 311 </Box> 230 312 <Typography variant="body2">{rev.content}</Typography> … … 232 314 ))} 233 315 {details.reviews.length === 0 && ( 234 <Typography color="text.secondary" align="center">No reviews yet. Be the first!</Typography> 316 <Typography color="text.secondary" align="center">No reviews yet. Be the 317 first!</Typography> 235 318 )} 236 319 </Box> -
pages/dashboard/admin/+Page.tsx
rdda6f51 rf46bf5c 19 19 import BuildCard from '../../../components/BuildCard'; 20 20 import BuildDetailsDialog from '../../../components/BuildDetailsDialog'; 21 import DeleteIcon from "@mui/icons-material/Delete"; 22 import {onDeleteBuild} from "../user/userDashboard.telefunc"; 21 23 22 24 export default function AdminDashboard() { … … 24 26 const [loading, setLoading] = useState(true); 25 27 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null); 26 27 // Build approval dialog state 28 const [deleteDialog, setDeleteDialog] = useState<{ open: boolean, buildId: number | null, buildName: string }>({ open: false, buildId: null, buildName: '' }); 29 const [deleteLoading, setDeleteLoading] = useState(false); 30 28 31 const [buildApprovalDialog, setBuildApprovalDialog] = useState<{ 29 32 open: boolean, … … 107 110 } catch (e) { 108 111 alert("Failed to update suggestion"); 112 } 113 }; 114 115 const openDeleteDialog = (buildId: number, buildName: string) => { 116 setDeleteDialog({ open: true, buildId, buildName }); 117 }; 118 119 const handleDelete = async () => { 120 if (!deleteDialog.buildId) return; 121 setDeleteLoading(true); 122 try { 123 await onDeleteBuild({buildId: deleteDialog.buildId}); 124 setDeleteDialog({ open: false, buildId: null, buildName: '' }); 125 loadData(); // refresh na user i favorite builds 126 setSelectedBuildId(null); 127 } catch (e) { 128 alert("Failed to delete build"); 129 } finally { 130 setDeleteLoading(false); 109 131 } 110 132 }; … … 253 275 <Grid container spacing={2}> 254 276 {data.userBuilds.slice(0, 4).map((build: any) => ( 255 <Grid item xs={12} md={3} key={build.id}> 256 <BuildCard 257 build={build} 258 onClick={() => setSelectedBuildId(build.id)} 259 /> 277 <Grid item xs={12} sm={6} md={4} key={build.id}> 278 <Box sx={{ position: 'relative' }}> 279 <BuildCard 280 build={build} 281 onClick={() => setSelectedBuildId(build.id)} 282 /> 283 <Box sx={{ mt: 1, display: 'flex', justifyContent: 'center' }}> 284 <Button 285 variant="outlined" 286 color="error" 287 size="small" 288 startIcon={<DeleteIcon />} 289 onClick={(e) => { 290 e.stopPropagation(); 291 openDeleteDialog(build.id, build.name); 292 }} 293 sx={{ width: '100%' }} 294 > 295 Delete 296 </Button> 297 </Box> 298 </Box> 260 299 </Grid> 261 300 ))} … … 329 368 </Dialog> 330 369 370 <Dialog open={deleteDialog.open} onClose={() => setDeleteDialog({ open: false, buildId: null, buildName: '' })}> 371 <DialogTitle>Delete Build</DialogTitle> 372 <DialogContent> 373 <Typography variant="h6" gutterBottom>{deleteDialog.buildName}</Typography> 374 <Typography color="text.secondary"> 375 Are you sure you want to permanently delete this build? This action cannot be undone. 376 </Typography> 377 </DialogContent> 378 <DialogActions> 379 <Button 380 onClick={() => setDeleteDialog({ open: false, buildId: null, buildName: '' })} 381 disabled={deleteLoading} 382 > 383 Cancel 384 </Button> 385 <Button 386 onClick={handleDelete} 387 variant="contained" 388 color="error" 389 disabled={deleteLoading} 390 startIcon={deleteLoading ? <CircularProgress size={20} /> : <DeleteIcon />} 391 > 392 {deleteLoading ? 'Deleting...' : 'Delete Build'} 393 </Button> 394 </DialogActions> 395 </Dialog> 396 331 397 <BuildDetailsDialog 332 398 open={!!selectedBuildId} … … 335 401 onClose={() => setSelectedBuildId(null)} 336 402 onClone={() => alert("Clone disabled in admin view")} 403 isDashboardView={true} 337 404 /> 338 405 </Container> -
pages/dashboard/user/+Page.tsx
rdda6f51 rf46bf5c 17 17 } from '@mui/material'; 18 18 import CloseIcon from '@mui/icons-material/Close'; 19 import DeleteIcon from '@mui/icons-material/Delete'; 19 20 20 21 import PersonIcon from '@mui/icons-material/Person'; … … 38 39 const [loading, setLoading] = useState(true); 39 40 const [error, setError] = useState<string | null>(null); 40 41 41 const [openMyBuildsDialog, setOpenMyBuildsDialog] = useState(false); 42 42 const [openFavoritesDialog, setOpenFavoritesDialog] = useState(false); 43 44 43 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null); 44 const [deleteDialog, setDeleteDialog] = useState<{ open: boolean, buildId: number | null, buildName: string }>({ open: false, buildId: null, buildName: '' }); 45 const [deleteLoading, setDeleteLoading] = useState(false); 45 46 46 47 const loadData = () => { … … 61 62 }, []); 62 63 63 const handleDelete = async (buildId: number) => { 64 if (!confirm("Are you sure you want to delete this build?")) return; 64 const openDeleteDialog = (buildId: number, buildName: string) => { 65 setDeleteDialog({ open: true, buildId, buildName }); 66 }; 67 68 const handleDelete = async () => { 69 if (!deleteDialog.buildId) return; 70 setDeleteLoading(true); 65 71 try { 66 await onDeleteBuild({buildId}); 67 loadData(); 72 await onDeleteBuild({buildId: deleteDialog.buildId}); 73 setDeleteDialog({ open: false, buildId: null, buildName: '' }); 74 loadData(); // refresh na user i favorite builds 68 75 setSelectedBuildId(null); 69 76 } catch (e) { 70 77 alert("Failed to delete build"); 78 } finally { 79 setDeleteLoading(false); 71 80 } 72 81 }; … … 86 95 variant="h6">{error}</Typography><Button 87 96 href="/auth/login" variant="contained">Login</Button></Container>; 88 // console.log("Current User ID passed to dialog:", data?.user?.id);89 97 90 98 return ( … … 150 158 <Grid item xs={12}> 151 159 <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}> 152 153 160 <Box sx={{ 154 161 display: 'flex', … … 185 192 {data.userBuilds.slice(0, 4).map((build: any) => ( 186 193 <Grid item xs={12} sm={6} md={4} key={build.id}> 187 <BuildCard 188 build={build} 189 onClick={() => setSelectedBuildId(build.id)} 190 /> 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> 191 215 </Grid> 192 216 ))} … … 208 232 {data.userBuilds.map((build: any) => ( 209 233 <Grid item xs={12} sm={6} md={4} key={build.id}> 210 <BuildCard 211 build={build} 212 onClick={() => { 213 setOpenMyBuildsDialog(false); 214 setSelectedBuildId(build.id); 215 }} 216 /> 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> 217 258 </Grid> 218 259 ))} … … 252 293 </Dialog> 253 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 254 322 </Grid> 255 323 </Grid> … … 265 333 }} 266 334 onClone={handleCloneWrapper} 335 isDashboardView={true} 267 336 /> 268 337 </Container> -
pages/forge/+Page.tsx
rdda6f51 rf46bf5c 131 131 let id = buildId; 132 132 if (!id) { 133 const result = await onAddNewBuild({name: "Draft Build", description: "Work in progress"}); 133 const result = await onAddNewBuild({ 134 name: buildName.trim() || "New Build", 135 description: description || "Work in progress" 136 }); 134 137 id = typeof result === 'number' ? result : (result as any)?.buildId; 135 138 if (!id || !Number.isInteger(id) || id <= 0) { … … 150 153 await onAddComponentToBuild({buildId: id, componentId: component.id}); 151 154 } catch (e) { 152 // console.error("Failed to add component to server build", e);153 155 alert("Failed to add component. Please try again."); 154 156 } finally { … … 156 158 } 157 159 }; 160 158 161 159 162 const handleRemovePart = async (slotId: string) => {
Note:
See TracChangeset
for help on using the changeset viewer.
