From 35e5a8d56b1c2870f965983327c616a6ed27b92b Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 11 Jun 2026 20:37:59 +0200 Subject: [PATCH] feat: add some more admin stuff --- src/App.tsx | 11 ++ src/pages/admin/AdminPanel.css | 43 ++++++++ src/pages/admin/AdminPanel.tsx | 125 +++++++++++++++------ src/pages/legal/GuidelinesPage.tsx | 171 +++++++++++++++++++++++++++++ src/pages/legal/LegalPages.css | 119 ++++++++++++++++++++ 5 files changed, 435 insertions(+), 34 deletions(-) create mode 100644 src/pages/legal/GuidelinesPage.tsx create mode 100644 src/pages/legal/LegalPages.css diff --git a/src/App.tsx b/src/App.tsx index 32c210c..91de5da 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,6 +20,7 @@ import ManagePlugin from './pages/dashboard/plugin/ManagePlugin'; import AddPlugin from './pages/dashboard/plugin/AddPlugin'; import TermsOfServicePage from './pages/legal/TermsOfServicePage'; import PrivacyPolicyPage from './pages/legal/PrivacyPolicyPage'; +import GuidelinesPage from './pages/legal/GuidelinesPage'; import AdminPanel from './pages/admin/AdminPanel'; import './App.css'; @@ -131,6 +132,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> @@ -305,6 +307,15 @@ const Footer = () => ( > Privacy + + (e.currentTarget.style.opacity = '0.9')} + onMouseLeave={e => (e.currentTarget.style.opacity = '0.4')} + > + Guidelines + ); diff --git a/src/pages/admin/AdminPanel.css b/src/pages/admin/AdminPanel.css index 4364dfc..2ad5a89 100644 --- a/src/pages/admin/AdminPanel.css +++ b/src/pages/admin/AdminPanel.css @@ -96,6 +96,49 @@ .role-badge.moderator { background: rgba(255, 209, 102, 0.15); color: #ffd166; border: 1px solid rgba(255, 209, 102, 0.3); } .role-badge.user { background: rgba(79, 126, 255, 0.1); color: #4f7eff; border: 1px solid rgba(79, 126, 255, 0.2); } +select.role-badge { + cursor: pointer; + font-family: inherit; + font-size: 0.85rem; + outline: none; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + text-align: center; +} + +select.role-badge option { + background: #1a1a1a; + color: #fff; +} + +.row-deleted { + opacity: 0.6; + background: rgba(255, 255, 255, 0.02); +} + +.status-active { + color: #4caf50; + font-weight: 600; + font-size: 0.85rem; +} + +.status-deleted { + color: #ff6b6b; + font-weight: 600; + font-size: 0.85rem; +} + +.admin-table td strong { + color: #ff6b6b; +} + +.admin-table td { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; +} + .type-badge.paid { background: rgba(255, 209, 102, 0.15); color: #ffd166; border: 1px solid rgba(255, 209, 102, 0.3); } .type-badge.free { background: rgba(6, 214, 160, 0.15); color: #06d6a0; border: 1px solid rgba(6, 214, 160, 0.3); } .type-badge.adwall { background: rgba(168, 85, 247, 0.15); color: #a855f7; border: 1px solid rgba(168, 85, 247, 0.3); } diff --git a/src/pages/admin/AdminPanel.tsx b/src/pages/admin/AdminPanel.tsx index 2c63367..391494a 100644 --- a/src/pages/admin/AdminPanel.tsx +++ b/src/pages/admin/AdminPanel.tsx @@ -3,9 +3,10 @@ import api from '../../api'; import './AdminPanel.css'; const AdminPanel = () => { - const [activeTab, setActiveTab] = useState<'users' | 'plugins'>('users'); + const [activeTab, setActiveTab] = useState<'users' | 'plugins' | 'logs'>('users'); const [users, setUsers] = useState([]); const [plugins, setPlugins] = useState([]); + const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -20,34 +21,52 @@ const AdminPanel = () => { if (activeTab === 'users') { const res = await api.get('/admin/users'); setUsers(Array.isArray(res.data) ? res.data : []); - } else { + } else if (activeTab === 'plugins') { const res = await api.get('/admin/plugins'); setPlugins(Array.isArray(res.data) ? res.data : []); + } else if (activeTab === 'logs') { + const res = await api.get('/admin/logs'); + setLogs(Array.isArray(res.data) ? res.data : []); } } catch (err: any) { setError(err.response?.data?.error || 'Failed to fetch data'); setUsers([]); setPlugins([]); + setLogs([]); } finally { setLoading(false); } }; const deleteUser = async (id: number) => { - if (!window.confirm('Are you sure you want to delete this user and all their plugins?')) return; + const reason = window.prompt('Enter reason for deactivation:'); + if (!reason) return; try { - await api.delete(`/admin/users/${id}`); - setUsers(users.filter(u => u.id !== id)); + await api.delete(`/admin/users/${id}`, { data: { reason } }); + setUsers(users.map(u => u.id === id ? { ...u, deleted_at: new Date().toISOString() } : u)); } catch (err: any) { alert(err.response?.data?.error || 'Failed to delete user'); } }; - const deletePlugin = async (id: number) => { - if (!window.confirm('Are you sure you want to delete this plugin?')) return; + const changeRole = async (id: number, newRole: string) => { + const reason = window.prompt(`Enter reason for changing role to ${newRole}:`); + if (!reason) return; try { - await api.delete(`/admin/plugins/${id}`); - setPlugins(plugins.filter(p => p.id !== id)); + await api.put(`/admin/users/${id}/role`, { role: newRole, reason }); + setUsers(users.map(u => u.id === id ? { ...u, role: newRole } : u)); + alert('Role updated successfully'); + } catch (err: any) { + alert(err.response?.data?.error || 'Failed to update role'); + } + }; + + const deletePlugin = async (id: number) => { + const reason = window.prompt('Enter reason for removal:'); + if (!reason) return; + try { + await api.delete(`/admin/plugins/${id}`, { data: { reason } }); + setPlugins(plugins.map(p => p.id === id ? { ...p, deleted_at: new Date().toISOString() } : p)); } catch (err: any) { alert(err.response?.data?.error || 'Failed to delete plugin'); } @@ -70,6 +89,12 @@ const AdminPanel = () => { > Plugins + @@ -87,21 +112,61 @@ const AdminPanel = () => { Email Role Stripe - Created + Status Actions {users.map(u => ( - + {u.id} {u.username} {u.email} - {u.role} + + + {u.stripe_ready ? '✅' : '❌'} - {u.created_at ? new Date(u.created_at).toLocaleDateString() : 'N/A'} + {u.deleted_at ? Deleted : Active} - + {!u.deleted_at && } + + + ))} + + + ) : activeTab === 'plugins' ? ( + + + + + + + + + + + + + + {plugins.map(p => ( + + + + + + + + ))} @@ -111,29 +176,21 @@ const AdminPanel = () => {
IDNameTypePriceDeveloperStatusActions
{p.id}{p.name}{p.type}€{(p.price_cents / 100).toFixed(2)}{p.dev_name}{p.deleted_at ? Deleted : Active} + {!p.deleted_at && }
- - - - - - - - + + + + + - {plugins.map(p => ( - - - - - - - - - + {logs.map(l => ( + + + + + + ))} diff --git a/src/pages/legal/GuidelinesPage.tsx b/src/pages/legal/GuidelinesPage.tsx new file mode 100644 index 0000000..8412774 --- /dev/null +++ b/src/pages/legal/GuidelinesPage.tsx @@ -0,0 +1,171 @@ +import React from 'react'; +import './LegalPages.css'; + +const GuidelinesPage = () => { + return ( +
+
+
+

PumpkinMarket Review Guidelines

+

High-quality plugins for a high-quality community.

+
Last Updated: June 11, 2026
+
+ +
+

+ Plugins are a great way to extend the functionality of PumpkinMC servers. Our goal is to provide a safe experience for users + and a great opportunity for every developer to succeed. We’ve developed these Review Guidelines to help you through + the development process. +

+
+

Before You Submit

+
    +
  • Ensure your plugin is complete and functional.
  • +
  • Test for crashes and server performance impact.
  • +
  • Check that all links, icons, and metadata are accurate and final.
  • +
  • Verify that your plugin follows all security best practices.
  • +
+
+
+ +
+

1. Safety

+

When users download a plugin from PumpkinMarket, they need to be certain that it is safe to do so. Your plugin should not contain self-replicating code, backdoors, or any features that could harm a server or its data.

+ +
+

1.1 Objectionable Content

+

Plugins should not include content that is offensive, insensitive, upsetting, intended to disgust, or in exceptionally poor taste. Examples of such content include:

+
    +
  • 1.1.1 Defamatory or Discriminatory: Content that is defamatory, discriminatory, or mean-spirited, including references or commentary about religion, race, sexual orientation, gender, national/ethnic origin, or other targeted groups.
  • +
  • 1.1.2 Violence: Realistic depictions of physical harm or violence against people or animals. Content that encourages self-harm or illegal acts.
  • +
  • 1.1.3 Sexual Content: Overtly sexual or pornographic material, including "adult-themed" plugins.
  • +
  • 1.1.4 Harassment: Features designed to facilitate doxxing, stalking, or targeted harassment of server players.
  • +
+
+ +
+

1.2 User Protection

+

PumpkinMarket is dedicated to the prevention of "griefing" and malicious exploitation at the platform level.

+
    +
  • 1.2.1 Malware: Plugins must not contain any malicious code. This includes viruses, backdoors, hidden "OP" commands, or any code that performs actions without the server owner's knowledge.
  • +
  • 1.2.2 Phishing: Plugins must not attempt to steal user credentials, tokens (like Discord tokens), or any sensitive configuration files from the server host.
  • +
  • 1.2.3 Remote Execution: Plugins must not download and execute arbitrary code from external sources that was not included in the original WASM binary approved by the market.
  • +
+
+ +
+

1.3 Data Privacy

+

Data privacy is a fundamental right. Plugins must be transparent about data collection.

+
    +
  • 1.3.1 Transparency: If your plugin sends data to an external server (e.g., for analytics or licensing), you must disclose this clearly in the description.
  • +
  • 1.3.2 Opt-out: All telemetry or external reporting must provide a configuration option to be disabled.
  • +
  • 1.3.3 Sensitive Files: Plugins must not access or upload files outside of their designated data folder without clear justification and disclosure.
  • +
+
+
+ +
+

2. Performance

+

Users expect plugins to be reliable and efficient. Unoptimized plugins can crash servers or cause significant lag, leading to a poor user experience.

+ +
+

2.1 Plugin Completeness

+

PumpkinMarket will reject plugins that are not yet complete. We do not accept "placeholder" plugins or versions that are non-functional.

+
    +
  • 2.1.1 Minimum Functionality: Plugins must provide a meaningful extension to the server experience. "Empty" plugins or those that only print a message to the console will be rejected.
  • +
  • 2.1.2 Stability: Plugins that cause the server to crash or hang during normal operation will be removed.
  • +
+
+ +
+

2.2 Accurate Metadata

+

The information you provide about your plugin must be accurate and honest.

+
    +
  • 2.2.1 Screenshots: Screenshots must accurately reflect the plugin's interface or effects in-game. Do not use generic or "stock" images that are unrelated.
  • +
  • 2.2.2 Categorization: Plugins must be placed in the most relevant category. Mis-categorizing to gain visibility is a violation.
  • +
  • 2.2.3 Descriptions: Do not include irrelevant keywords or "tag spam" in your description to manipulate search results.
  • +
+
+ +
+

2.3 Resource Management

+

PumpkinMC servers run in high-concurrency environments. Performance is critical.

+
    +
  • 2.3.1 CPU usage: Avoid blocking the main server thread. Use asynchronous tasks where possible.
  • +
  • 2.3.2 Memory leaks: Ensure your WASM binary does not leak memory over long runtimes.
  • +
+
+
+ +
+

3. Business

+

monetization on PumpkinMarket should be transparent and provide clear value to the user.

+ +
+

3.1 Payments

+
    +
  • 3.1.1 Integrated Payments: All transactions for plugins must go through the PumpkinMarket system. Linking to Patreon, PayPal, or other external payment sites within the plugin or description is not allowed.
  • +
  • 3.1.2 Digital Goods: Plugins must not unlock features via external "keys" or "codes" purchased outside the market.
  • +
+
+ +
+

3.2 Business Practices

+
    +
  • 3.2.1 Bait and Switch: Plugins must not change their primary purpose after approval or hide essential features behind unexpected paywalls.
  • +
  • 3.2.2 Fake Reviews: Developers found to be inflating their own ratings or reviews will be permanently banned.
  • +
  • 3.2.3 Scamming: Any plugin found to be a scam or "vaporware" will result in an immediate permanent ban and possible refunding of all affected users.
  • +
+
+
+ +
+

4. Design

+

A great plugin is more than just code; it's also about how it's presented and how users interact with it.

+ +
+

4.1 Copycats and Spam

+
    +
  • 4.1.1 Originality: Do not simply "clone" another popular plugin. While similar features are allowed, your plugin should offer unique value or a different implementation.
  • +
  • 4.1.2 Spam: Do not upload multiple near-identical plugins. Consolidate features into a single, high-quality plugin with configuration options.
  • +
+
+ +
+

4.2 User Experience

+
    +
  • 4.2.1 Consistency: We recommend following standard command patterns (e.g., /pluginname help) to ensure a familiar experience for users.
  • +
  • 4.2.2 Documentation: Provide clear instructions in your description or a linked wiki. Plugins that are impossible to configure or use will be rejected.
  • +
+
+
+ +
+

5. Legal

+

You are responsible for ensuring your plugin complies with all applicable laws and regulations.

+ +
+

5.1 Intellectual Property

+

You must be the original author of the code or have the legal right to distribute it. Using leaked or stolen code ("leaks") will result in an immediate ban.

+
+ +
+

5.2 Open Source Licenses

+

If your plugin includes open-source components, you must adhere to the terms of those licenses. Include necessary attributions and license files within your plugin archive.

+
+ +
+

5.3 Right to Remove

+

PumpkinMarket reserves the right to remove any plugin at its sole discretion if it is determined to be detrimental to the market's reputation or user safety, even if a specific guideline has not been technically violated.

+
+
+ +
+

We hope these guidelines help you build better plugins and a better PumpkinMarket. We’re excited to see what you create!

+
+
+
+ ); +}; + +export default GuidelinesPage; diff --git a/src/pages/legal/LegalPages.css b/src/pages/legal/LegalPages.css new file mode 100644 index 0000000..5eed732 --- /dev/null +++ b/src/pages/legal/LegalPages.css @@ -0,0 +1,119 @@ +.legal-container { + max-width: 900px; + margin: 0 auto; + padding: 80px 24px; + color: #e0e0e0; + line-height: 1.6; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +} + +.legal-content h1 { + font-size: 3rem; + font-weight: 800; + margin-bottom: 0.5rem; + color: #fff; + letter-spacing: -0.02em; +} + +.legal-content h1 span { + color: #ffb74d; +} + +.subtitle { + font-size: 1.25rem; + color: #999; + margin-bottom: 2rem; +} + +.last-updated { + font-size: 0.9rem; + color: #666; + margin-bottom: 3rem; + border-bottom: 1px solid #333; + padding-bottom: 2rem; +} + +.introduction p { + font-size: 1.15rem; + margin-bottom: 2rem; +} + +.callout { + background: rgba(255, 183, 77, 0.05); + border: 1px solid rgba(255, 183, 77, 0.2); + border-radius: 12px; + padding: 24px; + margin: 2rem 0; +} + +.callout h3 { + margin-top: 0; + color: #ffb74d; + font-size: 1.1rem; + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 1rem; +} + +.callout ul { + margin: 0; + padding-left: 20px; +} + +.callout li { + margin-bottom: 8px; + color: #ccc; +} + +.guideline-section { + margin-top: 4rem; +} + +.guideline-section h2 { + font-size: 2rem; + color: #fff; + margin-bottom: 1rem; + border-bottom: 2px solid #333; + padding-bottom: 0.5rem; +} + +.section-intro { + font-size: 1.1rem; + color: #bbb; + margin-bottom: 2rem; +} + +.sub-section { + margin-bottom: 2rem; + padding-left: 20px; + border-left: 2px solid #222; +} + +.sub-section h3 { + font-size: 1.25rem; + color: #ffb74d; + margin-bottom: 0.5rem; +} + +.sub-section p { + color: #aaa; + margin: 0; +} + +.guidelines-footer { + margin-top: 6rem; + padding-top: 3rem; + border-top: 1px solid #333; + text-align: center; + color: #666; +} + +@media (max-width: 768px) { + .legal-content h1 { + font-size: 2.25rem; + } + + .legal-container { + padding: 40px 20px; + } +}
IDNameTypePriceDeveloperDownloadsViewsActionsDateModeratorActionTargetReason
{p.id}{p.name}{p.type}€{(p.price_cents / 100).toFixed(2)}{p.dev_name}{p.downloads}{p.views} - -
{new Date(l.created_at).toLocaleString()}{l.moderator_name}{l.action.toUpperCase()}{l.target_type} ({l.target_id}){l.reason || 'No reason provided'}