chore: change home sorting
This commit is contained in:
parent
1de42e60bf
commit
56bcb62222
@ -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;
|
||||
|
||||
17
src/App.tsx
17
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<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>
|
||||
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user