Changeset 1bf6e1f for pages/index


Ignore:
Timestamp:
12/24/25 23:58:00 (7 months ago)
Author:
Mihail <mihail2.naumov@…>
Branches:
main
Children:
5c079f4
Parents:
76b980b
Message:

Implemented part of the frontend (index, user, completedBuilds).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • pages/index/+Page.tsx

    r76b980b r1bf6e1f  
    11import React, { useEffect, useState } from 'react';
    22import {
    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
    64} from '@mui/material';
    75import CloseIcon from '@mui/icons-material/Close';
    86import 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"
     7import ListIcon from '@mui/icons-material/List';
    138
    14 // --- PLACEHOLDER ASSETS ---
    15 const PLACEHOLDER_IMG = "https://placehold.co/600x400?text=Gaming+PC";
     9import BuildDetailsDialog from '../../components/BuildDetailsDialog';
     10import BuildCard from '../../components/BuildCard';
    1611
    17 import { onGetHighestRankedBuilds, onCloneBuild } from './buildCards.telefunc';
    18 import {onGetApprovedBuilds, onGetAuthState} from '../+Layout.telefunc';
    19 import {getAuthState} from "../../server/telefunc/ctx"; //
     12import { onGetApprovedBuilds, onGetAuthState, onCloneBuild } from '../+Layout.telefunc';
    2013
    2114export default function HomePage() {
    2215    const [data, setData] = useState<any>(null);
    23     const [selectedBuild, setSelectedBuild] = useState<any>(null); // For Detail Popup
    24     const [openDetailPopup, setOpenDetailPopup] = useState(false);
    2516
    26     // NEW: Popup for "All Top Ranked Builds"
     17    const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null);
     18
    2719    const [openRankedPopup, setOpenRankedPopup] = useState(false);
    2820
    2921    useEffect(() => {
    30         async function loadSite(){
    31             try{
     22        async function loadSite() {
     23            try {
    3224                const [
    33                     authData, highestRankedBuilds, commnityBuilds
     25                    authData, highestRankedBuilds, communityBuilds
    3426                ] = await Promise.all([
    3527                    onGetAuthState(),
    36                     onGetHighestRankedBuilds({ limit: 3 }),
     28                    onGetApprovedBuilds({ limit: 3 , sort: 'rating_desc' }),
    3729                    onGetApprovedBuilds({ limit: 12 })
    3830                ]);
     
    4234                    userId: authData.userId,
    4335                    prebuilts: highestRankedBuilds,
    44                     communityBuilds: commnityBuilds
     36                    communityBuilds: communityBuilds
    4537                });
    4638            } catch (error) {
    4739                console.error("Error loading homepage data:", error);
    4840            }
    49     }
     41        }
     42        loadSite();
     43    }, []);
    5044
    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) => {
    6146        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 });
    6449            alert("Build cloned! Check your dashboard.");
    65             setOpenDetailPopup(false);
     50            setSelectedBuildId(null);
    6651        }
    6752    };
     
    7459                position: 'relative', height: '500px',
    7560                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                }
    7765            }}>
    7866                <Box sx={{ position: 'relative', textAlign: 'center', zIndex: 1, p: 2 }}>
    7967                    <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 }}>
    8169                        Build, share, discuss, and discover custom PC configurations.
    8270                    </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 }}>
    8873                        Start Forging
    8974                    </Button>
     
    9277
    9378            <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                }}>
    9583                    <Box>
    9684                        <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>
    9888                    </Box>
    99                     <Button
    100                         variant="outlined"
    101                         startIcon={<ListIcon />}
    102                         onClick={() => setOpenRankedPopup(true)}
    103                     >
     89                    <Button variant="outlined" startIcon={<ListIcon />} onClick={() => setOpenRankedPopup(true)}>
    10490                        Show All Top Ranked
    10591                    </Button>
    10692                </Box>
    10793
    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>*/}
    120104
    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>
    124113                        </Grid>
    125114                    ))}
    126115                </Grid>
     116
    127117
    128118                <SectionHeader title="Community Forge" subtitle="Fresh builds from users around the world."/>
     
    130120                    {data.communityBuilds.map((build: any) => (
    131121                        <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                            />
    133126                        </Grid>
    134127                    ))}
     
    138131                    <Button variant="outlined" size="large" href="/completed-builds">View All Community Builds</Button>
    139132                </Box>
    140 
    141133            </Container>
    142134
    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>
    153135
    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            />
    192143
    193144
     
    199150                <DialogContent dividers>
    200151                    <Grid container spacing={3}>
    201 
    202152                        {data.prebuilts.concat(data.communityBuilds).map((build: any) => (
    203153                            <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                                />
    208161                            </Grid>
    209162                        ))}
     
    224177    );
    225178}
    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} reviews
    241                     </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.