Changeset e599341
- Timestamp:
- 12/28/25 00:07:22 (7 months ago)
- Branches:
- main
- Children:
- 9c87509
- Parents:
- f7c0b0d
- Files:
-
- 6 edited
-
components/BuildCard.tsx (modified) (3 diffs)
-
components/BuildDetailsDialog.tsx (modified) (6 diffs)
-
components/Navbar.tsx (modified) (2 diffs)
-
pages/completed-builds/+Page.tsx (modified) (11 diffs)
-
pages/dashboard/user/+Page.tsx (modified) (13 diffs)
-
pages/index/+Page.tsx (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
-
components/BuildCard.tsx
rf7c0b0d re599341 2 2 import { Card, CardMedia, CardContent, Typography, Box, Chip } from '@mui/material'; 3 3 import StarIcon from '@mui/icons-material/Star'; 4 import PersonIcon from '@mui/icons-material/Person';4 // import PersonIcon from '@mui/icons-material/Person'; 5 5 6 6 export 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); 8 8 9 9 return ( … … 25 25 {/*Ne se renderira user-ot*/} 26 26 <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>*/} 31 29 </Box> 32 30 … … 36 34 <StarIcon fontSize="small" sx={{ color: '#faaf00', mr: 0.5 }} /> 37 35 <Typography variant="body2" fontWeight="bold"> 38 {Number(build.avg _rating || 5).toFixed(1)}36 {Number(build.avgRating || 5).toFixed(1)} 39 37 </Typography> 40 38 </Box> -
components/BuildDetailsDialog.tsx
rf7c0b0d re599341 2 2 import { 3 3 Dialog, DialogTitle, DialogContent, Button, Grid, Box, Typography, 4 IconButton, Tab, Tabs, Table, TableBody, TableCell, TableRow, Rating, TextField, Avatar, Divider, Chip, CardMedia4 IconButton, Tab, Tabs, Table, TableBody, TableCell, TableRow, Rating, TextField, Avatar, Chip 5 5 } from '@mui/material'; 6 6 import CloseIcon from '@mui/icons-material/Close'; … … 8 8 import FavoriteIcon from '@mui/icons-material/Favorite'; 9 9 import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder'; 10 import PersonIcon from "@mui/icons-material/Person"; 10 11 import { onGetBuildDetails, onSetReview, onToggleFavorite } from '../pages/+Layout.telefunc'; 11 import PersonIcon from "@mui/icons-material/Person";12 12 13 13 const formatPrice = (price: any) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(Number(price) || 0); … … 24 24 if (open && buildId) { 25 25 setLoading(true); 26 setReviewText(""); 27 setRatingVal(5); 28 26 29 onGetBuildDetails({ buildId }) 27 30 .then(data => { … … 43 46 if (!currentUser) return alert("Please login to review."); 44 47 await onSetReview({ buildId, content: reviewText }); 48 49 setReviewText(""); 45 50 46 51 const refreshed = await onGetBuildDetails({ buildId }); … … 81 86 <Box sx={{ p: 3 }}> 82 87 {tabIndex === 0 && ( 83 <Grid container spacing={ 4}>88 <Grid container spacing={2}> 84 89 <Grid item xs={12} md={8}> 85 90 <Table size="small"> … … 88 93 <TableRow key={comp.id}> 89 94 <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> 105 102 </TableCell> 106 103 <TableCell> -
components/Navbar.tsx
rf7c0b0d re599341 4 4 import Typography from "@mui/material/Typography"; 5 5 import Button from "@mui/material/Button"; 6 import InputBase from "@mui/material/InputBase";7 6 import Box from "@mui/material/Box"; 8 import { styled } from "@mui/material/styles"; 7 import Menu from "@mui/material/Menu"; 8 import MenuItem from "@mui/material/MenuItem"; 9 import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; 10 import { 11 Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, ListItemText 12 } from "@mui/material"; 13 14 import MemoryIcon from '@mui/icons-material/Memory'; 15 import DeveloperBoardIcon from '@mui/icons-material/DeveloperBoard'; 16 import StorageIcon from '@mui/icons-material/Storage'; 17 import SdStorageIcon from '@mui/icons-material/SdStorage'; 18 import RouterIcon from '@mui/icons-material/Router'; 19 import LanIcon from '@mui/icons-material/Lan'; 20 import SpeakerIcon from '@mui/icons-material/Speaker'; 21 import AlbumIcon from '@mui/icons-material/Album'; 22 import SdCardIcon from '@mui/icons-material/SdCard'; 23 import CableIcon from '@mui/icons-material/Cable'; 24 9 25 import LogoUrl from '../assets/projectlogo.png'; 10 import { onGetAuthState} from "../pages/+Layout.telefunc";26 import { onGetAuthState } from "../pages/+Layout.telefunc"; 11 27 28 import ComponentDialog from "./ComponentDialog"; 12 29 13 type AuthState = { isLoggedIn: boolean; username: string | null }; 30 type AuthState = { isLoggedIn: boolean; username: string | null; isAdmin?: boolean }; 31 32 const 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 14 51 15 52 export default function Navbar() { 16 53 const [auth, setAuth] = useState<AuthState | null>(null); 54 const [openLogoutDialog, setOpenLogoutDialog] = useState(false); 17 55 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) => { 19 77 e.preventDefault(); 78 setOpenLogoutDialog(true); 79 }; 20 80 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 = "/"; 23 92 }; 24 93 … … 26 95 let active = true; 27 96 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 })); 30 103 return () => { active = false; }; 31 104 }, []); 32 105 106 const checkDashboardUrl = auth?.isAdmin ? '/dashboard/admin' : '/dashboard/user'; 33 107 const onHoverNav = { 34 108 color: 'inherit', 35 '&:hover': { 36 backgroundColor: '#ff8201', 37 color: 'white', 38 fontWeight: 'bold', 39 } 109 '&:hover': { backgroundColor: '#ff8201', color: 'white', fontWeight: 'bold' } 40 110 }; 41 111 42 112 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> 61 122 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> 67 125 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> 69 147 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 </> 85 186 ); 86 187 } -
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.
