From 92366a678142647938b53c6f59f443562fec5e22 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 30 Jul 2026 17:30:55 +0200 Subject: [PATCH] some fixes --- src/App.css | 2 +- src/App.tsx | 2 +- src/components/PluginCard.tsx | 3 +- src/pages/AuthorProfilePage.tsx | 3 +- src/pages/Home.tsx | 4 +- src/pages/PluginDetail.tsx | 90 ++++++++++++++++++------ src/pages/ProfilePage.tsx | 3 +- src/pages/dashboard/DashboardPlugins.tsx | 3 +- src/utils/url.ts | 17 +++++ 9 files changed, 99 insertions(+), 28 deletions(-) create mode 100644 src/utils/url.ts diff --git a/src/App.css b/src/App.css index 289fb65..d7cde99 100644 --- a/src/App.css +++ b/src/App.css @@ -384,7 +384,7 @@ h3 { } .plugin-sidebar { - order: -1; + order: 1; width: 100%; margin: 0; } diff --git a/src/App.tsx b/src/App.tsx index cb628ab..7a2ea6c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -126,7 +126,7 @@ const App = () => ( {/* All routes share MainLayout (navbar + footer) */} }> } /> - } /> + } /> } /> } /> } /> diff --git a/src/components/PluginCard.tsx b/src/components/PluginCard.tsx index 90537de..bf0c668 100644 --- a/src/components/PluginCard.tsx +++ b/src/components/PluginCard.tsx @@ -1,6 +1,7 @@ import { useState, useRef, useMemo } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import { getPluginUrl } from '../utils/url'; const getAccentColor = (name: string) => { const colors = [ @@ -81,7 +82,7 @@ const PluginCard = ({ plugin }: { plugin: any }) => { }; return ( - +
{slideshowImages.length > 0 ? ( diff --git a/src/pages/AuthorProfilePage.tsx b/src/pages/AuthorProfilePage.tsx index 5bd0781..0220d29 100644 --- a/src/pages/AuthorProfilePage.tsx +++ b/src/pages/AuthorProfilePage.tsx @@ -3,6 +3,7 @@ import { useParams, Link } from 'react-router-dom'; import api from '../api'; import { useTranslation } from 'react-i18next'; import SEO from '../components/SEO'; +import { getPluginUrl } from '../utils/url'; const AuthorProfilePage = () => { const { username } = useParams(); @@ -138,7 +139,7 @@ const AuthorProfilePage = () => { const imageUrl = plugin.preview_path; return ( - +
{imageUrl ? ( diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index bf575dd..3694a9e 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -5,6 +5,8 @@ import api from '../api'; import SEO from '../components/SEO'; import PluginCard from '../components/PluginCard'; +import { getPluginUrl } from '../utils/url'; + // Global cache to persist data across component unmounts (navigation) let homeCache: { popular: any[]; @@ -120,7 +122,7 @@ const Home = () => { {activeFeatured && (
- + {/* Main visual display */}
{activeImage ? ( diff --git a/src/pages/PluginDetail.tsx b/src/pages/PluginDetail.tsx index 3d2ebd4..71a8247 100644 --- a/src/pages/PluginDetail.tsx +++ b/src/pages/PluginDetail.tsx @@ -135,7 +135,8 @@ const formatDate = (dateStr: string) => { }; const PluginDetail = () => { - const { id } = useParams(); + const { id: rawId, slug } = useParams(); + const id = rawId ? rawId.split('-')[0] : rawId; const { user } = useAuth(); const navigate = useNavigate(); const location = useLocation(); @@ -233,6 +234,15 @@ const PluginDetail = () => { } else if (res.data.screenshots?.length > 0) { setMainScreenshot(res.data.screenshots[0].path); } + + // Canonical URL check & replace + if (res.data.name) { + const expectedSlug = res.data.name.toLowerCase().trim().replace(/[\s_\.\-]+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-\-+/g, '-').replace(/^-+/, '').replace(/-+$/, ''); + const targetPath = expectedSlug ? `/plugin/${res.data.id}-${expectedSlug}` : `/plugin/${res.data.id}`; + if (location.pathname !== targetPath || slug !== expectedSlug) { + window.history.replaceState({}, '', targetPath + location.search); + } + } }); }; @@ -485,33 +495,71 @@ const PluginDetail = () => { return `Download ${sizeStr}`; })(); + const cleanDesc = currentDescription.replace(/[#*`_\[\]]/g, '').slice(0, 160).trim(); + const productSchema = { - "@context": "https://schema.org/", - "@type": "SoftwareApplication", - "name": plugin.name, - "operatingSystem": "Minecraft", - "applicationCategory": "GameApplication", - "offers": { - "@type": "Offer", - "price": plugin.type === 'paid' ? (plugin.price_cents / 100).toFixed(2) : "0.00", - "priceCurrency": "EUR" - }, - "aggregateRating": stats ? { - "@type": "AggregateRating", - "ratingValue": stats.avg, - "reviewCount": stats.total - } : undefined, - "author": { - "@type": "Person", - "name": plugin.dev_name - } + "@context": "https://schema.org", + "@graph": [ + { + "@type": "SoftwareApplication", + "name": plugin.name, + "description": cleanDesc, + "operatingSystem": "Minecraft", + "applicationCategory": "GameApplication", + "softwareVersion": plugin.version || "1.0.0", + "fileSize": plugin.file_size ? `${plugin.file_size} B` : undefined, + "image": plugin.preview_path || "https://market.pumpkinmc.org/icon.png", + "offers": { + "@type": "Offer", + "price": plugin.type === 'paid' ? (plugin.price_cents / 100).toFixed(2) : "0.00", + "priceCurrency": "EUR", + "availability": "https://schema.org/InStock" + }, + "aggregateRating": stats ? { + "@type": "AggregateRating", + "ratingValue": stats.avg, + "ratingCount": stats.total, + "bestRating": "5", + "worstRating": "1" + } : undefined, + "author": { + "@type": "Person", + "name": plugin.dev_name, + "url": `https://market.pumpkinmc.org/profile/${plugin.dev_name}` + } + }, + { + "@type": "BreadcrumbList", + "itemListElement": [ + { + "@type": "ListItem", + "position": 1, + "name": "Home", + "item": "https://market.pumpkinmc.org" + }, + { + "@type": "ListItem", + "position": 2, + "name": plugin.category || "Plugins", + "item": `https://market.pumpkinmc.org/search?category=${encodeURIComponent(plugin.category || '')}` + }, + { + "@type": "ListItem", + "position": 3, + "name": plugin.name, + "item": `https://market.pumpkinmc.org/plugin/${plugin.id}` + } + ] + } + ] }; return (
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index fec844a..4b5ae4b 100644 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -5,6 +5,7 @@ import api from '../api'; import { useAuth } from '../App'; import { getCodeList } from 'country-list'; import DeveloperOnboardingModal from '../components/DeveloperOnboardingModal'; +import { getPluginUrl } from '../utils/url'; import { User, Mail, Globe, Lock, Shield, CreditCard, BookOpen, AlertTriangle, LogOut, CheckCircle, @@ -854,7 +855,7 @@ const ProfilePage = () => { {library.map((entry, idx) => ( diff --git a/src/pages/dashboard/DashboardPlugins.tsx b/src/pages/dashboard/DashboardPlugins.tsx index a51080b..193c714 100644 --- a/src/pages/dashboard/DashboardPlugins.tsx +++ b/src/pages/dashboard/DashboardPlugins.tsx @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { Settings, Plus, ExternalLink, Sparkles } from 'lucide-react'; import { useAuth } from '../../App'; import DeveloperOnboardingModal from '../../components/DeveloperOnboardingModal'; +import { getPluginUrl } from '../../utils/url'; // --- Main Component --- const DashboardPlugins = () => { @@ -121,7 +122,7 @@ const DashboardPlugins = () => { {/* View on Marketplace */}