Changeset e599341


Ignore:
Timestamp:
12/28/25 00:07:22 (7 months ago)
Author:
Mihail <mihail2.naumov@…>
Branches:
main
Children:
9c87509
Parents:
f7c0b0d
Message:

Added frontend elements

Files:
6 edited

Legend:

Unmodified
Added
Removed
  • components/BuildCard.tsx

    rf7c0b0d re599341  
    22import { Card, CardMedia, CardContent, Typography, Box, Chip } from '@mui/material';
    33import StarIcon from '@mui/icons-material/Star';
    4 import PersonIcon from '@mui/icons-material/Person';
     4// import PersonIcon from '@mui/icons-material/Person';
    55
    66export default function BuildCard({ build, onClick }: { build: any, onClick: () => void }) {
    7     const formattedPrice = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(build.total_price || 0);
     7    const formattedPrice = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR' }).format(build.total_price || 0);
    88
    99    return (
     
    2525                {/*Ne se renderira user-ot*/}
    2626                <Box sx={{ display: 'flex', alignItems: 'center', mb: 1, color: 'text.secondary' }}>
    27                 {/*    <PersonIcon sx={{ fontSize: 16, mr: 0.5 }} />*/}
    28                 {/*    <Typography variant="caption">{build.user || 'Unknown'}</Typography>*/}
    29                     <Typography variant="h6" gutterBottom noWrap title={build.cpu}>{build.cpu}</Typography>
    30                     <Typography variant="h6" gutterBottom noWrap title={build.gpu}>{build.gpu}</Typography>
     27                    {/*    <PersonIcon sx={{ fontSize: 16, mr: 0.5 }} />*/}
     28                    {/*    <Typography variant="caption">{build.user || 'Unknown'}</Typography>*/}
    3129                </Box>
    3230
     
    3634                        <StarIcon fontSize="small" sx={{ color: '#faaf00', mr: 0.5 }} />
    3735                        <Typography variant="body2" fontWeight="bold">
    38                             {Number(build.avg_rating || 5).toFixed(1)}
     36                            {Number(build.avgRating || 5).toFixed(1)}
    3937                        </Typography>
    4038                    </Box>
  • components/BuildDetailsDialog.tsx

    rf7c0b0d re599341  
    22import {
    33    Dialog, DialogTitle, DialogContent, Button, Grid, Box, Typography,
    4     IconButton, Tab, Tabs, Table, TableBody, TableCell, TableRow, Rating, TextField, Avatar, Divider, Chip, CardMedia
     4    IconButton, Tab, Tabs, Table, TableBody, TableCell, TableRow, Rating, TextField, Avatar, Chip
    55} from '@mui/material';
    66import CloseIcon from '@mui/icons-material/Close';
     
    88import FavoriteIcon from '@mui/icons-material/Favorite';
    99import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
     10import PersonIcon from "@mui/icons-material/Person";
    1011import { onGetBuildDetails, onSetReview, onToggleFavorite } from '../pages/+Layout.telefunc';
    11 import PersonIcon from "@mui/icons-material/Person";
    1212
    1313const formatPrice = (price: any) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(Number(price) || 0);
     
    2424        if (open && buildId) {
    2525            setLoading(true);
     26            setReviewText("");
     27            setRatingVal(5);
     28
    2629            onGetBuildDetails({ buildId })
    2730                .then(data => {
     
    4346        if (!currentUser) return alert("Please login to review.");
    4447        await onSetReview({ buildId, content: reviewText });
     48
     49        setReviewText("");
    4550
    4651        const refreshed = await onGetBuildDetails({ buildId });
     
    8186                        <Box sx={{ p: 3 }}>
    8287                            {tabIndex === 0 && (
    83                                 <Grid container spacing={4}>
     88                                <Grid container spacing={2}>
    8489                                    <Grid item xs={12} md={8}>
    8590                                        <Table size="small">
     
    8893                                                    <TableRow key={comp.id}>
    8994                                                        <TableCell sx={{ width: 50 }}>
    90                                                             {/*<Avatar*/}
    91                                                             {/*    src={comp.img_url || undefined}*/}
    92                                                             {/*    variant="rounded"*/}
    93                                                             {/*    sx={{ width: 40, height: 40, bgcolor: '#ff8201' }}*/}
    94                                                             {/*>*/}
    95                                                             {/*    {comp.type?.[0]?.toUpperCase()}*/}
    96                                                             {/*</Avatar>*/}
    97                                                             {/*<PersonIcon sx={{ fontSize: 16 }} />*/}
    98                                                             <CardMedia
    99                                                                 component="img"
    100                                                                 height="50"
    101                                                                 width="50"
    102                                                                 image={"https://placehold.co/400x400?text=CPU"}
    103 
    104                                                             />
     95                                                            <Avatar
     96                                                                src={comp.img_url || undefined}
     97                                                                variant="rounded"
     98                                                                sx={{ width: 45, height: 45, bgcolor: '#ff8201'}}
     99                                                            >
     100                                                                {comp.type?.substring(0, 3)?.toUpperCase()}
     101                                                            </Avatar>
    105102                                                        </TableCell>
    106103                                                        <TableCell>
  • components/Navbar.tsx

    rf7c0b0d re599341  
    44import Typography from "@mui/material/Typography";
    55import Button from "@mui/material/Button";
    6 import InputBase from "@mui/material/InputBase";
    76import Box from "@mui/material/Box";
    8 import { styled } from "@mui/material/styles";
     7import Menu from "@mui/material/Menu";
     8import MenuItem from "@mui/material/MenuItem";
     9import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
     10import {
     11    Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, ListItemText
     12} from "@mui/material";
     13
     14import MemoryIcon from '@mui/icons-material/Memory';
     15import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard';
     16import StorageIcon from '@mui/icons-material/Storage';
     17import SdStorageIcon from '@mui/icons-material/SdStorage';
     18import RouterIcon from '@mui/icons-material/Router';
     19import LanIcon from '@mui/icons-material/Lan';
     20import SpeakerIcon from '@mui/icons-material/Speaker';
     21import AlbumIcon from '@mui/icons-material/Album';
     22import SdCardIcon from '@mui/icons-material/SdCard';
     23import CableIcon from '@mui/icons-material/Cable';
     24
    925import LogoUrl from '../assets/projectlogo.png';
    10 import {onGetAuthState} from "../pages/+Layout.telefunc";
     26import { onGetAuthState } from "../pages/+Layout.telefunc";
    1127
     28import ComponentDialog from "./ComponentDialog";
    1229
    13 type AuthState = { isLoggedIn: boolean; username: string | null };
     30type AuthState = { isLoggedIn: boolean; username: string | null; isAdmin?: boolean };
     31
     32const COMPONENT_CATEGORIES = [
     33    { id: 'cpu', label: 'Processors', icon: <MemoryIcon fontSize="small" /> },
     34    { id: 'gpu', label: 'Graphics Cards', icon: <DeveloperBoardIcon fontSize="small" /> },
     35    { id: 'motherboard', label: 'Motherboards', icon: <DeveloperBoardIcon fontSize="small" /> },
     36    { id: 'memory', label: 'Memory (RAM)', icon: <SdStorageIcon fontSize="small" /> },
     37    { id: 'storage', label: 'Storage', icon: <StorageIcon fontSize="small" /> },
     38    { id: 'case', label: 'Cases', icon: <StorageIcon fontSize="small" /> },
     39    { id: 'power_supply', label: 'Power Supplies', icon: <StorageIcon fontSize="small" /> },
     40    { id: 'cooler', label: 'Cooling', icon: <StorageIcon fontSize="small" /> },
     41
     42    // Peripherals / Accessories (Missing ones)
     43    { id: 'network_adapter', label: 'Network Adapters (WiFi)', icon: <RouterIcon fontSize="small" /> },
     44    { id: 'network_card', label: 'Network Cards (Ethernet)', icon: <LanIcon fontSize="small" /> },
     45    { id: 'sound_card', label: 'Sound Cards', icon: <SpeakerIcon fontSize="small" /> },
     46    { id: 'optical_drive', label: 'Optical Drives', icon: <AlbumIcon fontSize="small" /> },
     47    { id: 'memory_card', label: 'Memory Cards', icon: <SdCardIcon fontSize="small" /> },
     48    { id: 'cables', label: 'Cables', icon: <CableIcon fontSize="small" /> },
     49];
     50
    1451
    1552export default function Navbar() {
    1653    const [auth, setAuth] = useState<AuthState | null>(null);
     54    const [openLogoutDialog, setOpenLogoutDialog] = useState(false);
    1755
    18     const handleLogout = async (e: React.MouseEvent) => { // funkcija za logout da go resetira auth state, za da ne go pise username-ot u navbar, plus te vrakja na login nazad
     56    const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
     57    const openMenu = Boolean(anchorEl);
     58
     59    const [browserOpen, setBrowserOpen] = useState(false);
     60    const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
     61
     62    const handleComponentsClick = (event: React.MouseEvent<HTMLButtonElement>) => {
     63        setAnchorEl(event.currentTarget);
     64    };
     65
     66    const handleMenuClose = () => {
     67        setAnchorEl(null);
     68    };
     69
     70    const handleCategorySelect = (categoryId: string) => {
     71        setSelectedCategory(categoryId);
     72        setBrowserOpen(true);
     73        handleMenuClose();
     74    };
     75
     76    const handleLogoutClick = (e: React.MouseEvent) => {
    1977        e.preventDefault();
     78        setOpenLogoutDialog(true);
     79    };
    2080
    21         setAuth({ isLoggedIn: false, username: null });
    22         window.location.href = "/api/auth/signout?callbackUrl=/auth/login";
     81    const confirmLogout = async () => {
     82        setAuth({ isLoggedIn: false, username: null, isAdmin: false });
     83        setOpenLogoutDialog(false);
     84        const csrfRes = await fetch("/api/auth/csrf");
     85        const { csrfToken } = await csrfRes.json();
     86        await fetch("/api/auth/signout", {
     87            method: "POST",
     88            headers: { "Content-Type": "application/x-www-form-urlencoded" },
     89            body: new URLSearchParams({ csrfToken: csrfToken, callbackUrl: "/" }),
     90        });
     91        window.location.href = "/";
    2392    };
    2493
     
    2695        let active = true;
    2796        onGetAuthState()
    28             .then((data) => active && setAuth({ isLoggedIn: data.isLoggedIn, username: data.username }))
    29             .catch(() => active && setAuth({ isLoggedIn: false, username: null }));
     97            .then((data) => active && setAuth({
     98                isLoggedIn: data.isLoggedIn,
     99                username: data.username,
     100                isAdmin: data.isAdmin
     101            }))
     102            .catch(() => active && setAuth({ isLoggedIn: false, username: null, isAdmin: false }));
    30103        return () => { active = false; };
    31104    }, []);
    32105
     106    const checkDashboardUrl = auth?.isAdmin ? '/dashboard/admin' : '/dashboard/user';
    33107    const onHoverNav = {
    34108        color: 'inherit',
    35         '&:hover': {
    36             backgroundColor: '#ff8201',
    37             color: 'white',
    38             fontWeight: 'bold',
    39         }
     109        '&:hover': { backgroundColor: '#ff8201', color: 'white', fontWeight: 'bold' }
    40110    };
    41111
    42112    return (
    43         <AppBar position="static" color="default" enableColorOnDark>
    44             <Toolbar>
    45                 <Box sx={{ display: 'flex', alignItems: 'center', mr: 4 }}>
    46                     <Box
    47                         component="img"
    48                         src={LogoUrl}
    49                         alt="PC Forge Logo"
    50                         sx={{ height: 40, mr: 2, cursor: 'pointer' }}
    51                     />
    52                     <Typography
    53                         variant="h6"
    54                         component="a"
    55                         href="/"
    56                         sx={{ textDecoration: "none", color: "inherit", fontWeight: "bold" }}
    57                     >
    58                         PC Forge
    59                     </Typography>
    60                 </Box>
     113        <>
     114            <AppBar position="static" color="default" enableColorOnDark>
     115                <Toolbar>
     116                    <Box sx={{ display: 'flex', alignItems: 'center', mr: 4 }}>
     117                        <Box component="img" src={LogoUrl} alt="PC Forge Logo" sx={{ height: 40, mr: 2, cursor: 'pointer' }} onClick={() => window.location.href='/'} />
     118                        <Typography variant="h6" component="a" href="/" sx={{ textDecoration: "none", color: "inherit", fontWeight: "bold" }}>
     119                            PC Forge
     120                        </Typography>
     121                    </Box>
    61122
    62                 <Box sx={{ display: { xs: "none", md: "flex" }, gap: 2}}>
    63                     <Button color="inherit" href="/forge" sx={onHoverNav}>Forge</Button>
    64                     <Button color="inherit" href="/components" sx={onHoverNav}>Components</Button>
    65                     <Button color="inherit" href="/completed-builds" sx={onHoverNav}>Completed Builds</Button>
    66                 </Box>
     123                    <Box sx={{ display: { xs: "none", md: "flex" }, gap: 2 }}>
     124                        <Button color="inherit" href="/forge" sx={onHoverNav}>Forge</Button>
    67125
    68                 <Box sx={{ flexGrow: 1 }} />
     126                        <Button
     127                            color="inherit"
     128                            onClick={handleComponentsClick}
     129                            endIcon={<KeyboardArrowDownIcon />}
     130                            sx={onHoverNav}
     131                        >
     132                            Components
     133                        </Button>
     134                        <Menu
     135                            anchorEl={anchorEl}
     136                            open={openMenu}
     137                            onClose={handleMenuClose}
     138                            MenuListProps={{ 'aria-labelledby': 'basic-button' }}
     139                        >
     140                            {COMPONENT_CATEGORIES.map((cat) => (
     141                                <MenuItem key={cat.id} onClick={() => handleCategorySelect(cat.id)}>
     142                                    {/*<ListItemIcon>{cat.icon}</ListItemIcon>*/}
     143                                    <ListItemText>{cat.label}</ListItemText>
     144                                </MenuItem>
     145                            ))}
     146                        </Menu>
    69147
    70                 <Box sx={{ display: 'flex', gap: 1 }}>
    71                     {auth?.isLoggedIn ? (
    72                         <>
    73                             <Button sx={onHoverNav} color="inherit" href="/dashboard/user">{auth.username}</Button>
    74                             <Button sx={onHoverNav} color="inherit" onClick={handleLogout}>Logout</Button>
    75                         </>
    76                     ) : (
    77                         <>
    78                             <Button color="inherit" href="/auth/login">Login</Button>
    79                             <Button color="inherit" href="/auth/register">Register</Button>
    80                         </>
    81                     )}
    82                 </Box>
    83             </Toolbar>
    84         </AppBar>
     148                        <Button color="inherit" href="/completed-builds" sx={onHoverNav}>Completed Builds</Button>
     149                    </Box>
     150
     151                    <Box sx={{ flexGrow: 1 }} />
     152
     153                    <Box sx={{ display: 'flex', gap: 1 }}>
     154                        {auth?.isLoggedIn ? (
     155                            <>
     156                                <Button sx={onHoverNav} color="inherit" href={checkDashboardUrl}>{auth.username}</Button>
     157                                <Button sx={onHoverNav} color="inherit" onClick={handleLogoutClick}>Logout</Button>
     158                            </>
     159                        ) : (
     160                            <>
     161                                <Button color="inherit" href="/auth/login">Login</Button>
     162                                <Button color="inherit" href="/auth/register">Register</Button>
     163                            </>
     164                        )}
     165                    </Box>
     166                </Toolbar>
     167            </AppBar>
     168
     169            <Dialog open={openLogoutDialog} onClose={() => setOpenLogoutDialog(false)}>
     170                <DialogTitle>Confirm Logout</DialogTitle>
     171                <DialogContent>
     172                    <DialogContentText>Are you sure you want to leave the Forge?</DialogContentText>
     173                </DialogContent>
     174                <DialogActions>
     175                    <Button onClick={() => setOpenLogoutDialog(false)}>Cancel</Button>
     176                    <Button onClick={confirmLogout} color="error" variant="contained" autoFocus>Logout</Button>
     177                </DialogActions>
     178            </Dialog>
     179
     180            <ComponentDialog
     181                open={browserOpen}
     182                category={selectedCategory}
     183                onClose={() => setBrowserOpen(false)}
     184            />
     185        </>
    85186    );
    86187}
  • pages/completed-builds/+Page.tsx

    rf7c0b0d re599341  
    11import React, { useEffect, useState } from 'react';
    22import {
    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
    55} from '@mui/material';
    66import SearchIcon from '@mui/icons-material/Search';
     
    99import BuildCard from '../../components/BuildCard';
    1010import BuildDetailsDialog from '../../components/BuildDetailsDialog';
    11 
    1211import { onGetApprovedBuilds, onCloneBuild, onGetAuthState } from '../+Layout.telefunc';
    1312
     
    2726            const [userData, data] = await Promise.all([
    2827                onGetAuthState(),
    29                 onGetApprovedBuilds({ q: searchQuery })
     28                onGetApprovedBuilds({q: searchQuery})
    3029            ]);
    3130            setUserId(userData.userId);
     
    5857
    5958            setBuilds(sortedData);
     59        } catch (e) {
     60            console.error("Failed to load builds", e);
    6061        } finally {
    6162            setLoading(false);
     
    6566    useEffect(() => {
    6667        loadBuilds();
    67     }, [sortBy, priceRange, searchQuery]);
    68 
    69     useEffect(() => {
    70         loadBuilds();
    7168    }, [sortBy, searchQuery]);
    7269
     
    7471        if (!userId) return alert("Please login to clone builds!");
    7572        if (confirm(`Clone this build?`)) {
    76             await onCloneBuild({ buildId });
     73            await onCloneBuild({buildId});
    7774            alert("Build cloned!");
    7875            setSelectedBuildId(null);
     
    8178
    8279    return (
    83         <Container maxWidth="xl" sx={{ mt: 4, mb: 10 }}>
     80        <Container maxWidth={false} sx={{ mt: 4, mb: 10, px: { xs: 2, md: 4 } }}>
    8481            <Typography variant="h4" fontWeight="bold" gutterBottom>Completed Builds</Typography>
    8582            <Typography color="text.secondary" sx={{ mb: 4 }}>
     
    8784            </Typography>
    8885
    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                }}>
    9192                    <Paper variant="outlined" sx={{ p: 3, position: 'sticky', top: 20 }}>
    9293                        <Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
     
    127128                        </Button>
    128129                    </Paper>
    129                 </Grid>
    130 
    131                 <Grid item xs={12} md={9}>
    132                     {/* Toolbar */}
     130                </Box>
     131
     132                <Box sx={{ flexGrow: 1 }}>
    133133                    <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
    134134                        <Typography fontWeight="bold">{builds.length} Builds Found</Typography>
     
    152152
    153153                    {loading ? (
    154                         <Box sx={{ p: 5, textAlign: 'center' }}>Loading...</Box>
     154                        <Box sx={{ p: 5, textAlign: 'center' }}>
     155                            <CircularProgress />
     156                        </Box>
    155157                    ) : (
    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                        >
    157172                            {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                                />
    166178                            ))}
    167179                            {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>
    173183                            )}
    174                         </Grid>
     184                        </Box>
    175185                    )}
    176                 </Grid>
    177             </Grid>
    178 
     186                </Box>
     187            </Box>
     188
     189            {/* @ts-ignore */}
    179190            <BuildDetailsDialog
    180191                open={!!selectedBuildId}
     
    186197        </Container>
    187198    );
     199
    188200}
    189 
  • pages/dashboard/user/+Page.tsx

    rf7c0b0d re599341  
    99    Button,
    1010    CircularProgress,
    11     Card,
    12     CardContent,
    13     CardActionArea,
    14     IconButton
     11    IconButton,
     12    Grid,
     13    Dialog,
     14    DialogTitle,
     15    DialogContent,
     16    DialogActions
    1517} from '@mui/material';
    16 import Grid from '@mui/material/Grid';
    17 import {Dialog, DialogTitle, DialogContent, DialogActions} from '@mui/material';
    1818import CloseIcon from '@mui/icons-material/Close';
    1919
     
    2121import ComputerIcon from '@mui/icons-material/Computer';
    2222import FavoriteIcon from '@mui/icons-material/Favorite';
    23 import DeleteIcon from '@mui/icons-material/Delete';
    24 import EditIcon from '@mui/icons-material/Edit';
    2523
    2624import {getUserInfoAndData, onDeleteBuild} from "./userDashboard.telefunc";
     25import {onCloneBuild} from "../../+Layout.telefunc";
     26
     27import BuildCard from "../../../components/BuildCard";
     28import BuildDetailsDialog from "../../../components/BuildDetailsDialog";
    2729
    2830type DashboardData = {
     
    3638    const [loading, setLoading] = useState(true);
    3739    const [error, setError] = useState<string | null>(null);
     40
    3841    const [openMyBuildsDialog, setOpenMyBuildsDialog] = useState(false);
     42    const [openFavoritesDialog, setOpenFavoritesDialog] = useState(false);
     43
     44    const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null);
    3945
    4046    const loadData = () => {
     
    6066            await onDeleteBuild({buildId});
    6167            loadData();
     68            setSelectedBuildId(null);
    6269        } catch (e) {
    6370            alert("Failed to delete build");
     
    6572    };
    6673
     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
    6784    if (loading) return <Box sx={{display: 'flex', justifyContent: 'center', mt: 10}}><CircularProgress/></Box>;
    6885    if (error || !data) return <Container sx={{mt: 5, textAlign: 'center'}}><Typography color="error"
    6986                                                                                        variant="h6">{error}</Typography><Button
    7087        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
    7390    return (
    7491        <Container maxWidth="xl" sx={{mt: 4, mb: 4, color: 'white'}}>
    7592            <Grid container spacing={3}>
    76 
    7793                <Grid item xs={12} md={3}>
    7894                    <Paper elevation={3}
     
    8399                        <Typography variant="body2" color="text.secondary" gutterBottom>{data.user.email}</Typography>
    84100                        <Divider sx={{width: '100%', my: 2}}/>
    85                         {/*<Button variant="outlined" fullWidth color="primary" sx={{mb: 1}}>Edit Profile</Button>*/}
    86101                    </Paper>
    87102                </Grid>
     
    92107                        <Grid item xs={12}>
    93108                            <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                                    )}
    97129                                </Box>
    98130                                <Divider sx={{mb: 2}}/>
     
    103135                                ) : (
    104136                                    <Grid container spacing={2}>
    105                                         {data.favoriteBuilds.map((build: any) => (
     137                                        {data.favoriteBuilds.slice(0, 4).map((build: any) => (
    106138                                            <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                                                />
    108143                                            </Grid>
    109144                                        ))}
     
    148183                                ) : (
    149184                                    <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}>
    152187                                                <BuildCard
    153188                                                    build={build}
    154                                                     onDelete={() => handleDelete(build.id)}
    155                                                     isOwner={true}
     189                                                    onClick={() => setSelectedBuildId(build.id)}
    156190                                                />
    157191                                            </Grid>
     
    162196                        </Grid>
    163197
    164                         <Dialog open={openMyBuildsDialog} onClose={() => setOpenMyBuildsDialog(false)} maxWidth="md"
     198                        <Dialog open={openMyBuildsDialog} onClose={() => setOpenMyBuildsDialog(false)} maxWidth="lg"
    165199                                fullWidth>
    166                             <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>All
    167                                 My Builds
     200                            <DialogTitle sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center'}}>
     201                                All My Builds
    168202                                <IconButton onClick={() => setOpenMyBuildsDialog(false)}>
    169203                                    <CloseIcon/>
     
    176210                                            <BuildCard
    177211                                                build={build}
    178                                                 onDelete={() => handleDelete(build.id)}
    179                                                 isOwner={true}
     212                                                onClick={() => {
     213                                                    setOpenMyBuildsDialog(false);
     214                                                    setSelectedBuildId(build.id);
     215                                                }}
    180216                                            />
    181217                                        </Grid>
     
    183219                                </Grid>
    184220                            </DialogContent>
    185 
    186221                            <DialogActions>
    187222                                <Button onClick={() => setOpenMyBuildsDialog(false)}>Close</Button>
     
    189224                        </Dialog>
    190225
     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
    191254                    </Grid>
    192255                </Grid>
    193256            </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            />
    194268        </Container>
    195269    );
    196270}
    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  
    1414export default function HomePage() {
    1515    const [data, setData] = useState<any>(null);
     16    const [allRanked, setAllRanked] = useState<any[]>([]);
    1617
    1718    const [selectedBuildId, setSelectedBuildId] = useState<number | null>(null);
    18 
    1919    const [openRankedPopup, setOpenRankedPopup] = useState(false);
    2020
     
    2323            try {
    2424                const [
    25                     authData, highestRankedBuilds, communityBuilds
     25                    authData,
     26                    highestRankedBuilds,
     27                    communityBuilds,
     28                    fullRankedList
    2629                ] = await Promise.all([
    2730                    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' })
    3034                ]);
    3135
     
    3640                    communityBuilds: communityBuilds
    3741                });
     42                setAllRanked(fullRankedList);
    3843            } catch (error) {
    3944                console.error("Error loading homepage data:", error);
     
    6974                        Build, share, discuss, and discover custom PC configurations.
    7075                    </Typography>
    71                     <Button variant="contained" size="large" href="/forger" startIcon={<AutoFixHighIcon />}
     76                    <Button variant="contained" size="large" href="/forge" startIcon={<AutoFixHighIcon />}
    7277                            sx={{ fontSize: '1.2rem', px: 4, py: 1.5, mt: 2 }}>
    7378                        Start Forging
     
    9297                </Box>
    9398
    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' }}>
    105100                    {data.prebuilts.slice(0, 3).map((build: any) => (
    106101                        <Grid item xs={12} sm={6} md={3} key={build.id} sx={{ display: 'flex' }}>
     
    114109                    ))}
    115110                </Grid>
    116 
    117111
    118112                <SectionHeader title="Community Forge" subtitle="Fresh builds from users around the world."/>
     
    133127            </Container>
    134128
    135 
    136129            <BuildDetailsDialog
    137130                open={!!selectedBuildId}
     
    142135            />
    143136
    144 
    145             <Dialog open={openRankedPopup} onClose={() => setOpenRankedPopup(false)} maxWidth="lg" fullWidth>
     137            <Dialog open={openRankedPopup} onClose={() => setOpenRankedPopup(false)} maxWidth="lg" fullWidth scroll="paper">
    146138                <DialogTitle sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
    147                     Hall of Fame (All Top Ranked)
     139                    Hall of Fame (Top Rated)
    148140                    <IconButton onClick={() => setOpenRankedPopup(false)}><CloseIcon /></IconButton>
    149141                </DialogTitle>
    150142                <DialogContent dividers>
    151143                    <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>
    161163                            </Grid>
    162164                        ))}
     
    171173function SectionHeader({ title, subtitle }: { title: string, subtitle: string }) {
    172174    return (
    173         <Box sx={{ mb: 4, borderLeft: '5px solid #1976d2', pl: 2 }}>
     175        <Box sx={{ mb: 4, borderLeft: '5px solid #ff8201', pl: 2 }}>
    174176            <Typography variant="h4" fontWeight="bold" color="text.primary">{title}</Typography>
    175177            <Typography variant="subtitle1" color="text.secondary">{subtitle}</Typography>
Note: See TracChangeset for help on using the changeset viewer.