From 56bcb62222710fe0e4b4aae3ddaa3eb95c9067cd Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Fri, 15 May 2026 08:50:18 +0200 Subject: [PATCH] chore: change home sorting --- src/App.css | 8 ++ src/App.tsx | 17 +++- src/pages/Home.tsx | 101 ++++++++++++++++++----- src/pages/dashboard/plugin/AddPlugin.tsx | 6 +- 4 files changed, 110 insertions(+), 22 deletions(-) diff --git a/src/App.css b/src/App.css index b148921..178a1a2 100644 --- a/src/App.css +++ b/src/App.css @@ -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; diff --git a/src/App.tsx b/src/App.tsx index 8c9c973..32c210c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -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(null); export const useAuth = () => useContext(AuthContext); @@ -112,6 +121,7 @@ const AdminRoute = ({ children }: { children: React.ReactNode }) => { const App = () => ( + {/* All routes share MainLayout (navbar + footer) */} }> @@ -211,7 +221,10 @@ const Navbar = ({ user }: any) => {
setIsMenuOpen(false)}>{t('home')} - {user && setIsMenuOpen(false)}>{t('dashboard')}} + {user && (user.plugin_count > 0 || user.role === 'admin') && ( + setIsMenuOpen(false)}>{t('dashboard')} + )} + {user && setIsMenuOpen(false)}>Publish} {user && user.role === 'admin' && setIsMenuOpen(false)}>Admin}
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index e509c40..0ae93c0 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -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([]); - const hasFetched = useRef(false); + const [topPaid, setTopPaid] = useState(homeCache?.topPaid || []); + const [popular, setPopular] = useState(homeCache?.popular || []); + const [newest, setNewest] = useState(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 ( +
+
+

Discovering best plugins...

+
+ ); + } + return ( <> {
-
- {plugins.map((plugin) => ( - - ))} - - {plugins.length === 0 && ( -
-

No plugins found. Check back later!

+ {topPaid.length > 0 && ( +
+

Top Premium

+
+ {topPaid.map((plugin) => ( + + ))}
- )} -
+ + )} + +
+

Most Popular

+
+ {popular.map((plugin) => ( + + ))} +
+
+ +
+

New & Trending

+
+ {newest.map((plugin) => ( + + ))} +
+
+ + {topPaid.length === 0 && popular.length === 0 && newest.length === 0 && ( +
+

No plugins found. Check back later!

+
+ )}
); diff --git a/src/pages/dashboard/plugin/AddPlugin.tsx b/src/pages/dashboard/plugin/AddPlugin.tsx index aa35352..3fa7baf 100644 --- a/src/pages/dashboard/plugin/AddPlugin.tsx +++ b/src/pages/dashboard/plugin/AddPlugin.tsx @@ -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);