+
{/* Header */}
-
+
-
+
+
+
+
+ Save Your Recovery Codes
+
+
+ If you ever lose access to your authenticator app or security keys, you can use these 10 one-time codes to regain access to your account.
+
-
- Save Your Emergency Recovery Codes
-
-
- If you lose access to your device or authenticator app, these one-time backup codes are the only way to regain access to your account.
-
{/* Warning Alert */}
@@ -99,8 +83,12 @@ export const RecoveryCodesModal: React.FC
= ({
color: '#fca5a5',
marginBottom: '1.25rem',
lineHeight: 1.4,
+ display: 'flex',
+ alignItems: 'center',
+ gap: '0.5rem',
}}>
- ⚠️ Important: We do not store these codes in plaintext and cannot recover them for you. Save them in a password manager now.
+
+ Important: We do not store these codes in plaintext and cannot recover them for you. Save them in a password manager now.
{/* Codes Grid */}
diff --git a/src/components/SecuritySetupCards.tsx b/src/components/SecuritySetupCards.tsx
index 6c6ef7d..8695ff9 100644
--- a/src/components/SecuritySetupCards.tsx
+++ b/src/components/SecuritySetupCards.tsx
@@ -322,7 +322,7 @@ export const SecuritySetupCards: React.FC
= ({
color: 'var(--mp-muted, #94a3b8)',
lineHeight: 1.5,
}}>
- 💡 How it works: Passkeys replace passwords and SMS codes with cryptographic keys stored securely in your device’s keychain (Apple Keychain, Google Password Manager, Windows Hello, 1Password, or YubiKey). They are 100% phishing-proof.
+ How it works: Passkeys replace passwords and SMS codes with cryptographic keys stored securely in your device’s keychain (Apple Keychain, Google Password Manager, Windows Hello, 1Password, or YubiKey). They are 100% phishing-proof.
{passkeyError && (
diff --git a/src/locales/de.json b/src/locales/de.json
index 5aa9af6..f51d9dd 100644
--- a/src/locales/de.json
+++ b/src/locales/de.json
@@ -112,15 +112,15 @@
"badge": "Entwicklerportal",
"subtitle_paid": "Richte dein Creator-Profil und deine Verkäufer-Identität ein, um Plugins zu verkaufen.",
"subtitle_free": "Richte dein Creator-Profil ein, um kostenlose Plugins für die Community zu veröffentlichen.",
- "step_profile": "1. Profil",
- "step_seller": "2. Verkäuferdaten",
+ "step_profile": "Profil",
+ "step_seller": "Verkäuferdaten",
"step_links": "Links",
"step_finish": "Fertigstellen",
"entity_question": "Veröffentlichst du als Einzelperson oder Organisation?",
"individual_title": "Einzelentwickler",
- "individual_desc": "Unabhängiger Entwickler oder Hobbyist.",
- "org_title": "Organisation / Studio",
- "org_desc": "Unternehmen, Team oder Studio.",
+ "individual_desc": "Ideal für unabhängige Entwickler, Studenten und Hobby-Programmierer. Veröffentliche kostenlose oder Premium-WebAssembly-Plugins unter deinem persönlichen Profil.",
+ "org_title": "Organisation oder Studio",
+ "org_desc": "Ideal für Entwicklerteams, Studios oder registrierte Unternehmen, die gemeinsam Plugins veröffentlichen. Verwalte deine Markenidentität und Teamauszahlungen.",
"display_name_label": "Öffentlicher Entwicklername *",
"display_name_placeholder": "z. B. PixelCraft Studios, MaxDev",
"display_name_hint": "Dieser Name wird öffentlich auf allen deinen Plugins angezeigt.",
@@ -144,7 +144,19 @@
"btn_continue": "Weiter ->",
"btn_back": "Zurück",
"btn_complete": "Onboarding abschließen",
- "btn_connect_stripe": "Speichern & Stripe verbinden ->"
+ "btn_connect_stripe": "Speichern & Stripe verbinden ->",
+ "step_account_type": "Kontotyp",
+ "org_type_label": "Organisationstyp:",
+ "org_type_placeholder": "Wähle deinen Organisationstyp ▾",
+ "btn_get_started": "Loslegen ->",
+ "plan_paid_question": "Planst du, kostenpflichtige Plugins zu veröffentlichen? *",
+ "option_yes": "Ja",
+ "option_no": "Nein",
+ "step_name": "Name",
+ "step_monetization": "Monetarisierung",
+ "dev_name_title": "Wähle deinen Entwicklernamen",
+ "dev_name_desc": "Diesen Namen sehen Spieler und Server-Besitzer auf Pumpkin Market bei deinen Plugins. Er kann sich von deinem rechtlichen Namen unterscheiden und jederzeit geändert werden.",
+ "name_taken_error": "Dieser Entwicklername ist bereits vergeben. Bitte wähle einen anderen Namen."
},
"dashboard": {
"overview": "Übersicht",
@@ -163,7 +175,19 @@
"payouts": "Stripe-Auszahlungen",
"payouts_status": "Stripe Connect Status",
"connected": "Verbunden",
- "setup_stripe": "Stripe-Auszahlungen einrichten"
+ "setup_stripe": "Stripe-Auszahlungen einrichten",
+ "create_plugin_title": "Neues Plugin erstellen",
+ "create_plugin_desc": "Erstelle deinen Plugin-Entwurf. Du kannst deinen Store-Eintrag bearbeiten, Builds hochladen und offline testen, bevor du veröffentlichst.",
+ "btn_create_draft": "Entwurf erstellen & verwalten ->",
+ "status_draft": "Offline (Entwurf)",
+ "status_published": "Veröffentlicht",
+ "btn_publish_store": "Im Store veröffentlichen",
+ "btn_unpublish_store": "Offline nehmen",
+ "publish_checklist_title": "Auf dem Marktplatz veröffentlichen",
+ "publish_checklist_desc": "Überprüfe die Anforderungen, bevor du dein Plugin auf dem Pumpkin Market veröffentlichst.",
+ "publish_ready_banner": "Alle Anforderungen erfüllt! Dein Plugin kann veröffentlicht werden.",
+ "publish_missing_banner": "Bitte erfülle alle fehlenden Anforderungen vor der Veröffentlichung.",
+ "btn_confirm_publish": "Bestätigen & im Store freigeben"
},
"upload": {
"title": "Neues Plugin veröffentlichen",
diff --git a/src/locales/en.json b/src/locales/en.json
index ac5c1a9..f166b17 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -112,15 +112,15 @@
"badge": "Developer Portal",
"subtitle_paid": "Set up your creator profile and seller identity to publish and sell plugins.",
"subtitle_free": "Set up your creator profile to publish free plugins for the Pumpkin community.",
- "step_profile": "1. Profile",
- "step_seller": "2. Seller Info",
+ "step_profile": "Profile",
+ "step_seller": "Seller Info",
"step_links": "Links",
"step_finish": "Finish",
"entity_question": "Are you publishing as an individual or organization?",
"individual_title": "Individual Creator",
- "individual_desc": "Independent developer or hobbyist.",
- "org_title": "Organization / Studio",
- "org_desc": "Company, team, or studio.",
+ "individual_desc": "Ideal for independent developers, students, and hobbyist creators. Publish free or premium WebAssembly plugins under your personal creator profile.",
+ "org_title": "Organization or Studio",
+ "org_desc": "Ideal for development teams, studios, or registered companies publishing plugins together. Manage your brand identity and organizational payouts.",
"display_name_label": "Developer / Studio Display Name *",
"display_name_placeholder": "e.g. PixelCraft Studios, JohnDoeDev",
"display_name_hint": "This name will be publicly visible on all your published plugins.",
@@ -144,7 +144,19 @@
"btn_continue": "Continue ->",
"btn_back": "Back",
"btn_complete": "Complete Onboarding",
- "btn_connect_stripe": "Save & Connect Stripe ->"
+ "btn_connect_stripe": "Save & Connect Stripe ->",
+ "step_account_type": "Account Type",
+ "org_type_label": "Organization type:",
+ "org_type_placeholder": "Select your organization type ▾",
+ "btn_get_started": "Get started ->",
+ "plan_paid_question": "Do you plan to publish Paid plugins? *",
+ "option_yes": "Yes",
+ "option_no": "No",
+ "step_name": "Name",
+ "step_monetization": "Monetization",
+ "dev_name_title": "Choose your developer name",
+ "dev_name_desc": "This is what players and server owners will see across Pumpkin Market to represent your profile and plugins. It can be different from your legal name, and you can change it at any time.",
+ "name_taken_error": "This developer name is already taken. Please choose another name."
},
"dashboard": {
"overview": "Overview",
@@ -163,7 +175,19 @@
"payouts": "Stripe Payouts",
"payouts_status": "Stripe Connect Status",
"connected": "Connected",
- "setup_stripe": "Set Up Stripe Payouts"
+ "setup_stripe": "Set Up Stripe Payouts",
+ "create_plugin_title": "Create New Plugin",
+ "create_plugin_desc": "Set up your plugin draft. You can configure your store listing, upload builds, and test offline before publishing.",
+ "btn_create_draft": "Create Draft & Manage ->",
+ "status_draft": "Offline (Draft)",
+ "status_published": "Published",
+ "btn_publish_store": "Publish to Store",
+ "btn_unpublish_store": "Take Offline",
+ "publish_checklist_title": "Publish to Marketplace",
+ "publish_checklist_desc": "Review requirements before releasing your plugin to the Pumpkin Market.",
+ "publish_ready_banner": "All requirements are met! Your plugin is ready to go live.",
+ "publish_missing_banner": "Please complete the missing requirements before publishing.",
+ "btn_confirm_publish": "Confirm & Release to Store"
},
"upload": {
"title": "Publish a New Plugin",
diff --git a/src/locales/es.json b/src/locales/es.json
index 09ddb1f..ea70300 100644
--- a/src/locales/es.json
+++ b/src/locales/es.json
@@ -112,15 +112,15 @@
"badge": "Portal de Desarrollador",
"subtitle_paid": "Configura tu perfil de creador e identidad fiscal para vender plugins.",
"subtitle_free": "Configura tu perfil de creador para publicar plugins gratuitos para la comunidad.",
- "step_profile": "1. Perfil",
- "step_seller": "2. Datos de Vendedor",
+ "step_profile": "Perfil",
+ "step_seller": "Datos de vendedor",
"step_links": "Enlaces",
"step_finish": "Finalizar",
"entity_question": "¿Publicas como particular u organización?",
"individual_title": "Creador Individual",
- "individual_desc": "Desarrollador independiente o aficionado.",
- "org_title": "Organización / Estudio",
- "org_desc": "Empresa, equipo o estudio.",
+ "individual_desc": "Ideal para desarrolladores independientes, estudiantes y aficionados. Publica plugins WebAssembly gratuitos o premium bajo tu perfil personal.",
+ "org_title": "Organización o Estudio",
+ "org_desc": "Ideal para equipos de desarrollo, estudios o empresas que publican plugins conjuntamente. Gestiona la identidad de tu marca y los pagos de la organización.",
"display_name_label": "Nombre público de Desarrollador / Estudio *",
"display_name_placeholder": "ej. PixelCraft Studios, JuanDev",
"display_name_hint": "Este nombre será visible públicamente en todos tus plugins.",
@@ -144,7 +144,19 @@
"btn_continue": "Continuar ->",
"btn_back": "Atrás",
"btn_complete": "Completar Registro",
- "btn_connect_stripe": "Guardar y Conectar Stripe ->"
+ "btn_connect_stripe": "Guardar y Conectar Stripe ->",
+ "step_account_type": "Tipo de cuenta",
+ "org_type_label": "Tipo de organización:",
+ "org_type_placeholder": "Selecciona tu tipo de organización ▾",
+ "btn_get_started": "Empezar ->",
+ "plan_paid_question": "¿Planeas publicar plugins de pago? *",
+ "option_yes": "Sí",
+ "option_no": "No",
+ "step_name": "Nombre",
+ "step_monetization": "Monetización",
+ "dev_name_title": "Elige tu nombre de desarrollador",
+ "dev_name_desc": "Este es el nombre que verán los jugadores y administradores en Pumpkin Market para tus plugins. Puede ser diferente de tu nombre legal y puedes cambiarlo en cualquier momento.",
+ "name_taken_error": "Este nombre de desarrollador ya está en uso. Por favor, elige otro."
},
"dashboard": {
"overview": "Resumen",
@@ -163,7 +175,19 @@
"payouts": "Pagos con Stripe",
"payouts_status": "Estado de Stripe Connect",
"connected": "Conectado",
- "setup_stripe": "Configurar Pagos Stripe"
+ "setup_stripe": "Configurar Pagos Stripe",
+ "create_plugin_title": "Crear nuevo plugin",
+ "create_plugin_desc": "Configura el borrador de tu plugin. Puedes editar la ficha, subir compilaciones y probar sin conexión antes de publicar.",
+ "btn_create_draft": "Crear borrador y gestionar ->",
+ "status_draft": "Desconectado (Borrador)",
+ "status_published": "Publicado",
+ "btn_publish_store": "Publicar en la tienda",
+ "btn_unpublish_store": "Poner fuera de línea",
+ "publish_checklist_title": "Publicar en el mercado",
+ "publish_checklist_desc": "Revisa los requisitos antes de lanzar tu plugin en Pumpkin Market.",
+ "publish_ready_banner": "¡Todos los requisitos están completos! Tu plugin está listo para publicarse.",
+ "publish_missing_banner": "Por favor, completa los requisitos pendientes antes de publicar.",
+ "btn_confirm_publish": "Confirmar y lanzar a la tienda"
},
"upload": {
"title": "Publicar un Nuevo Plugin",
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 090270e..a855cc2 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -112,15 +112,15 @@
"badge": "Portail Développeur",
"subtitle_paid": "Configurez votre profil de créateur et vos coordonnées de vendeur pour distribuer et vendre des plugins.",
"subtitle_free": "Configurez votre profil de créateur pour publier des plugins gratuits pour la communauté Pumpkin.",
- "step_profile": "1. Profil",
- "step_seller": "2. Vendeur",
+ "step_profile": "Profil",
+ "step_seller": "Infos vendeur",
"step_links": "Liens",
"step_finish": "Terminer",
"entity_question": "Publiez-vous en tant que particulier ou organisation ?",
"individual_title": "Créateur individuel",
- "individual_desc": "Développeur indépendant ou passionné.",
- "org_title": "Organisation / Studio",
- "org_desc": "Entreprise, équipe ou studio.",
+ "individual_desc": "Idéal pour les développeurs indépendants, étudiants et créateurs passionnés. Publiez des plugins WebAssembly gratuits ou payants sous votre profil personnel.",
+ "org_title": "Organisation ou studio",
+ "org_desc": "Idéal pour les équipes de développement, studios ou entreprises publiant des plugins ensemble. Gérez l'identité de votre marque et vos paiements professionnels.",
"display_name_label": "Nom public du développeur / studio *",
"display_name_placeholder": "ex : PixelCraft Studios, AlexDev",
"display_name_hint": "Ce nom apparaîtra sur tous vos plugins publiés.",
@@ -144,7 +144,19 @@
"btn_continue": "Continuer ->",
"btn_back": "Retour",
"btn_complete": "Terminer l'inscription",
- "btn_connect_stripe": "Enregistrer & Connecter Stripe ->"
+ "btn_connect_stripe": "Enregistrer & Connecter Stripe ->",
+ "step_account_type": "Type de compte",
+ "org_type_label": "Type d'organisation :",
+ "org_type_placeholder": "Sélectionnez votre type d'organisation ▾",
+ "btn_get_started": "Commencer ->",
+ "plan_paid_question": "Prévoyez-vous de publier des plugins payants ? *",
+ "option_yes": "Oui",
+ "option_no": "Non",
+ "step_name": "Nom",
+ "step_monetization": "Monétisation",
+ "dev_name_title": "Choisissez votre nom de développeur",
+ "dev_name_desc": "C'est le nom que les joueurs et administrateurs verront sur Pumpkin Market pour vos plugins. Il peut être différent de votre nom légal et pourra être modifié à tout moment.",
+ "name_taken_error": "Ce nom de développeur est déjà pris. Veuillez en choisir un autre."
},
"dashboard": {
"overview": "Aperçu",
@@ -163,7 +175,19 @@
"payouts": "Virements Stripe",
"payouts_status": "Statut Stripe Connect",
"connected": "Connecté",
- "setup_stripe": "Configurer les virements Stripe"
+ "setup_stripe": "Configurer les virements Stripe",
+ "create_plugin_title": "Créer un nouveau plugin",
+ "create_plugin_desc": "Configurez votre brouillon de plugin. Vous pouvez modifier la fiche de store, importer des builds et tester hors ligne avant de publier.",
+ "btn_create_draft": "Créer le brouillon & gérer ->",
+ "status_draft": "Hors ligne (Brouillon)",
+ "status_published": "Publié",
+ "btn_publish_store": "Publier sur le Store",
+ "btn_unpublish_store": "Mettre hors ligne",
+ "publish_checklist_title": "Publier sur le marché",
+ "publish_checklist_desc": "Vérifiez les prérequis avant de publier votre plugin sur Pumpkin Market.",
+ "publish_ready_banner": "Tous les prérequis sont validés ! Votre plugin est prêt à être publié.",
+ "publish_missing_banner": "Veuillez compléter les prérequis manquants avant de publier.",
+ "btn_confirm_publish": "Confirmer & publier sur le Store"
},
"upload": {
"title": "Publier un nouveau plugin",
diff --git a/src/locales/it.json b/src/locales/it.json
index 98cb378..6d3a0cb 100644
--- a/src/locales/it.json
+++ b/src/locales/it.json
@@ -112,15 +112,15 @@
"badge": "Portale Sviluppatore",
"subtitle_paid": "Configura il tuo profilo creator e i tuoi dati fiscali per pubblicare e vendere plugin.",
"subtitle_free": "Configura il tuo profilo creator per pubblicare plugin gratuiti per la community.",
- "step_profile": "1. Profilo",
- "step_seller": "2. Dati Venditore",
+ "step_profile": "Profilo",
+ "step_seller": "Dati venditore",
"step_links": "Link",
"step_finish": "Completa",
"entity_question": "Pubblichi come persona fisica o organizzazione?",
- "individual_title": "Creator Singolo",
- "individual_desc": "Sviluppatore indipendente o hobbista.",
- "org_title": "Organizzazione / Studio",
- "org_desc": "Azienda, team o studio di sviluppo.",
+ "individual_title": "Creatore Individuale",
+ "individual_desc": "Ideale per sviluppatori indipendenti, studenti e appassionati. Pubblica plugin WebAssembly gratuiti o a pagamento con il tuo profilo personale.",
+ "org_title": "Organizzazione o Studio",
+ "org_desc": "Ideale per team di sviluppo, studi o aziende che pubblicano plugin insieme. Gestisci l'identità del brand e i pagamenti dell'organizzazione.",
"display_name_label": "Nome Pubblico Sviluppatore / Studio *",
"display_name_placeholder": "es. PixelCraft Studios, MarcoDev",
"display_name_hint": "Questo nome sarà visibile pubblicamente su tutti i tuoi plugin.",
@@ -144,7 +144,19 @@
"btn_continue": "Continua ->",
"btn_back": "Indietro",
"btn_complete": "Completa Registrazione",
- "btn_connect_stripe": "Salva e Collega Stripe ->"
+ "btn_connect_stripe": "Salva e Collega Stripe ->",
+ "step_account_type": "Tipo di account",
+ "org_type_label": "Tipo di organizzazione:",
+ "org_type_placeholder": "Seleziona il tipo di organizzazione ▾",
+ "btn_get_started": "Inizia ->",
+ "plan_paid_question": "Hai intenzione di pubblicare plugin a pagamento? *",
+ "option_yes": "Sì",
+ "option_no": "No",
+ "step_name": "Nome",
+ "step_monetization": "Monetizzazione",
+ "dev_name_title": "Scegli il tuo nome sviluppatore",
+ "dev_name_desc": "Questo è il nome che i giocatori e i proprietari di server vedranno su Pumpkin Market per i tuoi plugin. Può essere diverso dal tuo nome legale e potrai modificarlo in qualsiasi momento.",
+ "name_taken_error": "Questo nome sviluppatore è già in uso. Scegline un altro."
},
"dashboard": {
"overview": "Panoramica",
@@ -163,7 +175,19 @@
"payouts": "Pagamenti Stripe",
"payouts_status": "Stato Stripe Connect",
"connected": "Collegato",
- "setup_stripe": "Configura Pagamenti Stripe"
+ "setup_stripe": "Configura Pagamenti Stripe",
+ "create_plugin_title": "Crea nuovo plugin",
+ "create_plugin_desc": "Configura la bozza del tuo plugin. Puoi modificare la scheda dello store, caricare build e testare offline prima della pubblicazione.",
+ "btn_create_draft": "Crea bozza e gestisci ->",
+ "status_draft": "Offline (Bozza)",
+ "status_published": "Pubblicato",
+ "btn_publish_store": "Pubblica sullo Store",
+ "btn_unpublish_store": "Metti offline",
+ "publish_checklist_title": "Pubblica sul Marketplace",
+ "publish_checklist_desc": "Verifica i requisiti prima di rilasciare il plugin su Pumpkin Market.",
+ "publish_ready_banner": "Tutti i requisiti sono soddisfatti! Il tuo plugin è pronto per la pubblicazione.",
+ "publish_missing_banner": "Completa i requisiti mancanti prima di pubblicare.",
+ "btn_confirm_publish": "Conferma e pubblica sullo Store"
},
"upload": {
"title": "Pubblica un Nuovo Plugin",
diff --git a/src/locales/ja.json b/src/locales/ja.json
index 84516c0..f5ba26d 100644
--- a/src/locales/ja.json
+++ b/src/locales/ja.json
@@ -112,15 +112,15 @@
"badge": "開発者ポータル",
"subtitle_paid": "プラグインを販売・配信するためのクリエイタープロフィールと事業者情報を設定します。",
"subtitle_free": "Pumpkin コミュニティ向けに無料プラグインを公開するためのクリエイタープロフィールを設定します。",
- "step_profile": "1. プロフィール",
- "step_seller": "2. 販売者情報",
+ "step_profile": "プロフィール",
+ "step_seller": "販売者情報",
"step_links": "リンク",
"step_finish": "完了",
"entity_question": "個人として公開しますか、それとも組織・スタジオとして公開しますか?",
"individual_title": "個人クリエイター",
- "individual_desc": "個人開発者またはホビー制作者。",
- "org_title": "組織 / スタジオ",
- "org_desc": "企業、チーム、またはスタジオ。",
+ "individual_desc": "個人開発者、学生、コミュニティクリエイターに最適です。無料または有料のWebAssemblyプラグインを個人プロフィールで公開できます。",
+ "org_title": "組織またはスタジオ",
+ "org_desc": "共同でプラグインを配信する開発チーム、スタジオ、法人に最適です。ブランドの管理や組織向け支払い設定が行えます。",
"display_name_label": "公開開発者 / スタジオ名 *",
"display_name_placeholder": "例: PixelCraft Studios, TanakaDev",
"display_name_hint": "この名前は公開するすべてのプラグインに表示されます。",
@@ -144,7 +144,19 @@
"btn_continue": "次へ進む ->",
"btn_back": "戻る",
"btn_complete": "登録を完了する",
- "btn_connect_stripe": "保存して Stripe と連携 ->"
+ "btn_connect_stripe": "保存して Stripe と連携 ->",
+ "step_account_type": "アカウントの種類",
+ "org_type_label": "組織の種類:",
+ "org_type_placeholder": "組織の種類を選択 ▾",
+ "btn_get_started": "使ってみる ->",
+ "plan_paid_question": "有料プラグインの公開を予定していますか? *",
+ "option_yes": "はい",
+ "option_no": "いいえ",
+ "step_name": "名前",
+ "step_monetization": "収益化",
+ "dev_name_title": "開発者名を設定してください",
+ "dev_name_desc": "これは、Pumpkin Market 上でプレイヤーやサーバー管理者に公開される名前です。本名と異なっていても問題なく、いつでも変更できます。",
+ "name_taken_error": "この開発者名は既に使用されています。別の名前を選択してください。"
},
"dashboard": {
"overview": "概要",
@@ -163,7 +175,19 @@
"payouts": "Stripe 送金",
"payouts_status": "Stripe Connect ステータス",
"connected": "連携済み",
- "setup_stripe": "Stripe 送金を設定"
+ "setup_stripe": "Stripe 送金を設定",
+ "create_plugin_title": "新規プラグイン作成",
+ "create_plugin_desc": "プラグインの下書きを設定します。公開前にストア掲載情報を編集し、ビルドをアップロードしてテストできます。",
+ "btn_create_draft": "下書きを作成して管理 ->",
+ "status_draft": "オフライン (下書き)",
+ "status_published": "公開済み",
+ "btn_publish_store": "ストアに公開",
+ "btn_unpublish_store": "非公開にする",
+ "publish_checklist_title": "マーケットプレイスに公開",
+ "publish_checklist_desc": "Pumpkin Market にプラグインをリリースする前に必須要件を確認してください。",
+ "publish_ready_banner": "すべての要件を満たしています!プラグインを公開する準備が整いました。",
+ "publish_missing_banner": "公開する前に未完了の要件を完了してください。",
+ "btn_confirm_publish": "確認してストアに公開"
},
"upload": {
"title": "新しいプラグインの公開",
diff --git a/src/locales/ko.json b/src/locales/ko.json
index ddaba66..ffd1a4c 100644
--- a/src/locales/ko.json
+++ b/src/locales/ko.json
@@ -112,15 +112,15 @@
"badge": "개발자 포털",
"subtitle_paid": "플러그인을 출시하고 판매하기 위해 크리에이터 프로필과 판매자 정보를 설정하세요.",
"subtitle_free": "Pumpkin 커뮤니티에 무료 플러그인을 공개하기 위해 크리에이터 프로필을 설정하세요.",
- "step_profile": "1. 프로필",
- "step_seller": "2. 판매자 정보",
+ "step_profile": "프로필",
+ "step_seller": "판매자 정보",
"step_links": "소셜 링크",
"step_finish": "완료",
"entity_question": "개인 자격으로 활동하십니까, 아니면 스튜디오/법인입니까?",
"individual_title": "개인 크리에이터",
- "individual_desc": "독립 개발자 또는 개인 개발자.",
- "org_title": "기업 / 스튜디오",
- "org_desc": "회사, 팀 또는 스튜디오.",
+ "individual_desc": "개인 개발자, 학생 및 취미 개발자에게 적합합니다. 개인 프로필을 통해 무료 또는 유료 WebAssembly 플러그인을 배포할 수 있습니다.",
+ "org_title": "조직 또는 스튜디오",
+ "org_desc": "플러그인을 함께 배포하는 개발팀, 스튜디오 또는 기업에 적합합니다. 브랜드 아이덴티티와 조직 정산을 관리하세요.",
"display_name_label": "개발자 / 스튜디오 공개 표시 이름 *",
"display_name_placeholder": "예: PixelCraft Studios, KimDev",
"display_name_hint": "이 이름은 출시하는 모든 플러그인에 공개적으로 표시됩니다.",
@@ -144,7 +144,19 @@
"btn_continue": "계속 ->",
"btn_back": "이전",
"btn_complete": "온보딩 완료",
- "btn_connect_stripe": "저장 및 Stripe 연결 ->"
+ "btn_connect_stripe": "저장 및 Stripe 연결 ->",
+ "step_account_type": "계정 유형",
+ "org_type_label": "조직 유형:",
+ "org_type_placeholder": "조직 유형을 선택하세요 ▾",
+ "btn_get_started": "시작하기 ->",
+ "plan_paid_question": "유료 플러그인을 배포할 계획이 있으신가요? *",
+ "option_yes": "예",
+ "option_no": "아니요",
+ "step_name": "이름",
+ "step_monetization": "수익 창출",
+ "dev_name_title": "개발자 이름을 설정하세요",
+ "dev_name_desc": "플레이어와 서버 관리자가 Pumpkin Market에서 귀하의 플러그인을 볼 때 표시되는 공개 이름입니다. 실명과 달라도 되며 언제든지 변경할 수 있습니다.",
+ "name_taken_error": "이 개발자 이름은 이미 사용 중입니다. 다른 이름을 선택하세요."
},
"dashboard": {
"overview": "개요",
@@ -163,7 +175,19 @@
"payouts": "Stripe 정산",
"payouts_status": "Stripe Connect 상태",
"connected": "연결됨",
- "setup_stripe": "Stripe 정산 설정"
+ "setup_stripe": "Stripe 정산 설정",
+ "create_plugin_title": "새 플러그인 만들기",
+ "create_plugin_desc": "플러그인 초안을 생성합니다. 게시 전에 스토어 등록정보를 편집하고 빌드를 업로드하여 오프라인으로 테스트할 수 있습니다.",
+ "btn_create_draft": "초안 생성 및 관리 ->",
+ "status_draft": "오프라인 (초안)",
+ "status_published": "게시됨",
+ "btn_publish_store": "스토어에 게시",
+ "btn_unpublish_store": "게시 취소 (오프라인)",
+ "publish_checklist_title": "마켓플레이스에 게시",
+ "publish_checklist_desc": "Pumpkin Market에 플러그인을 출시하기 전에 요구사항을 검토하세요.",
+ "publish_ready_banner": "모든 요구사항을 충족했습니다! 플러그인을 출시할 준비가 완료되었습니다.",
+ "publish_missing_banner": "게시하기 전에 누락된 요구사항을 완료해 주세요.",
+ "btn_confirm_publish": "확인 및 스토어에 출시"
},
"upload": {
"title": "새 플러그인 출시",
diff --git a/src/locales/nl.json b/src/locales/nl.json
index 5e02029..6306137 100644
--- a/src/locales/nl.json
+++ b/src/locales/nl.json
@@ -112,15 +112,15 @@
"badge": "Ontwikkelaarsportaal",
"subtitle_paid": "Stel je creatorprofiel en verkopersgegevens in om plugins te publiceren en verkopen.",
"subtitle_free": "Stel je creatorprofiel in om gratis plugins te delen met de Pumpkin-community.",
- "step_profile": "1. Profiel",
- "step_seller": "2. Verkopersgegevens",
+ "step_profile": "Profiel",
+ "step_seller": "Verkopergegevens",
"step_links": "Links",
"step_finish": "Afronden",
"entity_question": "Publiceer je als individu of als organisatie?",
- "individual_title": "Individuele Maker",
- "individual_desc": "Zelfstandige ontwikkelaar of hobbyist.",
- "org_title": "Organisatie / Studio",
- "org_desc": "Bedrijf, team of ontwikkelstudio.",
+ "individual_title": "Individuele maker",
+ "individual_desc": "Ideaal voor zelfstandige ontwikkelaars, studenten en hobbyisten. Publiceer gratis of betaalde WebAssembly-plugins onder je persoonlijke profiel.",
+ "org_title": "Organisatie of Studio",
+ "org_desc": "Ideaal voor ontwikkelingsteams, studio's of bedrijven die samen plugins publiceren. Beheer je merkidentiteit en zakelijke uitbetalingen.",
"display_name_label": "Openbare Ontwikkelaars- / Studionaam *",
"display_name_placeholder": "bijv. PixelCraft Studios, JanDev",
"display_name_hint": "Deze naam wordt openbaar getoond op al je gepubliceerde plugins.",
@@ -144,7 +144,19 @@
"btn_continue": "Doorgaan ->",
"btn_back": "Terug",
"btn_complete": "Registratie Voltooien",
- "btn_connect_stripe": "Opslaan en Stripe Koppelen ->"
+ "btn_connect_stripe": "Opslaan en Stripe Koppelen ->",
+ "step_account_type": "Accounttype",
+ "org_type_label": "Organisatietype:",
+ "org_type_placeholder": "Selecteer je organisatietype ▾",
+ "btn_get_started": "Aan de slag ->",
+ "plan_paid_question": "Ben je van plan betaalde plugins te publiceren? *",
+ "option_yes": "Ja",
+ "option_no": "Nee",
+ "step_name": "Naam",
+ "step_monetization": "Monetisatie",
+ "dev_name_title": "Kies je ontwikkelaarsnaam",
+ "dev_name_desc": "Dit is de naam die spelers en servereigenaren op Pumpkin Market zien bij je plugins. Deze mag afwijken van je officiële naam en kan op elk moment worden gewijzigd.",
+ "name_taken_error": "Deze ontwikkelaarsnaam is al in gebruik. Kies een andere naam."
},
"dashboard": {
"overview": "Overzicht",
@@ -163,7 +175,19 @@
"payouts": "Stripe Uitbetalingen",
"payouts_status": "Stripe Connect Status",
"connected": "Verbonden",
- "setup_stripe": "Stripe Uitbetalingen Instellen"
+ "setup_stripe": "Stripe Uitbetalingen Instellen",
+ "create_plugin_title": "Nieuwe plug-in maken",
+ "create_plugin_desc": "Stel je concept-plug-in in. Je kunt de winkelvermelding bewerken, builds uploaden en offline testen voordat je publiceert.",
+ "btn_create_draft": "Concept maken & beheren ->",
+ "status_draft": "Offline (Concept)",
+ "status_published": "Gepubliceerd",
+ "btn_publish_store": "Publiceren in winkel",
+ "btn_unpublish_store": "Offline halen",
+ "publish_checklist_title": "Publiceren op marktplaats",
+ "publish_checklist_desc": "Controleer de vereisten voordat je je plug-in uitbrengt op Pumpkin Market.",
+ "publish_ready_banner": "Aan alle vereisten is voldaan! Je plug-in is klaar voor publicatie.",
+ "publish_missing_banner": "Voldoe aan de ontbrekende vereisten voordat je publiceert.",
+ "btn_confirm_publish": "Bevestigen & in winkel uitbrengen"
},
"upload": {
"title": "Nieuwe Plugin Publiceren",
diff --git a/src/locales/pl.json b/src/locales/pl.json
index 6374b49..73bd3d9 100644
--- a/src/locales/pl.json
+++ b/src/locales/pl.json
@@ -112,15 +112,15 @@
"badge": "Panel Dewelopera",
"subtitle_paid": "Skonfiguruj profil twórcy i dane sprzedawcy, aby publikować i sprzedawać pluginy.",
"subtitle_free": "Skonfiguruj profil twórcy, aby publikować darmowe pluginy dla społeczności Pumpkin.",
- "step_profile": "1. Profil",
- "step_seller": "2. Dane Sprzedawcy",
+ "step_profile": "Profil",
+ "step_seller": "Dane sprzedawcy",
"step_links": "Linki",
"step_finish": "Koniec",
"entity_question": "Publikujesz jako osoba prywatna czy organizacja?",
- "individual_title": "Niezależny Twórca",
- "individual_desc": "Niezależny deweloper lub hobbysta.",
- "org_title": "Organizacja / Studio",
- "org_desc": "Firma, zespół lub studio.",
+ "individual_title": "Indywidualny twórca",
+ "individual_desc": "Idealne dla niezależnych programistów, studentów i pasjonatów. Publikuj darmowe lub płatne pluginy WebAssembly pod własnym profilem.",
+ "org_title": "Organizacja lub Studio",
+ "org_desc": "Idealne dla zespołów deweloperskich, studiów lub firm wspólnie publikujących pluginy. Zarządzaj marką i wypłatami organizacji.",
"display_name_label": "Publiczna Nazwa Dewelopera / Studia *",
"display_name_placeholder": "np. PixelCraft Studios, JanDev",
"display_name_hint": "Ta nazwa będzie widoczna publicznie przy wszystkich Twoich pluginach.",
@@ -144,7 +144,19 @@
"btn_continue": "Dalej ->",
"btn_back": "Wstecz",
"btn_complete": "Zakończ Rejestrację",
- "btn_connect_stripe": "Zapisz i Połącz Stripe ->"
+ "btn_connect_stripe": "Zapisz i Połącz Stripe ->",
+ "step_account_type": "Typ konta",
+ "org_type_label": "Typ organizacji:",
+ "org_type_placeholder": "Wybierz typ organizacji ▾",
+ "btn_get_started": "Rozpocznij ->",
+ "plan_paid_question": "Czy planujesz publikować płatne pluginy? *",
+ "option_yes": "Tak",
+ "option_no": "Nie",
+ "step_name": "Nazwa",
+ "step_monetization": "Monetyzacja",
+ "dev_name_title": "Wybierz swoją nazwę dewelopera",
+ "dev_name_desc": "To jest nazwa, którą gracze i właściciele serwerów zobaczą na Pumpkin Market przy Twoich pluginach. Może różnić się od Twojego oficjalnego nazwiska i możesz ją zmienić w dowolnym momencie.",
+ "name_taken_error": "Ta nazwa dewelopera jest już zajęta. Wybierz inną."
},
"dashboard": {
"overview": "Przegląd",
@@ -163,7 +175,19 @@
"payouts": "Wypłaty Stripe",
"payouts_status": "Status Stripe Connect",
"connected": "Połączono",
- "setup_stripe": "Skonfiguruj Wypłaty Stripe"
+ "setup_stripe": "Skonfiguruj Wypłaty Stripe",
+ "create_plugin_title": "Utwórz nowy plugin",
+ "create_plugin_desc": "Skonfiguruj wersję roboczą pluginu. Możesz edytować stronę w sklepie, wgrywać kompilacje i testować offline przed publikacją.",
+ "btn_create_draft": "Utwórz szkic i zarządzaj ->",
+ "status_draft": "Offline (Szkic)",
+ "status_published": "Opublikowany",
+ "btn_publish_store": "Opublikuj w sklepie",
+ "btn_unpublish_store": "Wyłącz ze sklepu",
+ "publish_checklist_title": "Publikacja na rynku",
+ "publish_checklist_desc": "Sprawdź wymagania przed publikacją pluginu na Pumpkin Market.",
+ "publish_ready_banner": "Wszystkie wymagania spełnione! Twój plugin jest gotowy do publikacji.",
+ "publish_missing_banner": "Uzupełnij brakujące wymagania przed publikacją.",
+ "btn_confirm_publish": "Potwierdź i opublikuj w sklepie"
},
"upload": {
"title": "Opublikuj Nowy Plugin",
diff --git a/src/locales/pt.json b/src/locales/pt.json
index 9c5941b..ed4aa47 100644
--- a/src/locales/pt.json
+++ b/src/locales/pt.json
@@ -112,15 +112,15 @@
"badge": "Portal do Desenvolvedor",
"subtitle_paid": "Configure seu perfil de criador e dados fiscais para publicar e vender plugins.",
"subtitle_free": "Configure seu perfil de criador para publicar plugins gratuitos para a comunidade Pumpkin.",
- "step_profile": "1. Perfil",
- "step_seller": "2. Dados de Vendedor",
+ "step_profile": "Perfil",
+ "step_seller": "Informações do vendedor",
"step_links": "Links",
"step_finish": "Finalizar",
"entity_question": "Você publica como pessoa física ou organização?",
"individual_title": "Criador Individual",
- "individual_desc": "Desenvolvedor independente ou entusiasta.",
- "org_title": "Organização / Estúdio",
- "org_desc": "Empresa, equipe ou estúdio.",
+ "individual_desc": "Ideal para desenvolvedores independentes, estudantes e criadores amadores. Publique plugins WebAssembly gratuitos ou premium em seu perfil pessoal.",
+ "org_title": "Organização ou Estúdio",
+ "org_desc": "Ideal para equipes de desenvolvimento, estúdios ou empresas que publicam plugins juntos. Gerencie a identidade da marca e os pagamentos corporativos.",
"display_name_label": "Nome Público de Desenvolvedor / Estúdio *",
"display_name_placeholder": "ex: PixelCraft Studios, DevAlex",
"display_name_hint": "Este nome será visível publicamente em todos os seus plugins.",
@@ -144,7 +144,19 @@
"btn_continue": "Continuar ->",
"btn_back": "Voltar",
"btn_complete": "Concluir Cadastro",
- "btn_connect_stripe": "Salvar e Conectar Stripe ->"
+ "btn_connect_stripe": "Salvar e Conectar Stripe ->",
+ "step_account_type": "Tipo de conta",
+ "org_type_label": "Tipo de organização:",
+ "org_type_placeholder": "Selecione o tipo de organização ▾",
+ "btn_get_started": "Começar ->",
+ "plan_paid_question": "Você planeja publicar plugins pagos? *",
+ "option_yes": "Sim",
+ "option_no": "Não",
+ "step_name": "Nome",
+ "step_monetization": "Monetização",
+ "dev_name_title": "Escolha o seu nome de desenvolvedor",
+ "dev_name_desc": "Este é o nome que jogadores e administradores verão no Pumpkin Market nos seus plugins. Pode ser diferente do seu nome legal e pode ser alterado a qualquer momento.",
+ "name_taken_error": "Este nome de desenvolvedor já está em uso. Por favor, escolha outro."
},
"dashboard": {
"overview": "Visão Geral",
@@ -163,7 +175,19 @@
"payouts": "Repasses Stripe",
"payouts_status": "Status do Stripe Connect",
"connected": "Conectado",
- "setup_stripe": "Configurar Repasses Stripe"
+ "setup_stripe": "Configurar Repasses Stripe",
+ "create_plugin_title": "Criar novo plugin",
+ "create_plugin_desc": "Configure seu rascunho de plugin. Você pode editar a página da loja, enviar compilações e testar offline antes de publicar.",
+ "btn_create_draft": "Criar rascunho e gerenciar ->",
+ "status_draft": "Offline (Rascunho)",
+ "status_published": "Publicado",
+ "btn_publish_store": "Publicar na loja",
+ "btn_unpublish_store": "Tirar do ar",
+ "publish_checklist_title": "Publicar no Marketplace",
+ "publish_checklist_desc": "Revise os requisitos antes de lançar seu plugin no Pumpkin Market.",
+ "publish_ready_banner": "Todos os requisitos foram atendidos! Seu plugin está pronto para ser lançado.",
+ "publish_missing_banner": "Por favor, complete os requisitos pendentes antes de publicar.",
+ "btn_confirm_publish": "Confirmar e lançar na loja"
},
"upload": {
"title": "Publicar Novo Plugin",
diff --git a/src/locales/ru.json b/src/locales/ru.json
index f695144..c531495 100644
--- a/src/locales/ru.json
+++ b/src/locales/ru.json
@@ -112,15 +112,15 @@
"badge": "Портал разработчика",
"subtitle_paid": "Настройте профиль автора и данные продавца для публикации и продажи плагинов.",
"subtitle_free": "Настройте профиль автора для публикации бесплатных плагинов для сообщества Pumpkin.",
- "step_profile": "1. Профиль",
- "step_seller": "2. Данные продавца",
+ "step_profile": "Профиль",
+ "step_seller": "Данные продавца",
"step_links": "Ссылки",
"step_finish": "Завершение",
"entity_question": "Вы публикуете плагины как физлицо или организация?",
- "individual_title": "Индивидуальный автор",
- "individual_desc": "Независимый разработчик или энтузиаст.",
- "org_title": "Организация / Студия",
- "org_desc": "Компания, команда или студия.",
+ "individual_title": "Индивидуальный разработчик",
+ "individual_desc": "Идеально для независимых разработчиков, студентов и энтузиастов. Публикуйте бесплатные или платные WebAssembly-плагины под личным профилем.",
+ "org_title": "Организация или студия",
+ "org_desc": "Идеально для команд разработчиков, студий или компаний, публикующих плагины вместе. Управляйте брендом и выплатами организации.",
"display_name_label": "Публичное имя разработчика / студии *",
"display_name_placeholder": "напр. PixelCraft Studios, AlexDev",
"display_name_hint": "Это имя будет отображаться на всех ваших опубликованных плагинах.",
@@ -144,7 +144,19 @@
"btn_continue": "Продолжить ->",
"btn_back": "Назад",
"btn_complete": "Завершить регистрацию",
- "btn_connect_stripe": "Сохранить и подключить Stripe ->"
+ "btn_connect_stripe": "Сохранить и подключить Stripe ->",
+ "step_account_type": "Тип аккаунта",
+ "org_type_label": "Тип организации:",
+ "org_type_placeholder": "Выберите тип организации ▾",
+ "btn_get_started": "Начать ->",
+ "plan_paid_question": "Планируете ли вы публиковать платные плагины? *",
+ "option_yes": "Да",
+ "option_no": "Нет",
+ "step_name": "Имя",
+ "step_monetization": "Монетизация",
+ "dev_name_title": "Выберите имя разработчика",
+ "dev_name_desc": "Это имя игроки и владельцы серверов увидят в Pumpkin Market на ваших плагинах. Оно может отличаться от юридического имени, и его можно изменить в любое время.",
+ "name_taken_error": "Это имя разработчика уже занято. Пожалуйста, выберите другое."
},
"dashboard": {
"overview": "Обзор",
@@ -163,7 +175,19 @@
"payouts": "Выплаты Stripe",
"payouts_status": "Статус Stripe Connect",
"connected": "Подключено",
- "setup_stripe": "Настроить выплаты Stripe"
+ "setup_stripe": "Настроить выплаты Stripe",
+ "create_plugin_title": "Создать новый плагин",
+ "create_plugin_desc": "Создайте черновик плагина. Вы можете настроить страницу в магазине, загрузить сборки и протестировать перед публикацией.",
+ "btn_create_draft": "Создать черновик и перейти ->",
+ "status_draft": "Оффлайн (Черновик)",
+ "status_published": "Опубликован",
+ "btn_publish_store": "Опубликовать в магазине",
+ "btn_unpublish_store": "Снять с публикации",
+ "publish_checklist_title": "Публикация на торговой площадке",
+ "publish_checklist_desc": "Проверьте требования перед выпуском плагина в Pumpkin Market.",
+ "publish_ready_banner": "Все требования выполнены! Ваш плагин готов к публикации.",
+ "publish_missing_banner": "Пожалуйста, выполните недостающие требования перед публикацией.",
+ "btn_confirm_publish": "Подтвердить и выпустить в магазин"
},
"upload": {
"title": "Публикация нового плагина",
diff --git a/src/locales/tr.json b/src/locales/tr.json
index e9eefff..3d5fb84 100644
--- a/src/locales/tr.json
+++ b/src/locales/tr.json
@@ -112,15 +112,15 @@
"badge": "Geliştirici Portalı",
"subtitle_paid": "Eklenti yayınlamak ve satmak için üretici profilinizi ve satıcı bilgilerinizi ayarlayın.",
"subtitle_free": "Pumpkin topluluğu için ücretsiz eklentiler yayınlamak amacıyla profilinizi oluşturun.",
- "step_profile": "1. Profil",
- "step_seller": "2. Satıcı Bilgileri",
+ "step_profile": "Profil",
+ "step_seller": "Satıcı bilgileri",
"step_links": "Bağlantılar",
"step_finish": "Tamamla",
"entity_question": "Bireysel mi yoksa kuruluş olarak mı yayınlıyorsunuz?",
"individual_title": "Bireysel Geliştirici",
- "individual_desc": "Bağımsız geliştirici veya hobi üreticisi.",
- "org_title": "Kuruluş / Stüdyo",
- "org_desc": "Şirket, ekip veya stüdyo.",
+ "individual_desc": "Bağımsız geliştiriciler, öğrenciler ve hobi amaçlı içerik üreticileri için uygundur. Ücretsiz veya ücretli WebAssembly eklentilerini kişisel profilinizle yayınlayın.",
+ "org_title": "Kuruluş veya Stüdyo",
+ "org_desc": "Birlikte eklenti yayınlayan geliştirici ekipleri, stüdyolar veya şirketler için uygundur. Marka kimliğinizi ve kurumsal ödemelerinizi yönetin.",
"display_name_label": "Geliştirici / Stüdyo Görünen Adı *",
"display_name_placeholder": "örn. PixelCraft Studios, CanDev",
"display_name_hint": "Bu isim yayınladığınız tüm eklentilerde herkese açık olarak görünecektir.",
@@ -144,7 +144,19 @@
"btn_continue": "Devam Et ->",
"btn_back": "Geri",
"btn_complete": "Kaydı Tamamla",
- "btn_connect_stripe": "Kaydet ve Stripe'a Bağla ->"
+ "btn_connect_stripe": "Kaydet ve Stripe'a Bağla ->",
+ "step_account_type": "Hesap türü",
+ "org_type_label": "Kuruluş türü:",
+ "org_type_placeholder": "Kuruluş türünüzü seçin ▾",
+ "btn_get_started": "Başlayın ->",
+ "plan_paid_question": "Ücretli eklentiler yayınlamayı planlıyor musunuz? *",
+ "option_yes": "Evet",
+ "option_no": "Hayır",
+ "step_name": "Ad",
+ "step_monetization": "Para Kazanma",
+ "dev_name_title": "Geliştirici adınızı seçin",
+ "dev_name_desc": "Bu isim, oyuncuların ve sunucu sahiplerinin Pumpkin Market üzerinde eklentilerinizde göreceği isimdir. Yasal adınızdan farklı olabilir ve istediğiniz zaman değiştirebilirsiniz.",
+ "name_taken_error": "Bu geliştirici adı zaten alınmış. Lütfen başka bir ad seçin."
},
"dashboard": {
"overview": "Genel Bakış",
@@ -163,7 +175,19 @@
"payouts": "Stripe Ödemeleri",
"payouts_status": "Stripe Connect Durumu",
"connected": "Bağlandı",
- "setup_stripe": "Stripe Ödemelerini Ayarla"
+ "setup_stripe": "Stripe Ödemelerini Ayarla",
+ "create_plugin_title": "Yeni Eklenti Oluştur",
+ "create_plugin_desc": "Eklenti taslağınızı oluşturun. Yayınlamadan önce mağaza sayfasını düzenleyebilir, derlemeleri yükleyebilir ve çevrimdışı test edebilirsiniz.",
+ "btn_create_draft": "Taslak Oluştur & Yönet ->",
+ "status_draft": "Çevrimdışı (Taslak)",
+ "status_published": "Yayınlandı",
+ "btn_publish_store": "Mağazada Yayınla",
+ "btn_unpublish_store": "Yayından Kaldır",
+ "publish_checklist_title": "Pazaryerinde Yayınla",
+ "publish_checklist_desc": "Eklentinizi Pumpkin Market'te yayınlamadan önce gereksinimleri kontrol edin.",
+ "publish_ready_banner": "Tüm gereksinimler karşılandı! Eklentiniz yayına hazır.",
+ "publish_missing_banner": "Lütfen yayınlamadan önce eksik gereksinimleri tamamlayın.",
+ "btn_confirm_publish": "Onayla ve Mağazada Yayınla"
},
"upload": {
"title": "Yeni Eklenti Yayınla",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index b5f1af4..23f894e 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -112,15 +112,15 @@
"badge": "开发者门户",
"subtitle_paid": "设置您的创作者档案和商家信息以发布并销售插件。",
"subtitle_free": "设置您的创作者档案以为 Pumpkin 社区发布免费开源插件。",
- "step_profile": "1. 基本档案",
- "step_seller": "2. 商家信息",
+ "step_profile": "个人资料",
+ "step_seller": "卖家信息",
"step_links": "社交链接",
"step_finish": "完成",
"entity_question": "您是以个人还是组织/工作室名义发布?",
- "individual_title": "独立创作者",
- "individual_desc": "独立开发者或个人爱好者。",
- "org_title": "组织 / 工作室",
- "org_desc": "公司、团队或工作室。",
+ "individual_title": "个人创作者",
+ "individual_desc": "适合独立开发者、学生和社区爱好者。以个人身份发布免费或付费 WebAssembly 插件并获取收益。",
+ "org_title": "组织或工作室",
+ "org_desc": "适合共同发布插件的开发团队、工作室或注册企业。管理团队品牌标识与组织结算。",
"display_name_label": "开发者 / 工作室公开显示名称 *",
"display_name_placeholder": "例如:PixelCraft Studios, AlexDev",
"display_name_hint": "此名称将公开显示在您发布的所有插件页面上。",
@@ -144,7 +144,19 @@
"btn_continue": "继续 ->",
"btn_back": "返回",
"btn_complete": "完成注册",
- "btn_connect_stripe": "保存并绑定 Stripe ->"
+ "btn_connect_stripe": "保存并绑定 Stripe ->",
+ "step_account_type": "账户类型",
+ "org_type_label": "组织类型:",
+ "org_type_placeholder": "选择您的组织类型 ▾",
+ "btn_get_started": "立即开始 ->",
+ "plan_paid_question": "您是否计划发布付费插件? *",
+ "option_yes": "是",
+ "option_no": "否",
+ "step_name": "名称",
+ "step_monetization": "变现设置",
+ "dev_name_title": "设置您的开发者名称",
+ "dev_name_desc": "这是玩家和服主在 Pumpkin Market 上浏览您的插件时看到的公开名称。它无需与您的真实姓名相同,且日后随时可以修改。",
+ "name_taken_error": "此开发者名称已被占用,请选择其他名称。"
},
"dashboard": {
"overview": "数据概览",
@@ -163,7 +175,19 @@
"payouts": "Stripe 结算",
"payouts_status": "Stripe Connect 状态",
"connected": "已连接",
- "setup_stripe": "设置 Stripe 结算账户"
+ "setup_stripe": "设置 Stripe 结算账户",
+ "create_plugin_title": "创建新插件",
+ "create_plugin_desc": "设置您的插件草稿。您可以在发布前配置商店页面、上传构建文件并进行离线测试。",
+ "btn_create_draft": "创建草稿并管理 ->",
+ "status_draft": "离线 (草稿)",
+ "status_published": "已发布",
+ "btn_publish_store": "发布至商店",
+ "btn_unpublish_store": "下架离线",
+ "publish_checklist_title": "发布至市场",
+ "publish_checklist_desc": "在将插件发布到 Pumpkin Market 前检查所有发布要求。",
+ "publish_ready_banner": "所有要求均已达成!您的插件已准备好上线发布。",
+ "publish_missing_banner": "请在发布前完善所有缺失的要求。",
+ "btn_confirm_publish": "确认并正式发布至商店"
},
"upload": {
"title": "发布新插件",
diff --git a/src/main.tsx b/src/main.tsx
index 8ea3083..aa00a56 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,20 +1,7 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
-import '@fontsource/inter/400.css'
-import '@fontsource/inter/500.css'
-import '@fontsource/inter/600.css'
-import '@fontsource/inter/700.css'
-import '@fontsource/inter/800.css'
-import '@fontsource/inter/900.css'
-import '@fontsource/jetbrains-mono/700.css'
-import '@fontsource/nunito/400.css'
-import '@fontsource/nunito/500.css'
-import '@fontsource/nunito/600.css'
-import '@fontsource/nunito/700.css'
-import '@fontsource/nunito/800.css'
-import '@fontsource/dm-sans/400.css'
-import '@fontsource/dm-sans/500.css'
-import '@fontsource/dm-sans/700.css'
+import '@fontsource-variable/inter'
+import '@fontsource-variable/jetbrains-mono'
import './index.css'
import App from './App.tsx'
import './i18n'
diff --git a/src/pages/AuthorProfilePage.tsx b/src/pages/AuthorProfilePage.tsx
index 0220d29..172dbd1 100644
--- a/src/pages/AuthorProfilePage.tsx
+++ b/src/pages/AuthorProfilePage.tsx
@@ -1,13 +1,21 @@
import { useState, useEffect } from 'react';
-import { useParams, Link } from 'react-router-dom';
+import { useParams } from 'react-router-dom';
import api from '../api';
-import { useTranslation } from 'react-i18next';
import SEO from '../components/SEO';
-import { getPluginUrl } from '../utils/url';
+import PluginCard from '../components/PluginCard';
+
+const getAccentColor = (name: string) => {
+ const colors = [
+ '#4f7eff', '#ff6b6b', '#ffd166', '#06d6a0',
+ '#a855f7', '#f97316', '#06b6d4', '#ec4899',
+ ];
+ let hash = 0;
+ for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
+ return colors[Math.abs(hash) % colors.length];
+};
const AuthorProfilePage = () => {
const { username } = useParams();
- const { i18n } = useTranslation();
const [author, setAuthor] = useState
(null);
const [plugins, setPlugins] = useState([]);
const [loading, setLoading] = useState(true);
@@ -36,44 +44,6 @@ const AuthorProfilePage = () => {
});
}, [username]);
- const getDescription = (translatedStr: string) => {
- try {
- const data = JSON.parse(translatedStr);
- const currentLang = i18n.language.split('-')[0];
- return data[currentLang] || data.en || Object.values(data)[0] || 'No description available.';
- } catch {
- return 'No description available.';
- }
- };
-
- const getAccentColor = (name: string) => {
- const colors = [
- '#4f7eff', '#ff6b6b', '#ffd166', '#06d6a0',
- '#a855f7', '#f97316', '#06b6d4', '#ec4899',
- ];
- let hash = 0;
- for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash);
- return colors[Math.abs(hash) % colors.length];
- };
-
- const getEffectivePrice = (plugin: any): { display: string; isSale: boolean; originalDisplay: string } => {
- const base = plugin.price_cents ?? 0;
- const saleActive = plugin.sale_active && plugin.sale_discount_percent > 0;
- if (saleActive) {
- const saleCents = Math.round(base * (1 - plugin.sale_discount_percent / 100));
- return {
- display: `€${(saleCents / 100).toFixed(2)}`,
- isSale: true,
- originalDisplay: `€${(base / 100).toFixed(2)}`,
- };
- }
- return {
- display: `€${(base / 100).toFixed(2)}`,
- isSale: false,
- originalDisplay: '',
- };
- };
-
if (loading) return LOADING...
;
if (error || !author) return {error || 'User not found'}
;
@@ -130,65 +100,9 @@ const AuthorProfilePage = () => {
Plugins by {author.username}
- {plugins.map((plugin) => {
- const accent = getAccentColor(plugin.name);
- const desc = getDescription(plugin.translated_descriptions);
- const initial = plugin.name.charAt(0).toUpperCase();
- const priceInfo = getEffectivePrice(plugin);
- const isSale = plugin.type === 'paid' && priceInfo.isSale;
- const imageUrl = plugin.preview_path;
-
- return (
-
-
-
- {imageUrl ? (
-

- ) : (
-
- {initial}
-
- )}
-
-
{plugin.category}
- {plugin.type === 'free' ? (
-
Free
- ) : isSale ? (
-
- {priceInfo.originalDisplay}
- {priceInfo.display}
- -{plugin.sale_discount_percent}%
-
- ) : (
-
{priceInfo.display}
- )}
-
-
-
{plugin.name}
-
by {plugin.dev_name}
-
{desc}
-
-
-
- {(plugin.downloads ?? 0).toLocaleString()}
-
- {plugin.version && (
-
- v{plugin.version}
-
- )}
-
-
- {isSale && (
-
Sale — {plugin.sale_discount_percent}% off
- )}
-
-
-
- );
- })}
+ {plugins.map((plugin) => (
+
+ ))}
{plugins.length === 0 && (
diff --git a/src/pages/PluginDetail.tsx b/src/pages/PluginDetail.tsx
index bfe5a20..3fefb42 100644
--- a/src/pages/PluginDetail.tsx
+++ b/src/pages/PluginDetail.tsx
@@ -659,10 +659,9 @@ const PluginDetail = () => {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
- fontSize: '1.2rem',
flexShrink: 0
}}>
- ⏳
+
diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx
index 4f170d1..6bb4e55 100644
--- a/src/pages/ProfilePage.tsx
+++ b/src/pages/ProfilePage.tsx
@@ -83,11 +83,12 @@ const Field: React.FC
= ({ label, hint, children }) => (
interface SaveBtnProps {
isSaving?: boolean;
+ disabled?: boolean;
label?: string;
}
-const SaveBtn: React.FC = ({ isSaving = false, label = 'Save Changes' }) => (
-
-
navigate('/dashboard/add-plugin')}>
+ setIsCreateModalOpen(true)}>
{t('developer.dashboard.add_plugin')}
@@ -199,50 +240,47 @@ const DashboardPlugins = () => {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
+ fontSize: '1.25rem',
fontWeight: 700,
color: '#f97316',
- fontSize: '1.2rem',
flexShrink: 0,
border: '1px solid rgba(249, 115, 22, 0.3)'
}}>
- {!req.preview_url && req.plugin_name.charAt(0).toUpperCase()}
+ {!req.preview_url && req.plugin_name?.charAt(0).toUpperCase()}
-
-
- {req.plugin_name}
-
-
- Ownership Transfer Request
-
+
+ {req.plugin_name}
+ {req.plugin_category && (
+
+ {req.plugin_category}
+
+ )}
+
+
+ Transfer request from @{req.sender_name}
-
- @{req.sender_name} has requested to transfer ownership of this plugin to your account.
-
-
+
handleAcceptTransfer(req.id)}
+ className="btn btn-secondary"
disabled={transferLoadingId === req.id}
+ onClick={() => handleRejectTransfer(req.id)}
+ style={{ padding: '0.45rem 0.9rem', fontSize: '0.82rem', color: '#f87171' }}
>
-
- {transferLoadingId === req.id ? 'Accepting…' : 'Accept Transfer'}
+
+ Decline
handleRejectTransfer(req.id)}
+ className="btn btn-primary"
disabled={transferLoadingId === req.id}
+ onClick={() => handleAcceptTransfer(req.id)}
+ style={{ padding: '0.45rem 0.9rem', fontSize: '0.82rem' }}
>
- Decline
+
+ {transferLoadingId === req.id ? 'Accepting…' : 'Accept Transfer'}
@@ -317,6 +355,7 @@ const DashboardPlugins = () => {
const isPaid = pluginType === 'paid' || (p.priceCents && p.priceCents > 0);
const priceFormatted = isPaid ? `€${((p.priceCents || 0) / 100).toFixed(2)}` : 'Free';
const initial = (p.name || '?').charAt(0).toUpperCase();
+ const isDraft = p.status === 'draft';
return (
@@ -403,9 +442,9 @@ const DashboardPlugins = () => {
{/* Visibility & Type */}
-
-
- Public
+
+
+ {isDraft ? t('developer.dashboard.status_draft', 'Offline (Draft)') : t('developer.dashboard.status_published', 'Published')}
{isPaid ? priceFormatted : 'Free'}
@@ -468,10 +507,10 @@ const DashboardPlugins = () => {
- {searchTerm ? 'No plugins matched your filter.' : 'No plugins published yet.'}
+ {searchTerm ? 'No plugins matched your filter.' : 'No plugins created yet.'}
- {searchTerm ? 'Try adjusting your search or category filter.' : 'Click "Add New Plugin" to publish your first creation.'}
+ {searchTerm ? 'Try adjusting your search or category filter.' : 'Click "Add New Plugin" to create your first plugin.'}
|
@@ -481,6 +520,108 @@ const DashboardPlugins = () => {
+
+ {/* Create New Plugin Draft Modal */}
+ {isCreateModalOpen && (
+ setIsCreateModalOpen(false)}>
+
e.stopPropagation()}>
+
setIsCreateModalOpen(false)} aria-label="Close">
+
+
+
+
+
{t('developer.dashboard.create_plugin_title', 'Create New Plugin')}
+
{t('developer.dashboard.create_plugin_desc', 'Set up your plugin draft. You can configure your store listing, upload builds, and test offline before publishing.')}
+
+
+ {createError && (
+
+ )}
+
+
+
+
+ )}
);
};
diff --git a/src/pages/dashboard/plugin/AddPlugin.tsx b/src/pages/dashboard/plugin/AddPlugin.tsx
index bb83d17..79b8c1e 100644
--- a/src/pages/dashboard/plugin/AddPlugin.tsx
+++ b/src/pages/dashboard/plugin/AddPlugin.tsx
@@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
import {
Globe, ImageIcon, Plus, Search, X, Upload, FileCode,
DollarSign, CheckCircle, ChevronRight, ChevronLeft,
- Zap, Lock, AlertTriangle, Tag, Store, User, Sparkles,
+ Zap, Lock, AlertTriangle, Tag, Store, User,
ShieldCheck,
} from 'lucide-react';
import api from '../../../api';
@@ -168,7 +168,6 @@ const LangPicker = ({ usedCodes, onAdd, onClose }: { usedCodes: string[]; onAdd:
const AddPlugin = () => {
const navigate = useNavigate();
const { user, refreshUser } = useAuth();
- const [isDevModalOpen, setIsDevModalOpen] = useState(false);
const [step, setStep] = useState(0);
const [submitting, setSubmitting] = useState(false);
const [done, setDone] = useState(false);
@@ -184,38 +183,10 @@ const AddPlugin = () => {
if (user && !user.is_developer) {
return (
-
-
-
-
-
- Developer Profile Required
-
-
- Before publishing plugins on Pumpkin Market, you must complete your developer profile. It takes less than a minute!
-
-
setIsDevModalOpen(true)}
- >
- Set Up Developer Profile
-
-
-
setIsDevModalOpen(false)}
- onSuccess={() => refreshUser?.()}
- />
-
+
refreshUser?.()}
+ />
);
}
diff --git a/src/pages/dashboard/plugin/ManagePlugin.css b/src/pages/dashboard/plugin/ManagePlugin.css
index bfb2149..dd0f973 100644
--- a/src/pages/dashboard/plugin/ManagePlugin.css
+++ b/src/pages/dashboard/plugin/ManagePlugin.css
@@ -208,6 +208,110 @@
background: var(--mp-bg);
}
+/* ── Top Plugin Management Status Bar ────────────────────────── */
+.mp-top-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1.5rem;
+ padding: 1.25rem 1.75rem;
+ margin-bottom: 2rem;
+ background: var(--mp-surface);
+ border: 1px solid var(--mp-border);
+ border-radius: var(--mp-radius);
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+ flex-wrap: wrap;
+}
+
+.mp-top-bar-left {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.mp-status-pill {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ padding: 0.35rem 0.85rem;
+ border-radius: 99px;
+ font-size: 0.8rem;
+ font-weight: 600;
+}
+
+.mp-status-pill.published {
+ background: rgba(16, 185, 129, 0.15);
+ border: 1px solid rgba(16, 185, 129, 0.35);
+ color: #34d399;
+}
+
+.mp-status-pill.draft {
+ background: rgba(148, 163, 184, 0.12);
+ border: 1px solid rgba(148, 163, 184, 0.25);
+ color: #94a3b8;
+}
+
+.mp-status-dot-pulse {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: currentColor;
+ box-shadow: 0 0 8px currentColor;
+}
+
+.mp-top-bar-desc {
+ font-size: 0.82rem;
+ color: var(--mp-text-2);
+}
+
+.mp-top-bar-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
+.mp-action-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ padding: 0.55rem 1rem;
+ border-radius: var(--mp-radius-sm);
+ font-size: 0.85rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ border: 1px solid transparent;
+ text-decoration: none;
+}
+
+.mp-action-btn-primary {
+ background: #f97316;
+ color: #fff;
+}
+.mp-action-btn-primary:hover {
+ background: #ea580c;
+}
+
+.mp-action-btn-secondary {
+ background: var(--mp-surface-2);
+ border-color: var(--mp-border);
+ color: var(--mp-text);
+}
+.mp-action-btn-secondary:hover {
+ background: rgba(255, 255, 255, 0.08);
+ border-color: var(--mp-border-2);
+}
+
+.mp-action-btn-danger {
+ background: rgba(239, 68, 68, 0.1);
+ border-color: rgba(239, 68, 68, 0.25);
+ color: #f87171;
+}
+.mp-action-btn-danger:hover {
+ background: rgba(239, 68, 68, 0.2);
+}
+
/* ── Shared Tab Components ──────────────────────────────────── */
.mp-tab-header {
margin-bottom: 2rem;
diff --git a/src/pages/dashboard/plugin/ManagePlugin.tsx b/src/pages/dashboard/plugin/ManagePlugin.tsx
index 1afdc1a..8996e67 100644
--- a/src/pages/dashboard/plugin/ManagePlugin.tsx
+++ b/src/pages/dashboard/plugin/ManagePlugin.tsx
@@ -1,10 +1,12 @@
import { useState, useEffect } from 'react';
-import { useNavigate, useParams } from 'react-router-dom';
+import { useNavigate, useParams, Link } from 'react-router-dom';
import api from '../../../api';
import { useAuth } from '../../../App';
+import { useTranslation } from 'react-i18next';
import {
LayoutGrid, Tag, Upload, DollarSign, Trash2,
- Circle, BarChart3, Key, Star, ArrowRightLeft
+ Circle, BarChart3, Key, Star, ArrowRightLeft,
+ Rocket, Eye, PowerOff
} from 'lucide-react';
import StoreListing from './StoreListing';
@@ -16,6 +18,8 @@ import PluginReviewsTab from './PluginReviewsTab';
import TransferOwnership from './TransferOwnership';
import DangerZone from './DangerZone';
import PluginAnalyticsTab from './PluginAnalyticsTab';
+import PublishReadinessModal from './PublishReadinessModal';
+import { getPluginUrl } from '../../../utils/url';
import './ManagePlugin.css';
export type PluginData = {
@@ -53,12 +57,15 @@ const NAV_ITEMS = [
];
const ManagePlugin = () => {
+ const { t } = useTranslation();
const { id } = useParams<{ id: string }>();
const { user } = useAuth();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState('listing');
const [loading, setLoading] = useState(true);
const [plugin, setPlugin] = useState(null);
+ const [isPublishModalOpen, setIsPublishModalOpen] = useState(false);
+ const [unpublishing, setUnpublishing] = useState(false);
useEffect(() => {
if (user) {
@@ -84,6 +91,24 @@ const ManagePlugin = () => {
const refreshPlugin = () => fetchPlugin();
+ const handleUnpublish = async () => {
+ if (!plugin) return;
+ const confirm = window.confirm(
+ `Take "${plugin.name}" offline?\n\nThis will remove the plugin from marketplace search and category browsing until you publish it again.`
+ );
+ if (!confirm) return;
+
+ setUnpublishing(true);
+ try {
+ await api.post(`/plugins/${plugin.id}/unpublish`);
+ await fetchPlugin();
+ } catch (err: any) {
+ alert(err.response?.data?.error || 'Failed to unpublish plugin.');
+ } finally {
+ setUnpublishing(false);
+ }
+ };
+
if (loading) {
return (
@@ -95,11 +120,7 @@ const ManagePlugin = () => {
if (!plugin) return null;
- const statusColor = {
- published: 'var(--mp-success)',
- draft: 'var(--mp-muted)',
- review: 'var(--mp-warn)',
- }[plugin.status ?? 'published'];
+ const isLive = plugin.status === 'published';
return (
@@ -112,9 +133,9 @@ const ManagePlugin = () => {
{plugin.name}
-
-
- {plugin.status ?? 'published'}
+
+
+ {isLive ? t('developer.dashboard.status_published', 'Published') : t('developer.dashboard.status_draft', 'Offline (Draft)')}
@@ -145,6 +166,62 @@ const ManagePlugin = () => {
{/* ── Main Content ──────────────────────────────────────── */}
+ {/* Top Google/Apple/Steam style publishing & status bar */}
+
+
+
+
+ {isLive ? t('developer.dashboard.status_published', 'Published') : t('developer.dashboard.status_draft', 'Offline (Draft)')}
+
+
+ {isLive
+ ? 'This plugin is public. You can publish updates or manage store details.'
+ : 'Draft changes are private. Complete your store listing & binary build, then publish.'}
+
+
+
+
+ {!isLive ? (
+ <>
+
setIsPublishModalOpen(true)}
+ title="Check what requirements remain before publishing"
+ >
+ Check Readiness
+
+
setIsPublishModalOpen(true)}
+ >
+
+ {t('developer.dashboard.btn_publish_store', 'Publish to Store')}
+
+ >
+ ) : (
+ <>
+
+
+ View on Store
+
+
+
+ {unpublishing ? 'Unpublishing…' : t('developer.dashboard.btn_unpublish_store', 'Take Offline')}
+
+ >
+ )}
+
+
+
{activeTab === 'listing' && (
)}
@@ -173,6 +250,17 @@ const ManagePlugin = () => {
)}
+
+ {/* Readiness & Publish Modal */}
+
setIsPublishModalOpen(false)}
+ onPublished={refreshPlugin}
+ onNavigateTab={(tabKey) => setActiveTab(tabKey)}
+ />
);
};
diff --git a/src/pages/dashboard/plugin/PublishReadinessModal.css b/src/pages/dashboard/plugin/PublishReadinessModal.css
new file mode 100644
index 0000000..602bcea
--- /dev/null
+++ b/src/pages/dashboard/plugin/PublishReadinessModal.css
@@ -0,0 +1,277 @@
+.prm-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.75);
+ backdrop-filter: blur(6px);
+ z-index: 999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.prm-container {
+ background: var(--mp-surface, #151820);
+ border: 1px solid var(--mp-border, #252a38);
+ border-radius: 14px;
+ width: 100%;
+ max-width: 620px;
+ max-height: 90vh;
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ box-shadow: 0 24px 48px rgba(0, 0, 0, 0.6);
+ color: var(--mp-text, #e8eaf2);
+ overflow: hidden;
+}
+
+.prm-close-btn {
+ position: absolute;
+ top: 1.25rem;
+ right: 1.25rem;
+ background: transparent;
+ border: none;
+ color: var(--mp-text-3, #5a6075);
+ cursor: pointer;
+ padding: 0.35rem;
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.15s ease;
+}
+
+.prm-close-btn:hover {
+ color: #fff;
+ background: rgba(255, 255, 255, 0.08);
+}
+
+.prm-header {
+ padding: 1.5rem 1.75rem 1.25rem;
+ border-bottom: 1px solid var(--mp-border, #252a38);
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.prm-header-icon {
+ width: 44px;
+ height: 44px;
+ border-radius: 10px;
+ background: rgba(249, 115, 22, 0.12);
+ border: 1px solid rgba(249, 115, 22, 0.25);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.prm-title {
+ font-size: 1.2rem;
+ font-weight: 700;
+ margin: 0 0 0.2rem 0;
+}
+
+.prm-subtitle {
+ font-size: 0.85rem;
+ color: var(--mp-text-2, #8b91a8);
+ margin: 0;
+}
+
+.prm-alert {
+ margin: 1rem 1.75rem 0;
+ padding: 0.75rem 1rem;
+ border-radius: 8px;
+ font-size: 0.85rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.prm-alert-error {
+ background: rgba(239, 68, 68, 0.12);
+ border: 1px solid rgba(239, 68, 68, 0.3);
+ color: #f87171;
+}
+
+.prm-alert-success {
+ background: rgba(16, 185, 129, 0.12);
+ border: 1px solid rgba(16, 185, 129, 0.3);
+ color: #34d399;
+}
+
+.prm-loading {
+ padding: 3rem 2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 0.75rem;
+ color: var(--mp-text-2, #8b91a8);
+ font-size: 0.9rem;
+}
+
+.prm-spin {
+ animation: prm-rotate 1s linear infinite;
+ color: #f97316;
+}
+
+@keyframes prm-rotate {
+ to { transform: rotate(360deg); }
+}
+
+.prm-body {
+ padding: 1.25rem 1.75rem;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.prm-status-banner {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.75rem;
+ padding: 1rem;
+ border-radius: 10px;
+}
+
+.prm-status-banner h4 {
+ font-size: 0.95rem;
+ font-weight: 600;
+ margin: 0 0 0.15rem 0;
+}
+
+.prm-status-banner p {
+ font-size: 0.8rem;
+ margin: 0;
+ opacity: 0.85;
+}
+
+.prm-status-banner-ready {
+ background: rgba(16, 185, 129, 0.1);
+ border: 1px solid rgba(16, 185, 129, 0.3);
+ color: #34d399;
+}
+
+.prm-status-banner-missing {
+ background: rgba(245, 158, 11, 0.1);
+ border: 1px solid rgba(245, 158, 11, 0.3);
+ color: #fbbf24;
+}
+
+.prm-checklist {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ background: var(--mp-surface-2, #1c2030);
+ border: 1px solid var(--mp-border, #252a38);
+ border-radius: 10px;
+ padding: 0.5rem;
+}
+
+.prm-check-row {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.65rem 0.75rem;
+ border-radius: 6px;
+ background: transparent;
+ transition: background 0.15s ease;
+}
+
+.prm-check-row.ready {
+ background: rgba(16, 185, 129, 0.03);
+}
+
+.prm-check-row.missing {
+ background: rgba(239, 68, 68, 0.04);
+}
+
+.prm-check-status {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
+
+.prm-check-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.prm-check-title {
+ font-size: 0.88rem;
+ font-weight: 600;
+ color: var(--mp-text, #e8eaf2);
+}
+
+.prm-check-desc {
+ font-size: 0.76rem;
+ color: var(--mp-text-2, #8b91a8);
+ margin-top: 0.1rem;
+}
+
+.prm-fix-btn {
+ padding: 0.35rem 0.65rem;
+ font-size: 0.76rem;
+ font-weight: 600;
+ color: #f97316;
+ background: rgba(249, 115, 22, 0.1);
+ border: 1px solid rgba(249, 115, 22, 0.25);
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ white-space: nowrap;
+}
+
+.prm-fix-btn:hover {
+ background: rgba(249, 115, 22, 0.2);
+ color: #fff;
+}
+
+.prm-footer {
+ padding: 1.25rem 1.75rem;
+ border-top: 1px solid var(--mp-border, #252a38);
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 0.75rem;
+ background: var(--mp-surface, #151820);
+}
+
+.prm-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ padding: 0.65rem 1.25rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.15s ease;
+ border: none;
+}
+
+.prm-btn-secondary {
+ background: rgba(255, 255, 255, 0.08);
+ color: var(--mp-text, #e8eaf2);
+}
+
+.prm-btn-secondary:hover {
+ background: rgba(255, 255, 255, 0.12);
+}
+
+.prm-btn-primary {
+ background: #f97316;
+ color: #fff;
+}
+
+.prm-btn-primary:hover:not(:disabled) {
+ background: #ea580c;
+}
+
+.prm-btn-primary:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
diff --git a/src/pages/dashboard/plugin/PublishReadinessModal.tsx b/src/pages/dashboard/plugin/PublishReadinessModal.tsx
new file mode 100644
index 0000000..b0fa498
--- /dev/null
+++ b/src/pages/dashboard/plugin/PublishReadinessModal.tsx
@@ -0,0 +1,294 @@
+import React, { useState, useEffect } from 'react';
+import {
+ CheckCircle2, XCircle, AlertTriangle, Rocket, X, RefreshCw
+} from 'lucide-react';
+import api from '../../../api';
+import { useTranslation } from 'react-i18next';
+import './PublishReadinessModal.css';
+
+export interface PublishReadiness {
+ nameReady: boolean;
+ categoryReady: boolean;
+ descriptionReady: boolean;
+ previewReady: boolean;
+ screenshotsReady: boolean;
+ screenshotsCount: number;
+ binaryReady: boolean;
+ binaryVersion: string | null;
+ pricingReady: boolean;
+ securityReady: boolean;
+ canPublish: boolean;
+ missingRequirements: string[];
+}
+
+interface Props {
+ isOpen: boolean;
+ pluginId: number;
+ pluginName: string;
+ isPublished?: boolean;
+ onClose: () => void;
+ onPublished: () => void;
+ onNavigateTab: (tabKey: string) => void;
+}
+
+export const PublishReadinessModal: React.FC
= ({
+ isOpen,
+ pluginId,
+ pluginName,
+ onClose,
+ onPublished,
+ onNavigateTab,
+}) => {
+ const { t } = useTranslation();
+ const [readiness, setReadiness] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [publishing, setPublishing] = useState(false);
+ const [error, setError] = useState(null);
+ const [successMsg, setSuccessMsg] = useState(null);
+
+ useEffect(() => {
+ if (isOpen) {
+ fetchReadiness();
+ }
+ }, [isOpen, pluginId]);
+
+ const fetchReadiness = async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const res = await api.get(`/plugins/${pluginId}/readiness`);
+ setReadiness(res.data);
+ } catch (err: any) {
+ setError(err.response?.data?.error || 'Failed to check publish readiness');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handlePublish = async () => {
+ setPublishing(true);
+ setError(null);
+ try {
+ await api.post(`/plugins/${pluginId}/publish`);
+ setSuccessMsg('Plugin published successfully and is now live!');
+ onPublished();
+ setTimeout(() => {
+ onClose();
+ }, 1800);
+ } catch (err: any) {
+ setError(err.response?.data?.error || err.response?.data?.message || 'Failed to publish plugin');
+ } finally {
+ setPublishing(false);
+ }
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
e.stopPropagation()}>
+
+
+
+
+ {/* Header */}
+
+
+
+
+
+
{t('developer.dashboard.publish_checklist_title', 'Publish to Marketplace')}
+
+ {t('developer.dashboard.publish_checklist_desc', 'Review requirements before releasing your plugin to the Pumpkin Market.')}
+
+
+
+
+ {error && (
+
+ )}
+
+ {successMsg && (
+
+
+ {successMsg}
+
+ )}
+
+ {loading ? (
+
+
+ Checking store requirements…
+
+ ) : readiness ? (
+
+ {/* Overall status banner */}
+ {readiness.canPublish ? (
+
+
+
+
{t('developer.dashboard.publish_ready_banner', 'All requirements are met! Your plugin is ready to go live.')}
+
Your store page, assets, and WebAssembly binary are all validated.
+
+
+ ) : (
+
+
+
+
{t('developer.dashboard.publish_missing_banner', 'Please complete the missing requirements before publishing.')}
+
{readiness.missingRequirements.length} item(s) need attention before release.
+
+
+ )}
+
+ {/* Checklist items */}
+
+ {/* 1. Name */}
+
+
+ {readiness.nameReady ? : }
+
+
+
Plugin Name & Identity
+
{readiness.nameReady ? pluginName : 'Plugin name is missing or empty'}
+
+ {!readiness.nameReady && (
+
{ onNavigateTab('listing'); onClose(); }}>
+ Fix in Listing
+
+ )}
+
+
+ {/* 2. Category */}
+
+
+ {readiness.categoryReady ? : }
+
+
+
Category
+
{readiness.categoryReady ? 'Valid category selected' : 'No category selected'}
+
+ {!readiness.categoryReady && (
+
{ onNavigateTab('listing'); onClose(); }}>
+ Fix in Listing
+
+ )}
+
+
+ {/* 3. Description */}
+
+
+ {readiness.descriptionReady ? : }
+
+
+
Store Description
+
{readiness.descriptionReady ? 'Description provided in at least 1 language' : 'Missing store description'}
+
+ {!readiness.descriptionReady && (
+
{ onNavigateTab('listing'); onClose(); }}>
+ Fix in Listing
+
+ )}
+
+
+ {/* 4. Preview Icon */}
+
+
+ {readiness.previewReady ? : }
+
+
+
Store Icon / Avatar
+
{readiness.previewReady ? 'Icon uploaded' : 'Store icon image is required'}
+
+ {!readiness.previewReady && (
+
{ onNavigateTab('listing'); onClose(); }}>
+ Upload Icon
+
+ )}
+
+
+ {/* 5. Screenshots */}
+
+
+ {readiness.screenshotsReady ? : }
+
+
+
Screenshots
+
{readiness.screenshotsReady ? `${readiness.screenshotsCount} screenshot(s) uploaded` : 'At least 1 screenshot is required'}
+
+ {!readiness.screenshotsReady && (
+
{ onNavigateTab('listing'); onClose(); }}>
+ Add Screenshots
+
+ )}
+
+
+ {/* 6. Binary */}
+
+
+ {readiness.binaryReady ? : }
+
+
+
WebAssembly Binary (.wasm)
+
{readiness.binaryReady ? `Valid binary uploaded (${readiness.binaryVersion ? `v${readiness.binaryVersion}` : 'Ready'})` : 'No .wasm binary uploaded yet'}
+
+ {!readiness.binaryReady && (
+
{ onNavigateTab('update'); onClose(); }}>
+ Upload Binary
+
+ )}
+
+
+ {/* 7. Pricing */}
+
+
+ {readiness.pricingReady ? : }
+
+
+
Pricing & Payouts
+
{readiness.pricingReady ? 'Valid pricing configuration' : 'Paid plugin requires price > 0 and connected Stripe account'}
+
+ {!readiness.pricingReady && (
+
{ onNavigateTab('pricing'); onClose(); }}>
+ Configure Price
+
+ )}
+
+
+ {/* 8. Security */}
+
+
+ {readiness.securityReady ? : }
+
+
+
Account Security
+
{readiness.securityReady ? '2FA or Passkey verified' : '2FA or Passkey required in user settings'}
+
+
+
+
+ ) : null}
+
+ {/* Footer Actions */}
+
+
+ Close
+
+
+
+ {publishing ? 'Publishing…' : t('developer.dashboard.btn_confirm_publish', 'Confirm & Release to Store')}
+
+
+
+
+ );
+};
+
+export default PublishReadinessModal;