source: pages/index/+Page.tsx@ 67dfe57

main
Last change on this file since 67dfe57 was 67dfe57, checked in by Tome <gjorgievtome@…>, 7 months ago

Fix AuthState

  • Property mode set to 100644
File size: 12.6 KB
Line 
1import React, { useEffect, useState } from 'react';
2import {
3 Container, Box, Typography, Button, Grid, Card, CardMedia, CardContent,
4 CardActions, Chip, Rating, Dialog, DialogTitle, DialogContent, DialogActions,
5 IconButton, Divider
6} from '@mui/material';
7import CloseIcon from '@mui/icons-material/Close';
8import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
9import CommentIcon from '@mui/icons-material/Comment';
10import StarIcon from '@mui/icons-material/Star';
11import HardwareIcon from '@mui/icons-material/Memory';
12import ListIcon from '@mui/icons-material/List'; // Icon for "Show All"
13
14// --- PLACEHOLDER ASSETS ---
15const PLACEHOLDER_IMG = "https://placehold.co/600x400?text=Gaming+PC";
16
17import { onGetHighestRankedBuilds, onCloneBuild } from './buildCards.telefunc';
18import {onGetApprovedBuilds, onGetAuthState} from '../+Layout.telefunc';
19import {getAuthState} from "../../server/telefunc/ctx"; //
20
21export default function HomePage() {
22 const [data, setData] = useState<any>(null);
23 const [selectedBuild, setSelectedBuild] = useState<any>(null); // For Detail Popup
24 const [openDetailPopup, setOpenDetailPopup] = useState(false);
25
26 // NEW: Popup for "All Top Ranked Builds"
27 const [openRankedPopup, setOpenRankedPopup] = useState(false);
28
29 useEffect(() => {
30 async function loadSite(){
31 try{
32 const [
33 authData, highestRankedBuilds, commnityBuilds
34 ] = await Promise.all([
35 onGetAuthState(),
36 onGetHighestRankedBuilds({ limit: 3 }),
37 onGetApprovedBuilds({ limit: 12 })
38 ]);
39
40 setData({
41 isLoggedIn: authData.isLoggedIn,
42 userId: authData.userId,
43 prebuilts: highestRankedBuilds,
44 communityBuilds: commnityBuilds
45 });
46 } catch (error) {
47 console.error("Error loading homepage data:", error);
48 }
49 }
50
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 () => {
61 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 });
64 alert("Build cloned! Check your dashboard.");
65 setOpenDetailPopup(false);
66 }
67 };
68
69 if (!data) return <Box sx={{ p: 10, textAlign: 'center' }}>Loading Forge...</Box>;
70
71 return (
72 <Box>
73 <Box sx={{
74 position: 'relative', height: '500px',
75 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)' }
77 }}>
78 <Box sx={{ position: 'relative', textAlign: 'center', zIndex: 1, p: 2 }}>
79 <Typography variant="h2" fontWeight="bold" gutterBottom>Forge Your Ultimate Machine</Typography>
80 <Typography variant="h5" sx={{ maxWidth: '800px', mx: 'auto' }}>
81 Build, share, discuss, and discover custom PC configurations.
82 </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 }}>
88 Start Forging
89 </Button>
90 </Box>
91 </Box>
92
93 <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' }}>
95 <Box>
96 <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>
98 </Box>
99 <Button
100 variant="outlined"
101 startIcon={<ListIcon />}
102 onClick={() => setOpenRankedPopup(true)}
103 >
104 Show All Top Ranked
105 </Button>
106 </Box>
107
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>
120
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)} />
124 </Grid>
125 ))}
126 </Grid>
127
128 <SectionHeader title="Community Forge" subtitle="Fresh builds from users around the world."/>
129 <Grid container spacing={3}>
130 {data.communityBuilds.map((build: any) => (
131 <Grid item xs={12} sm={6} md={3} key={build.id}>
132 <BuildCard build={build} onClick={() => handleCardClick(build)} />
133 </Grid>
134 ))}
135 </Grid>
136
137 <Box sx={{ display: 'flex', justifyContent: 'center', mt: 6 }}>
138 <Button variant="outlined" size="large" href="/completed-builds">View All Community Builds</Button>
139 </Box>
140
141 </Container>
142
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
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>
192
193
194 <Dialog open={openRankedPopup} onClose={() => setOpenRankedPopup(false)} maxWidth="lg" fullWidth>
195 <DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
196 Hall of Fame (All Top Ranked)
197 <IconButton onClick={() => setOpenRankedPopup(false)}><CloseIcon /></IconButton>
198 </DialogTitle>
199 <DialogContent dividers>
200 <Grid container spacing={3}>
201
202 {data.prebuilts.concat(data.communityBuilds).map((build: any) => (
203 <Grid item xs={12} sm={6} md={3} key={`popup-${build.id}`}>
204 <BuildCard build={build} onClick={() => {
205 setOpenRankedPopup(false);
206 handleCardClick(build);
207 }} />
208 </Grid>
209 ))}
210 </Grid>
211 </DialogContent>
212 </Dialog>
213
214 </Box>
215 );
216}
217
218function SectionHeader({ title, subtitle }: { title: string, subtitle: string }) {
219 return (
220 <Box sx={{ mb: 4, borderLeft: '5px solid #1976d2', pl: 2 }}>
221 <Typography variant="h4" fontWeight="bold" color="text.primary">{title}</Typography>
222 <Typography variant="subtitle1" color="text.secondary">{subtitle}</Typography>
223 </Box>
224 );
225}
226
227function 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} reviews
241 </Typography>
242 </Box>
243 </CardContent>
244 </Card>
245 );
246}
247
248function 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 TracBrowser for help on using the repository browser.