Changeset b6e1b3c for components
- Timestamp:
- 12/29/25 02:32:48 (6 months ago)
- Branches:
- main
- Children:
- f46bf5c
- Parents:
- 3870834
- File:
-
- 1 edited
-
components/BuildDetailsDialog.tsx (modified) (15 diffs)
Legend:
- Unmodified
- Added
- Removed
-
components/BuildDetailsDialog.tsx
r3870834 rb6e1b3c 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>
Note:
See TracChangeset
for help on using the changeset viewer.
