Changeset 1bf6e1f for pages/index/+Page.tsx
- Timestamp:
- 12/24/25 23:58:00 (7 months ago)
- Branches:
- main
- Children:
- 5c079f4
- Parents:
- 76b980b
- File:
-
- 1 edited
-
pages/index/+Page.tsx (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
pages/index/+Page.tsx
r76b980b r1bf6e1f 1 1 import React, { useEffect, useState } from 'react'; 2 2 import { 3 Container, Box, Typography, Button, Grid, Card, CardMedia, CardContent, 4 CardActions, Chip, Rating, Dialog, DialogTitle, DialogContent, DialogActions, 5 IconButton, Divider 3 Container, Box, Typography, Button, Grid, IconButton, Dialog, DialogTitle, DialogContent 6 4 } from '@mui/material'; 7 5 import CloseIcon from '@mui/icons-material/Close'; 8 6 import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; 9 import CommentIcon from '@mui/icons-material/Comment'; 10 import StarIcon from '@mui/icons-material/Star'; 11 import HardwareIcon from '@mui/icons-material/Memory'; 12 import ListIcon from '@mui/icons-material/List'; // Icon for "Show All" 7 import ListIcon from '@mui/icons-material/List'; 13 8 14 // --- PLACEHOLDER ASSETS --- 15 const PLACEHOLDER_IMG = "https://placehold.co/600x400?text=Gaming+PC";9 import BuildDetailsDialog from '../../components/BuildDetailsDialog'; 10 import BuildCard from '../../components/BuildCard'; 16 11 17 import { onGetHighestRankedBuilds, onCloneBuild } from './buildCards.telefunc'; 18 import {onGetApprovedBuilds, onGetAuthState} from '../+Layout.telefunc'; 19 import {getAuthState} from "../../server/telefunc/ctx"; // 12 import { onGetApprovedBuilds, onGetAuthState, onCloneBuild } from '../+Layout.telefunc'; 20 13 21 14 export default function HomePage() { 22 15 const [data, setData] = useState<any>(null); 23 const [selectedBuild, setSelectedBuild] = useState<any>(null); // For Detail Popup24 const [openDetailPopup, setOpenDetailPopup] = useState(false);25 16 26 // NEW: Popup for "All Top Ranked Builds" 17 const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null); 18 27 19 const [openRankedPopup, setOpenRankedPopup] = useState(false); 28 20 29 21 useEffect(() => { 30 async function loadSite() {31 try {22 async function loadSite() { 23 try { 32 24 const [ 33 authData, highestRankedBuilds, comm nityBuilds25 authData, highestRankedBuilds, communityBuilds 34 26 ] = await Promise.all([ 35 27 onGetAuthState(), 36 onGet HighestRankedBuilds({ limit: 3}),28 onGetApprovedBuilds({ limit: 3 , sort: 'rating_desc' }), 37 29 onGetApprovedBuilds({ limit: 12 }) 38 30 ]); … … 42 34 userId: authData.userId, 43 35 prebuilts: highestRankedBuilds, 44 communityBuilds: comm nityBuilds36 communityBuilds: communityBuilds 45 37 }); 46 38 } catch (error) { 47 39 console.error("Error loading homepage data:", error); 48 40 } 49 } 41 } 42 loadSite(); 43 }, []); 50 44 51 loadSite(); 52 }, []); 53 54 // Open Build Details 55 const handleCardClick = (build: any) => { 56 setSelectedBuild(build); 57 setOpenDetailPopup(true); 58 }; 59 60 const handleClone = async () => { 45 const handleCloneWrapper = async (buildId: number) => { 61 46 if (!data?.isLoggedIn) return alert("Please login to clone builds!"); 62 if (confirm(`Clone "${selectedBuild.name}"to your dashboard?`)) {63 await onCloneBuild({ buildId : selectedBuild.id});47 if (confirm(`Clone this build to your dashboard?`)) { 48 await onCloneBuild({ buildId }); 64 49 alert("Build cloned! Check your dashboard."); 65 set OpenDetailPopup(false);50 setSelectedBuildId(null); 66 51 } 67 52 }; … … 74 59 position: 'relative', height: '500px', 75 60 display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', 76 '&::before': { content: '""', position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', backgroundColor: 'rgba(0,0,0,0.6)' } 61 '&::before': { 62 content: '""', position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', 63 backgroundColor: 'rgba(0,0,0,0.6)' 64 } 77 65 }}> 78 66 <Box sx={{ position: 'relative', textAlign: 'center', zIndex: 1, p: 2 }}> 79 67 <Typography variant="h2" fontWeight="bold" gutterBottom>Forge Your Ultimate Machine</Typography> 80 <Typography variant="h5" sx={{ maxWidth: '800px', mx: 'auto' }}>68 <Typography variant="h5" sx={{ maxWidth: '800px', mx: 'auto', mb: 1 }}> 81 69 Build, share, discuss, and discover custom PC configurations. 82 70 </Typography> 83 <Typography variant="h5" sx={{ mb: 1, maxWidth: '800px', mx: 'auto' }}> 84 Join the community today. 85 </Typography> 86 87 <Button variant="contained" size="large" href="/forger" startIcon={<AutoFixHighIcon />} sx={{ fontSize: '1.2rem', px: 4, py: 1.5 }}> 71 <Button variant="contained" size="large" href="/forger" startIcon={<AutoFixHighIcon />} 72 sx={{ fontSize: '1.2rem', px: 4, py: 1.5, mt: 2 }}> 88 73 Start Forging 89 74 </Button> … … 92 77 93 78 <Container maxWidth="xl" sx={{ mt: 6, mb: 10 }}> 94 <Box sx={{ mb: 4, borderLeft: '5px solid #ff8201', pl: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'end' }}> 79 <Box sx={{ 80 mb: 4, borderLeft: '5px solid #ff8201', pl: 2, 81 display: 'flex', justifyContent: 'space-between', alignItems: 'end' 82 }}> 95 83 <Box> 96 84 <Typography variant="h4" fontWeight="bold">Hall of Fame</Typography> 97 <Typography variant="subtitle1" color="text.secondary">The highest rated configurations from across the community.</Typography> 85 <Typography variant="subtitle1" color="text.secondary"> 86 The highest rated configurations from across the community. 87 </Typography> 98 88 </Box> 99 <Button 100 variant="outlined" 101 startIcon={<ListIcon />} 102 onClick={() => setOpenRankedPopup(true)} 103 > 89 <Button variant="outlined" startIcon={<ListIcon />} onClick={() => setOpenRankedPopup(true)}> 104 90 Show All Top Ranked 105 91 </Button> 106 92 </Box> 107 93 108 <Grid container spacing={3} sx={{ mb: 8 }}> 109 <Grid item xs={12} md={3}> 110 <Box sx={{ 111 height: '100%', p: 3, bgcolor: '#ff8201', color: 'black', borderRadius: 2, 112 display: 'flex', flexDirection: 'column', justifyContent: 'center' 113 }}> 114 <Typography color={'black'} variant="h4" fontWeight="bold" gutterBottom>The Elite</Typography> 115 <Typography fontWeight={'bold'}> 116 These 3 builds represent the pinnacle of performance and looks as voted by the PC Forge community. 117 </Typography> 118 </Box> 119 </Grid> 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>*/} 120 104 121 {data.prebuilts.map((build: any) => ( 122 <Grid item xs={12} sm={6} md={3} key={build.id}> 123 <BuildCard build={build} onClick={() => handleCardClick(build)} /> 105 {data.prebuilts.slice(0, 3).map((build: any) => ( 106 <Grid item xs={12} sm={6} md={3} key={build.id} sx={{ display: 'flex' }}> 107 <Box sx={{ width: '100%' }}> 108 <BuildCard 109 build={build} 110 onClick={() => setSelectedBuildId(build.id)} 111 /> 112 </Box> 124 113 </Grid> 125 114 ))} 126 115 </Grid> 116 127 117 128 118 <SectionHeader title="Community Forge" subtitle="Fresh builds from users around the world."/> … … 130 120 {data.communityBuilds.map((build: any) => ( 131 121 <Grid item xs={12} sm={6} md={3} key={build.id}> 132 <BuildCard build={build} onClick={() => handleCardClick(build)} /> 122 <BuildCard 123 build={build} 124 onClick={() => setSelectedBuildId(build.id)} 125 /> 133 126 </Grid> 134 127 ))} … … 138 131 <Button variant="outlined" size="large" href="/completed-builds">View All Community Builds</Button> 139 132 </Box> 140 141 133 </Container> 142 134 143 <Dialog open={openDetailPopup} onClose={() => setOpenDetailPopup(false)} maxWidth="md" fullWidth>144 {selectedBuild && (145 <>146 <DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>147 <Box>148 <Typography variant="h5" fontWeight="bold">{selectedBuild.name}</Typography>149 <Typography variant="subtitle2" color="text.secondary">By {selectedBuild.username || "Unknown"}</Typography>150 </Box>151 <IconButton onClick={() => setOpenDetailPopup(false)}><CloseIcon /></IconButton>152 </DialogTitle>153 135 154 <DialogContent dividers> 155 <Grid container spacing={4}> 156 <Grid item xs={12} md={6}> 157 <Box component="img" src={selectedBuild.imageUrl || PLACEHOLDER_IMG} sx={{ width: '100%', borderRadius: 2, mb: 2 }} /> 158 <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> 159 <Rating value={Number(selectedBuild.avgRating) || 0} readOnly precision={0.5} /> 160 <Typography>({selectedBuild.ratingCount || 0} reviews)</Typography> 161 </Box> 162 </Grid> 163 <Grid item xs={12} md={6}> 164 <Typography variant="h6" gutterBottom>Build Specs</Typography> 165 <ComponentRow name="CPU" value="Intel Core i9-13900K" /> {/* Placeholder */} 166 <ComponentRow name="GPU" value="NVIDIA RTX 4090" /> {/* Placeholder */} 167 168 <Box sx={{ mt: 4, bgcolor: '#f5f5f5', p: 2, borderRadius: 1 }}> 169 <Typography variant="subtitle2" fontWeight="bold">Actions</Typography> 170 <Box sx={{ display: 'flex', gap: 1, mt: 1 }}> 171 <Button size="small" variant="outlined" startIcon={<StarIcon />}>Rate</Button> 172 <Button size="small" variant="outlined" startIcon={<CommentIcon />}>Review</Button> 173 </Box> 174 </Box> 175 </Grid> 176 </Grid> 177 </DialogContent> 178 179 <DialogActions sx={{ p: 2 }}> 180 {data.userId === selectedBuild.userId ? ( 181 <Button variant="contained" href={`/forger?edit=${selectedBuild.id}`}>Edit</Button> 182 ) : ( 183 <Button variant="contained" color="secondary" startIcon={<AutoFixHighIcon />} onClick={null}> 184 Clone & Edit 185 </Button> 186 )} 187 <Button href={`/build/${selectedBuild.id}`}>View Full Details</Button> 188 </DialogActions> 189 </> 190 )} 191 </Dialog> 136 <BuildDetailsDialog 137 open={!!selectedBuildId} 138 buildId={selectedBuildId} 139 currentUser={data.userId} 140 onClose={() => setSelectedBuildId(null)} 141 onClone={handleCloneWrapper} 142 /> 192 143 193 144 … … 199 150 <DialogContent dividers> 200 151 <Grid container spacing={3}> 201 202 152 {data.prebuilts.concat(data.communityBuilds).map((build: any) => ( 203 153 <Grid item xs={12} sm={6} md={3} key={`popup-${build.id}`}> 204 <BuildCard build={build} onClick={() => { 205 setOpenRankedPopup(false); 206 handleCardClick(build); 207 }} /> 154 <BuildCard 155 build={build} 156 onClick={() => { 157 setOpenRankedPopup(false); 158 setSelectedBuildId(build.id); // Open details from popup 159 }} 160 /> 208 161 </Grid> 209 162 ))} … … 224 177 ); 225 178 } 226 227 function BuildCard({ build, onClick }: { build: any, onClick: () => void }) {228 return (229 <Card sx={{ height: '100%', display: 'flex', flexDirection: 'column', cursor: 'pointer', transition: 'all 0.2s', '&:hover': { transform: 'translateY(-4px)', boxShadow: 6 } }} onClick={onClick}>230 <CardMedia component="img" height="160" image={build.imageUrl || PLACEHOLDER_IMG} alt={build.name} />231 <CardContent sx={{ flexGrow: 1, pb: 1 }}>232 <Typography gutterBottom variant="h6" noWrap>{build.name}</Typography>233 234 <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mt: 1 }}>235 <Box sx={{ display: 'flex', alignItems: 'center', px: 1, borderRadius: 1 }}>236 <StarIcon fontSize="small" sx={{ color: '#faaf00', mr: 0.5 }} />237 <Typography variant="body2" fontWeight="bold">{Number(build.avgRating || 0).toFixed(1)}</Typography>238 </Box>239 <Typography variant="caption" color="text.secondary">240 {build.commentCount || 0} reviews241 </Typography>242 </Box>243 </CardContent>244 </Card>245 );246 }247 248 function ComponentRow({ name, value }: { name: string, value: string }) {249 return (250 <Box sx={{ display: 'flex', justifyContent: 'space-between', py: 1, borderBottom: '1px solid #eee' }}>251 <Typography variant="body2" color="text.secondary">{name}</Typography>252 <Typography variant="body2" fontWeight="bold">{value}</Typography>253 </Box>254 );255 }
Note:
See TracChangeset
for help on using the changeset viewer.
