- Timestamp:
- 12/28/25 00:07:22 (7 months ago)
- Branches:
- main
- Children:
- 9c87509
- Parents:
- f7c0b0d
- Location:
- pages
- Files:
-
- 3 edited
-
completed-builds/+Page.tsx (modified) (11 diffs)
-
dashboard/user/+Page.tsx (modified) (13 diffs)
-
index/+Page.tsx (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
-
pages/completed-builds/+Page.tsx
rf7c0b0d re599341 1 1 import React, { useEffect, useState } from 'react'; 2 2 import { 3 Container, Grid,Box, Typography, TextField, MenuItem, Select,4 Slider, Button, Paper, InputAdornment 3 Container, Box, Typography, TextField, MenuItem, Select, 4 Slider, Button, Paper, InputAdornment, CircularProgress 5 5 } from '@mui/material'; 6 6 import SearchIcon from '@mui/icons-material/Search'; … … 9 9 import BuildCard from '../../components/BuildCard'; 10 10 import BuildDetailsDialog from '../../components/BuildDetailsDialog'; 11 12 11 import { onGetApprovedBuilds, onCloneBuild, onGetAuthState } from '../+Layout.telefunc'; 13 12 … … 27 26 const [userData, data] = await Promise.all([ 28 27 onGetAuthState(), 29 onGetApprovedBuilds({ q: searchQuery})28 onGetApprovedBuilds({q: searchQuery}) 30 29 ]); 31 30 setUserId(userData.userId); … … 58 57 59 58 setBuilds(sortedData); 59 } catch (e) { 60 console.error("Failed to load builds", e); 60 61 } finally { 61 62 setLoading(false); … … 65 66 useEffect(() => { 66 67 loadBuilds(); 67 }, [sortBy, priceRange, searchQuery]);68 69 useEffect(() => {70 loadBuilds();71 68 }, [sortBy, searchQuery]); 72 69 … … 74 71 if (!userId) return alert("Please login to clone builds!"); 75 72 if (confirm(`Clone this build?`)) { 76 await onCloneBuild({ buildId});73 await onCloneBuild({buildId}); 77 74 alert("Build cloned!"); 78 75 setSelectedBuildId(null); … … 81 78 82 79 return ( 83 <Container maxWidth= "xl" sx={{ mt: 4, mb: 10}}>80 <Container maxWidth={false} sx={{ mt: 4, mb: 10, px: { xs: 2, md: 4 } }}> 84 81 <Typography variant="h4" fontWeight="bold" gutterBottom>Completed Builds</Typography> 85 82 <Typography color="text.secondary" sx={{ mb: 4 }}> … … 87 84 </Typography> 88 85 89 <Grid container spacing={4}> 90 <Grid item xs={12} md={3}> 86 <Box sx={{ display: 'flex', flexDirection: { xs: 'column', md: 'row' }, gap: 4 }}> 87 88 <Box sx={{ 89 width: { xs: '100%', md: '280px' }, 90 flexShrink: 0 91 }}> 91 92 <Paper variant="outlined" sx={{ p: 3, position: 'sticky', top: 20 }}> 92 93 <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}> … … 127 128 </Button> 128 129 </Paper> 129 </Grid> 130 131 <Grid item xs={12} md={9}> 132 {/* Toolbar */} 130 </Box> 131 132 <Box sx={{ flexGrow: 1 }}> 133 133 <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}> 134 134 <Typography fontWeight="bold">{builds.length} Builds Found</Typography> … … 152 152 153 153 {loading ? ( 154 <Box sx={{ p: 5, textAlign: 'center' }}>Loading...</Box> 154 <Box sx={{ p: 5, textAlign: 'center' }}> 155 <CircularProgress /> 156 </Box> 155 157 ) : ( 156 <Grid container spacing={3}> 158 <Box 159 sx={{ 160 display: 'grid', 161 gridTemplateColumns: { 162 xs: '1fr', 163 sm: 'repeat(2, 1fr)', 164 md: 'repeat(3, 1fr)', 165 lg: 'repeat(4, 1fr)', 166 xl: 'repeat(5, 1fr)' 167 }, 168 gap: 3, 169 width: '100%' 170 }} 171 > 157 172 {builds.map((build) => ( 158 <Grid item xs={12} sm={6} lg={4} key={build.id} sx={{ display: 'flex' }}> 159 <Box sx={{ width: '100%' }}> 160 <BuildCard 161 build={build} 162 onClick={() => setSelectedBuildId(build.id)} 163 /> 164 </Box> 165 </Grid> 173 <BuildCard 174 key={build.id} 175 build={build} 176 onClick={() => setSelectedBuildId(build.id)} 177 /> 166 178 ))} 167 179 {builds.length === 0 && ( 168 <Grid item xs={12}> 169 <Box sx={{ p: 5, textAlign: 'center', bgcolor: '#f5f5f5', borderRadius: 2 }}> 170 <Typography>No builds found matching your filters.</Typography> 171 </Box> 172 </Grid> 180 <Box sx={{ gridColumn: '1 / -1', p: 5, textAlign: 'center', bgcolor: '#f5f5f5', borderRadius: 2 }}> 181 <Typography>No builds found matching your filters.</Typography> 182 </Box> 173 183 )} 174 </ Grid>184 </Box> 175 185 )} 176 </Grid> 177 </Grid> 178 186 </Box> 187 </Box> 188 189 {/* @ts-ignore */} 179 190 <BuildDetailsDialog 180 191 open={!!selectedBuildId} … … 186 197 </Container> 187 198 ); 199 188 200 } 189 -
pages/dashboard/user/+Page.tsx
rf7c0b0d re599341 9 9 Button, 10 10 CircularProgress, 11 Card, 12 CardContent, 13 CardActionArea, 14 IconButton 11 IconButton, 12 Grid, 13 Dialog, 14 DialogTitle, 15 DialogContent, 16 DialogActions 15 17 } from '@mui/material'; 16 import Grid from '@mui/material/Grid';17 import {Dialog, DialogTitle, DialogContent, DialogActions} from '@mui/material';18 18 import CloseIcon from '@mui/icons-material/Close'; 19 19 … … 21 21 import ComputerIcon from '@mui/icons-material/Computer'; 22 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 23 26 24 import {getUserInfoAndData, onDeleteBuild} from "./userDashboard.telefunc"; 25 import {onCloneBuild} from "../../+Layout.telefunc"; 26 27 import BuildCard from "../../../components/BuildCard"; 28 import BuildDetailsDialog from "../../../components/BuildDetailsDialog"; 27 29 28 30 type DashboardData = { … … 36 38 const [loading, setLoading] = useState(true); 37 39 const [error, setError] = useState<string | null>(null); 40 38 41 const [openMyBuildsDialog, setOpenMyBuildsDialog] = useState(false); 42 const [openFavoritesDialog, setOpenFavoritesDialog] = useState(false); 43 44 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null); 39 45 40 46 const loadData = () => { … … 60 66 await onDeleteBuild({buildId}); 61 67 loadData(); 68 setSelectedBuildId(null); 62 69 } catch (e) { 63 70 alert("Failed to delete build"); … … 65 72 }; 66 73 74 const handleCloneWrapper = async (buildId: number) => { 75 if (!data?.user) return alert("Please login to clone builds!"); 76 if (confirm(`Clone this build to your dashboard?`)) { 77 await onCloneBuild({buildId}); 78 alert("Build cloned! Check your dashboard."); 79 setSelectedBuildId(null); 80 loadData(); 81 } 82 }; 83 67 84 if (loading) return <Box sx={{display: 'flex', justifyContent: 'center', mt: 10}}><CircularProgress/></Box>; 68 85 if (error || !data) return <Container sx={{mt: 5, textAlign: 'center'}}><Typography color="error" 69 86 variant="h6">{error}</Typography><Button 70 87 href="/auth/login" variant="contained">Login</Button></Container>; 71 72 // @ts-ignore 88 // console.log("Current User ID passed to dialog:", data?.user?.id); 89 73 90 return ( 74 91 <Container maxWidth="xl" sx={{mt: 4, mb: 4, color: 'white'}}> 75 92 <Grid container spacing={3}> 76 77 93 <Grid item xs={12} md={3}> 78 94 <Paper elevation={3} … … 83 99 <Typography variant="body2" color="text.secondary" gutterBottom>{data.user.email}</Typography> 84 100 <Divider sx={{width: '100%', my: 2}}/> 85 {/*<Button variant="outlined" fullWidth color="primary" sx={{mb: 1}}>Edit Profile</Button>*/}86 101 </Paper> 87 102 </Grid> … … 92 107 <Grid item xs={12}> 93 108 <Paper elevation={3} sx={{p: 3, minHeight: '300px', bgcolor: '#1e1e1e'}}> 94 <Box sx={{display: 'flex', alignItems: 'center', mb: 2}}> 95 <FavoriteIcon color="error" sx={{mr: 1}}/> 96 <Typography variant="h6" fontWeight="bold">Favorited Builds</Typography> 109 <Box sx={{ 110 display: 'flex', 111 alignItems: 'center', 112 justifyContent: 'space-between', 113 mb: 2 114 }}> 115 <Box sx={{display: 'flex', alignItems: 'center'}}> 116 <FavoriteIcon color="error" sx={{mr: 1}}/> 117 <Typography variant="h6" fontWeight="bold">Favorited Builds</Typography> 118 </Box> 119 120 {data.favoriteBuilds.length > 4 && ( 121 <Button 122 variant="outlined" 123 size="small" 124 onClick={() => setOpenFavoritesDialog(true)} 125 > 126 See All ({data.favoriteBuilds.length}) 127 </Button> 128 )} 97 129 </Box> 98 130 <Divider sx={{mb: 2}}/> … … 103 135 ) : ( 104 136 <Grid container spacing={2}> 105 {data.favoriteBuilds. map((build: any) => (137 {data.favoriteBuilds.slice(0, 4).map((build: any) => ( 106 138 <Grid item xs={12} sm={6} md={4} key={build.id}> 107 <BuildCard build={build}/> 139 <BuildCard 140 build={build} 141 onClick={() => setSelectedBuildId(build.id)} 142 /> 108 143 </Grid> 109 144 ))} … … 148 183 ) : ( 149 184 <Grid container spacing={2}> 150 {data.userBuilds.slice(0, 3).map((build: any) => (151 <Grid item xs={12} sm={6} md={ 3} key={build.id}>185 {data.userBuilds.slice(0, 4).map((build: any) => ( 186 <Grid item xs={12} sm={6} md={4} key={build.id}> 152 187 <BuildCard 153 188 build={build} 154 onDelete={() => handleDelete(build.id)} 155 isOwner={true} 189 onClick={() => setSelectedBuildId(build.id)} 156 190 /> 157 191 </Grid> … … 162 196 </Grid> 163 197 164 <Dialog open={openMyBuildsDialog} onClose={() => setOpenMyBuildsDialog(false)} maxWidth=" md"198 <Dialog open={openMyBuildsDialog} onClose={() => setOpenMyBuildsDialog(false)} maxWidth="lg" 165 199 fullWidth> 166 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> All167 My Builds200 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> 201 All My Builds 168 202 <IconButton onClick={() => setOpenMyBuildsDialog(false)}> 169 203 <CloseIcon/> … … 176 210 <BuildCard 177 211 build={build} 178 onDelete={() => handleDelete(build.id)} 179 isOwner={true} 212 onClick={() => { 213 setOpenMyBuildsDialog(false); 214 setSelectedBuildId(build.id); 215 }} 180 216 /> 181 217 </Grid> … … 183 219 </Grid> 184 220 </DialogContent> 185 186 221 <DialogActions> 187 222 <Button onClick={() => setOpenMyBuildsDialog(false)}>Close</Button> … … 189 224 </Dialog> 190 225 226 <Dialog open={openFavoritesDialog} onClose={() => setOpenFavoritesDialog(false)} maxWidth="lg" 227 fullWidth> 228 <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}> 229 All Favorited Builds 230 <IconButton onClick={() => setOpenFavoritesDialog(false)}> 231 <CloseIcon/> 232 </IconButton> 233 </DialogTitle> 234 <DialogContent dividers> 235 <Grid container spacing={2} sx={{mt: 1}}> 236 {data.favoriteBuilds.map((build: any) => ( 237 <Grid item xs={12} sm={6} md={4} key={build.id}> 238 <BuildCard 239 build={build} 240 onClick={() => { 241 setOpenFavoritesDialog(false); 242 setSelectedBuildId(build.id); 243 }} 244 /> 245 </Grid> 246 ))} 247 </Grid> 248 </DialogContent> 249 <DialogActions> 250 <Button onClick={() => setOpenFavoritesDialog(false)}>Close</Button> 251 </DialogActions> 252 </Dialog> 253 191 254 </Grid> 192 255 </Grid> 193 256 </Grid> 257 258 <BuildDetailsDialog 259 open={!!selectedBuildId} 260 buildId={selectedBuildId} 261 currentUser={data?.user?.id ? Number(data.user.id) : undefined} 262 onClose={() => { 263 setSelectedBuildId(null); 264 loadData(); 265 }} 266 onClone={handleCloneWrapper} 267 /> 194 268 </Container> 195 269 ); 196 270 } 197 198 function BuildCard({build, onDelete, isOwner}: { build: any, onDelete?: () => void, isOwner?: boolean }) {199 return (200 <Card sx={{height: '100%', display: 'flex', flexDirection: 'column'}}>201 <CardActionArea href={`/build/${build.id}`} sx={{flexGrow: 1}}>202 <CardContent>203 <Typography variant="subtitle1" fontWeight="bold" noWrap>204 {build.name || `Build #${build.id}`}205 </Typography>206 <Typography variant="body2" color="text.secondary">207 Created: {build.createdAt ? new Date(build.createdAt).toLocaleDateString() : 'Unknown'}208 </Typography>209 </CardContent>210 </CardActionArea>211 212 {isOwner && (213 <Box sx={{display: 'flex', justifyContent: 'flex-end', p: 1, borderTop: '1px solid #eee'}}>214 <IconButton size="small" href={`/build/edit/${build.id}`} color="primary">215 <EditIcon fontSize="small"/>216 </IconButton>217 <IconButton size="small" onClick={onDelete} color="error">218 <DeleteIcon fontSize="small"/>219 </IconButton>220 </Box>221 )}222 </Card>223 );224 } -
pages/index/+Page.tsx
rf7c0b0d re599341 14 14 export default function HomePage() { 15 15 const [data, setData] = useState<any>(null); 16 const [allRanked, setAllRanked] = useState<any[]>([]); 16 17 17 18 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null); 18 19 19 const [openRankedPopup, setOpenRankedPopup] = useState(false); 20 20 … … 23 23 try { 24 24 const [ 25 authData, highestRankedBuilds, communityBuilds 25 authData, 26 highestRankedBuilds, 27 communityBuilds, 28 fullRankedList 26 29 ] = await Promise.all([ 27 30 onGetAuthState(), 28 onGetApprovedBuilds({ limit: 3 , sort: 'rating_desc' }), 29 onGetApprovedBuilds({ limit: 12 }) 31 onGetApprovedBuilds({ limit: 5 , sort: 'rating_desc' }), 32 onGetApprovedBuilds({ limit: 12 }), 33 onGetApprovedBuilds({ limit: 10, sort: 'rating_desc' }) 30 34 ]); 31 35 … … 36 40 communityBuilds: communityBuilds 37 41 }); 42 setAllRanked(fullRankedList); 38 43 } catch (error) { 39 44 console.error("Error loading homepage data:", error); … … 69 74 Build, share, discuss, and discover custom PC configurations. 70 75 </Typography> 71 <Button variant="contained" size="large" href="/forge r" startIcon={<AutoFixHighIcon />}76 <Button variant="contained" size="large" href="/forge" startIcon={<AutoFixHighIcon />} 72 77 sx={{ fontSize: '1.2rem', px: 4, py: 1.5, mt: 2 }}> 73 78 Start Forging … … 92 97 </Box> 93 98 94 <Grid container spacing={2} sx={{ mb: 8, alignItems: 'stretch' }}> 95 {/*<Grid item xs={12} md={3} sx={{ display: 'flex' }}>*/} 96 {/* <Box sx={{*/} 97 {/* width: '100%', p: 3, bgcolor: '#ff8201', color: 'black', borderRadius: 2,*/} 98 {/* display: 'flex', flexDirection: 'column', justifyContent: 'center'*/} 99 {/* }}>*/} 100 {/* <Typography color={'black'} variant="h5" fontWeight="bold" gutterBottom>The Elite</Typography>*/} 101 {/* <Typography variant="body1" fontWeight="bold">The top 3 community-voted builds.</Typography>*/} 102 {/* </Box>*/} 103 {/*</Grid>*/} 104 99 <Grid container spacing={3} sx={{ mb: 8, alignItems: 'stretch' }}> 105 100 {data.prebuilts.slice(0, 3).map((build: any) => ( 106 101 <Grid item xs={12} sm={6} md={3} key={build.id} sx={{ display: 'flex' }}> … … 114 109 ))} 115 110 </Grid> 116 117 111 118 112 <SectionHeader title="Community Forge" subtitle="Fresh builds from users around the world."/> … … 133 127 </Container> 134 128 135 136 129 <BuildDetailsDialog 137 130 open={!!selectedBuildId} … … 142 135 /> 143 136 144 145 <Dialog open={openRankedPopup} onClose={() => setOpenRankedPopup(false)} maxWidth="lg" fullWidth> 137 <Dialog open={openRankedPopup} onClose={() => setOpenRankedPopup(false)} maxWidth="lg" fullWidth scroll="paper"> 146 138 <DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> 147 Hall of Fame ( All Top Ranked)139 Hall of Fame (Top Rated) 148 140 <IconButton onClick={() => setOpenRankedPopup(false)}><CloseIcon /></IconButton> 149 141 </DialogTitle> 150 142 <DialogContent dividers> 151 143 <Grid container spacing={3}> 152 {data.prebuilts.concat(data.communityBuilds).map((build: any) => ( 153 <Grid item xs={12} sm={6} md={3} key={`popup-${build.id}`}> 154 <BuildCard 155 build={build} 156 onClick={() => { 157 setOpenRankedPopup(false); 158 setSelectedBuildId(build.id); // Open details from popup 159 }} 160 /> 144 {allRanked.map((build: any, index: number) => ( 145 <Grid item xs={12} sm={6} md={3} key={`ranked-${build.id}`}> 146 <Box sx={{ position: 'relative' }}> 147 <Box sx={{ 148 position: 'absolute', top: -10, left: -10, zIndex: 1, 149 width: 30, height: 30, borderRadius: '50%', 150 bgcolor: index < 3 ? '#ff8201' : 'grey.700', color: 'white', 151 display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 'bold' 152 }}> 153 #{index + 1} 154 </Box> 155 <BuildCard 156 build={build} 157 onClick={() => { 158 setOpenRankedPopup(false); 159 setSelectedBuildId(build.id); 160 }} 161 /> 162 </Box> 161 163 </Grid> 162 164 ))} … … 171 173 function SectionHeader({ title, subtitle }: { title: string, subtitle: string }) { 172 174 return ( 173 <Box sx={{ mb: 4, borderLeft: '5px solid # 1976d2', pl: 2 }}>175 <Box sx={{ mb: 4, borderLeft: '5px solid #ff8201', pl: 2 }}> 174 176 <Typography variant="h4" fontWeight="bold" color="text.primary">{title}</Typography> 175 177 <Typography variant="subtitle1" color="text.secondary">{subtitle}</Typography>
Note:
See TracChangeset
for help on using the changeset viewer.
