From 8c622777181bdc612c13904f9d35ef08814fd4c0 Mon Sep 17 00:00:00 2001 From: unclecode Date: Mon, 6 Oct 2025 20:58:35 +0800 Subject: [PATCH] feat(marketplace): add sponsor logo uploads Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- docs/md_v2/marketplace/admin/admin.css | 109 +++++++++++++ docs/md_v2/marketplace/admin/admin.js | 143 ++++++++++++++++-- docs/md_v2/marketplace/backend/server.py | 46 +++++- .../marketplace/backend/uploads/.gitignore | 2 + docs/md_v2/marketplace/marketplace.css | 15 ++ docs/md_v2/marketplace/marketplace.js | 19 ++- 6 files changed, 321 insertions(+), 13 deletions(-) create mode 100644 docs/md_v2/marketplace/backend/uploads/.gitignore diff --git a/docs/md_v2/marketplace/admin/admin.css b/docs/md_v2/marketplace/admin/admin.css index 7296a801..66b975a9 100644 --- a/docs/md_v2/marketplace/admin/admin.css +++ b/docs/md_v2/marketplace/admin/admin.css @@ -431,6 +431,16 @@ gap: 0.5rem; } +.table-logo { + width: 48px; + height: 48px; + object-fit: contain; + border-radius: 6px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + padding: 4px; +} + .btn-edit, .btn-delete, .btn-duplicate { padding: 0.25rem 0.5rem; background: transparent; @@ -585,6 +595,105 @@ cursor: pointer; } +.sponsor-form { + grid-template-columns: 200px repeat(2, minmax(220px, 1fr)); + align-items: flex-start; + grid-auto-flow: dense; +} + +.sponsor-logo-group { + grid-row: span 3; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.span-two { + grid-column: span 2; +} + +.logo-upload { + position: relative; + width: 180px; +} + +.image-preview { + width: 180px; + height: 180px; + border: 1px dashed var(--border-color); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-tertiary); + overflow: hidden; +} + +.image-preview.empty { + color: var(--text-secondary); + font-size: 0.75rem; + text-align: center; + padding: 0.75rem; +} + +.image-preview img { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.upload-btn { + position: absolute; + left: 50%; + bottom: 12px; + transform: translateX(-50%); + padding: 0.35rem 1rem; + background: linear-gradient(135deg, var(--primary-cyan), var(--primary-teal)); + border: none; + border-radius: 999px; + color: var(--bg-dark); + font-size: 0.75rem; + font-weight: 600; + cursor: pointer; + box-shadow: 0 6px 18px rgba(80, 255, 255, 0.25); +} + +.upload-btn:hover { + box-shadow: 0 8px 22px rgba(80, 255, 255, 0.35); +} + +.logo-upload input[type="file"] { + display: none; +} + +.upload-hint { + font-size: 0.75rem; + color: var(--text-secondary); + margin: 0; +} + +@media (max-width: 960px) { + .sponsor-form { + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + } + + .sponsor-logo-group { + grid-column: 1 / -1; + grid-row: auto; + flex-direction: row; + align-items: center; + gap: 1.5rem; + } + + .logo-upload { + width: 160px; + } + + .span-two { + grid-column: 1 / -1; + } +} + /* Rich Text Editor */ .editor-toolbar { display: flex; diff --git a/docs/md_v2/marketplace/admin/admin.js b/docs/md_v2/marketplace/admin/admin.js index 258858da..adb31d0b 100644 --- a/docs/md_v2/marketplace/admin/admin.js +++ b/docs/md_v2/marketplace/admin/admin.js @@ -1,5 +1,21 @@ // Admin Dashboard - Smart & Powerful -const API_BASE = '/api'; +const { API_BASE, API_ORIGIN } = (() => { + const { hostname, port } = window.location; + if ((hostname === 'localhost' || hostname === '127.0.0.1') && port === '8000') { + const origin = 'http://127.0.0.1:8100'; + return { API_BASE: `${origin}/api`, API_ORIGIN: origin }; + } + return { API_BASE: '/api', API_ORIGIN: '' }; +})(); + +const resolveAssetUrl = (path) => { + if (!path) return ''; + if (/^https?:\/\//i.test(path)) return path; + if (path.startsWith('/') && API_ORIGIN) { + return `${API_ORIGIN}${path}`; + } + return path; +}; class AdminDashboard { constructor() { @@ -144,13 +160,19 @@ class AdminDashboard { } async apiCall(endpoint, options = {}) { + const isFormData = options.body instanceof FormData; + const headers = { + 'Authorization': `Bearer ${this.token}`, + ...options.headers + }; + + if (!isFormData && !headers['Content-Type']) { + headers['Content-Type'] = 'application/json'; + } + const response = await fetch(`${API_BASE}${endpoint}`, { ...options, - headers: { - 'Authorization': `Bearer ${this.token}`, - 'Content-Type': 'application/json', - ...options.headers - } + headers }); if (response.status === 401) { @@ -189,7 +211,10 @@ class AdminDashboard { } async loadSponsors() { - this.data.sponsors = await this.apiCall('/sponsors'); + const cacheBuster = Date.now(); + this.data.sponsors = await this.apiCall(`/sponsors?limit=100&_=${cacheBuster}`, { + cache: 'no-store' + }); this.renderSponsorsTable(this.data.sponsors); } @@ -314,6 +339,7 @@ class AdminDashboard { ID + Logo Company Tier Start @@ -326,6 +352,7 @@ class AdminDashboard { ${sponsors.map(sponsor => ` ${sponsor.id} + ${sponsor.logo_url ? `` : '-'} ${sponsor.company_name} ${sponsor.tier} ${new Date(sponsor.start_date).toLocaleDateString()} @@ -389,6 +416,10 @@ class AdminDashboard { modal.classList.remove('hidden'); modal.dataset.type = type; + + if (type === 'sponsors') { + this.setupLogoUploadHandlers(); + } } getAppForm(app) { @@ -524,9 +555,22 @@ class AdminDashboard { } getSponsorForm(sponsor) { + const existingFile = sponsor?.logo_url ? sponsor.logo_url.split('/').pop().split('?')[0] : ''; return ` -
-
+