chore: change home sorting

This commit is contained in:
Alexander Medvedev 2026-05-15 08:50:18 +02:00
parent 1de42e60bf
commit 56bcb62222
4 changed files with 110 additions and 22 deletions

View File

@ -553,6 +553,14 @@ h3 {
}
/* --- Plugin Grid --- */
.home-section {
margin-bottom: 4rem;
}
.home-section:last-child {
margin-bottom: 0;
}
.plugin-grid,
.home-plugin-grid {
display: grid;

View File

@ -1,5 +1,5 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route, Link, NavLink, useNavigate, Outlet, Navigate } from 'react-router-dom';
import { BrowserRouter as Router, Routes, Route, Link, NavLink, useNavigate, Outlet, Navigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import Home from './pages/Home';
import PluginDetail from './pages/PluginDetail';
@ -25,6 +25,15 @@ import './App.css';
import api from './api';
// --- Scroll To Top Helper ---
const ScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
};
// --- Auth Context ---
const AuthContext = createContext<any>(null);
export const useAuth = () => useContext(AuthContext);
@ -112,6 +121,7 @@ const AdminRoute = ({ children }: { children: React.ReactNode }) => {
const App = () => (
<Router>
<AuthProvider>
<ScrollToTop />
<Routes>
{/* All routes share MainLayout (navbar + footer) */}
<Route path="/" element={<MainLayout />}>
@ -211,7 +221,10 @@ const Navbar = ({ user }: any) => {
<div className={`nav-content ${isMenuOpen ? 'open' : ''}`}>
<div className="nav-links">
<NavLink to="/" onClick={() => setIsMenuOpen(false)}>{t('home')}</NavLink>
{user && <NavLink to="/dashboard" onClick={() => setIsMenuOpen(false)}>{t('dashboard')}</NavLink>}
{user && (user.plugin_count > 0 || user.role === 'admin') && (
<NavLink to="/dashboard" onClick={() => setIsMenuOpen(false)}>{t('dashboard')}</NavLink>
)}
{user && <NavLink to="/dashboard/add-plugin" onClick={() => setIsMenuOpen(false)}>Publish</NavLink>}
{user && user.role === 'admin' && <NavLink to="/admin" onClick={() => setIsMenuOpen(false)}>Admin</NavLink>}
</div>

View File

@ -4,24 +4,64 @@ import api from '../api';
import SEO from '../components/SEO';
import PluginCard from '../components/PluginCard';
// Global cache to persist data across component unmounts (navigation)
// This allows the page to render immediately when going "back",
// which is required for the browser to restore scroll position.
let homeCache: {
topPaid: any[];
popular: any[];
newest: any[];
} | null = null;
const Home = () => {
const { t } = useTranslation();
const [plugins, setPlugins] = useState<any[]>([]);
const hasFetched = useRef(false);
const [topPaid, setTopPaid] = useState<any[]>(homeCache?.topPaid || []);
const [popular, setPopular] = useState<any[]>(homeCache?.popular || []);
const [newest, setNewest] = useState<any[]>(homeCache?.newest || []);
const [loading, setLoading] = useState(!homeCache);
const hasFetched = useRef(!!homeCache);
useEffect(() => {
if (hasFetched.current) return;
api.get('/plugins', { params: { q: '', sort: 'newest' } })
.then(res => {
setPlugins(Array.isArray(res.data) ? res.data : []);
const fetchSections = async () => {
try {
const [topPaidRes, popularRes, newestRes] = await Promise.all([
api.get('/plugins', { params: { type: 'paid', sort: 'downloads', limit: 4 } }),
api.get('/plugins', { params: { sort: 'downloads', limit: 8 } }),
api.get('/plugins', { params: { sort: 'newest', limit: 12 } })
]);
const data = {
topPaid: Array.isArray(topPaidRes.data) ? topPaidRes.data : [],
popular: Array.isArray(popularRes.data) ? popularRes.data : [],
newest: Array.isArray(newestRes.data) ? newestRes.data : []
};
setTopPaid(data.topPaid);
setPopular(data.popular);
setNewest(data.newest);
homeCache = data;
hasFetched.current = true;
})
.catch(err => {
} catch (err) {
console.error('Fetch error:', err);
setPlugins([]);
});
} finally {
setLoading(false);
}
};
fetchSections();
}, []);
if (loading) {
return (
<div className="loading-state">
<div className="spinner"></div>
<p>Discovering best plugins...</p>
</div>
);
}
return (
<>
<SEO
@ -35,17 +75,40 @@ const Home = () => {
</section>
<div className="container" id="browse">
<div className="home-plugin-grid">
{plugins.map((plugin) => (
<PluginCard key={plugin.id} plugin={plugin} />
))}
{plugins.length === 0 && (
<div className="empty-state">
<p>No plugins found. Check back later!</p>
{topPaid.length > 0 && (
<section className="home-section">
<h2 className="section-title"><span>Top</span> Premium</h2>
<div className="home-plugin-grid">
{topPaid.map((plugin) => (
<PluginCard key={`top-${plugin.id}`} plugin={plugin} />
))}
</div>
)}
</div>
</section>
)}
<section className="home-section">
<h2 className="section-title"><span>Most</span> Popular</h2>
<div className="home-plugin-grid">
{popular.map((plugin) => (
<PluginCard key={`pop-${plugin.id}`} plugin={plugin} />
))}
</div>
</section>
<section className="home-section">
<h2 className="section-title"><span>New</span> & Trending</h2>
<div className="home-plugin-grid">
{newest.map((plugin) => (
<PluginCard key={`new-${plugin.id}`} plugin={plugin} />
))}
</div>
</section>
{topPaid.length === 0 && popular.length === 0 && newest.length === 0 && (
<div className="empty-state">
<p>No plugins found. Check back later!</p>
</div>
)}
</div>
</>
);

View File

@ -191,7 +191,7 @@ const LangPicker = ({ usedCodes, onAdd, onClose }: { usedCodes: string[]; onAdd:
// ── Main ──────────────────────────────────────────────────────────────────────
const AddPlugin = () => {
const navigate = useNavigate();
const { user } = useAuth();
const { user, refreshUser } = useAuth();
const [step, setStep] = useState(0);
const [submitting, setSubmitting] = useState(false);
const [done, setDone] = useState(false);
@ -362,6 +362,10 @@ const AddPlugin = () => {
if (wasmFile) fd.append('wasm', wasmFile);
screenshots.forEach(f => fd.append('screenshots', f));
await api.post('/plugins', fd);
// Refresh user to update plugin_count in JWT for Navbar
await refreshUser();
setDone(true);
} catch (err: any) {
console.error(err);