From 9a33d57de13872e81edc450ee2394434f9ac19d5 Mon Sep 17 00:00:00 2001 From: rob Date: Wed, 31 Dec 2025 21:06:40 -0400 Subject: [PATCH] Add JavaScript interactivity and consent API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Complete main.js with all interactive functions: - Mobile menu toggle with icon switching - Search modal with live search - Dashboard modals (tokens, deprecate, settings) - Tool detail page (copy install, report modal) - Toast notifications - Add /api/v1/consent endpoint for cookie preferences - Session-based consent storage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/smarttools/registry/app.py | 25 ++ src/smarttools/web/routes.py | 12 +- src/smarttools/web/static/js/main.js | 233 +++++++++++++++++- .../web/templates/pages/community.html | 81 ++++++ .../web/templates/pages/donate.html | 71 ++++++ 5 files changed, 404 insertions(+), 18 deletions(-) create mode 100644 src/smarttools/web/templates/pages/community.html create mode 100644 src/smarttools/web/templates/pages/donate.html diff --git a/src/smarttools/registry/app.py b/src/smarttools/registry/app.py index cbde5e6..3fccee0 100644 --- a/src/smarttools/registry/app.py +++ b/src/smarttools/registry/app.py @@ -1369,6 +1369,31 @@ def create_app() -> Flask: return jsonify({"data": {"status": "submitted"}}) + @app.route("/api/v1/consent", methods=["POST"]) + def save_consent() -> Response: + """Save user consent preferences for analytics/ads.""" + try: + data = request.get_json(force=True) or {} + except Exception: + data = {} + + analytics = bool(data.get("analytics", False)) + ads = bool(data.get("ads", False)) + + # Store consent in session (works with our SQLite session interface) + from flask import session + session["consent_analytics"] = analytics + session["consent_ads"] = ads + session["consent_given"] = True + + return jsonify({ + "data": { + "analytics": analytics, + "ads": ads, + "saved": True + } + }) + @app.route("/api/v1/webhook/gitea", methods=["POST"]) def webhook_gitea() -> Response: if request.content_length and request.content_length > MAX_BODY_BYTES: diff --git a/src/smarttools/web/routes.py b/src/smarttools/web/routes.py index 55af0fc..0da3bbf 100644 --- a/src/smarttools/web/routes.py +++ b/src/smarttools/web/routes.py @@ -508,11 +508,7 @@ web_bp.add_url_rule("/tutorials/", endpoint="tutorial", view_func=tut @web_bp.route("/community", endpoint="community") def community(): - return render_template( - "pages/content.html", - title="Community", - body="Community features will live here. For now, join the discussion and share ideas.", - ) + return render_template("pages/community.html") @web_bp.route("/about", endpoint="about") @@ -522,11 +518,7 @@ def about(): @web_bp.route("/donate", endpoint="donate") def donate(): - return render_template( - "pages/content.html", - title="Support SmartTools", - body="Donations help fund infrastructure, development, and broader access to AI tools.", - ) + return render_template("pages/donate.html") @web_bp.route("/privacy", endpoint="privacy") diff --git a/src/smarttools/web/static/js/main.js b/src/smarttools/web/static/js/main.js index dcbd9b6..6203c50 100644 --- a/src/smarttools/web/static/js/main.js +++ b/src/smarttools/web/static/js/main.js @@ -61,21 +61,51 @@ function debounce(func, wait) { // Mobile menu toggle function toggleMobileMenu() { const menu = document.getElementById('mobile-menu'); - const overlay = document.getElementById('mobile-menu-overlay'); - if (menu && overlay) { + const openIcon = document.getElementById('menu-icon-open'); + const closeIcon = document.getElementById('menu-icon-close'); + + if (menu) { + const isHidden = menu.classList.contains('hidden'); menu.classList.toggle('hidden'); - overlay.classList.toggle('hidden'); - document.body.classList.toggle('overflow-hidden'); + + // Toggle icons + if (openIcon && closeIcon) { + if (isHidden) { + openIcon.classList.add('hidden'); + closeIcon.classList.remove('hidden'); + } else { + openIcon.classList.remove('hidden'); + closeIcon.classList.add('hidden'); + } + } } } function closeMobileMenu() { const menu = document.getElementById('mobile-menu'); - const overlay = document.getElementById('mobile-menu-overlay'); - if (menu && overlay) { + const openIcon = document.getElementById('menu-icon-open'); + const closeIcon = document.getElementById('menu-icon-close'); + + if (menu) { menu.classList.add('hidden'); - overlay.classList.add('hidden'); - document.body.classList.remove('overflow-hidden'); + if (openIcon) openIcon.classList.remove('hidden'); + if (closeIcon) closeIcon.classList.add('hidden'); + } +} + +// Mobile filters toggle (tools page) +function toggleMobileFilters() { + const filters = document.getElementById('mobile-filters'); + if (filters) { + filters.classList.toggle('hidden'); + } +} + +// Mobile TOC toggle (docs page) +function toggleMobileToc() { + const toc = document.getElementById('mobile-toc'); + if (toc) { + toc.classList.toggle('hidden'); } } @@ -211,3 +241,190 @@ function createToastContainer() { document.body.appendChild(container); return container; } + +// ============================================ +// Tool Detail Page Functions +// ============================================ + +function copyInstall() { + const command = document.getElementById('install-command'); + if (command) { + const text = command.textContent || command.innerText; + navigator.clipboard.writeText(text.trim()).then(() => { + showToast('Install command copied!', 'success'); + }); + } +} + +function openReportModal() { + const modal = document.getElementById('report-modal'); + if (modal) modal.classList.remove('hidden'); +} + +function closeReportModal() { + const modal = document.getElementById('report-modal'); + if (modal) modal.classList.add('hidden'); +} + +// ============================================ +// Dashboard - Token Management +// ============================================ + +function openCreateTokenModal() { + const modal = document.getElementById('create-token-modal'); + if (modal) modal.classList.remove('hidden'); +} + +function closeCreateTokenModal() { + const modal = document.getElementById('create-token-modal'); + if (modal) modal.classList.add('hidden'); +} + +function closeTokenCreatedModal() { + const modal = document.getElementById('token-created-modal'); + if (modal) modal.classList.add('hidden'); + // Reload to show new token in list + window.location.reload(); +} + +function copyNewToken() { + const tokenEl = document.getElementById('new-token-value'); + if (tokenEl) { + navigator.clipboard.writeText(tokenEl.textContent.trim()).then(() => { + showToast('Token copied to clipboard!', 'success'); + }); + } +} + +function revokeToken(tokenId, tokenName) { + if (confirm(`Are you sure you want to revoke the token "${tokenName}"? This cannot be undone.`)) { + fetch(`/api/v1/tokens/${tokenId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + } + }).then(response => { + if (response.ok) { + showToast('Token revoked successfully', 'success'); + window.location.reload(); + } else { + showToast('Failed to revoke token', 'error'); + } + }).catch(() => { + showToast('Failed to revoke token', 'error'); + }); + } +} + +// ============================================ +// Dashboard - Tool Management +// ============================================ + +let deprecateToolOwner = ''; +let deprecateToolName = ''; + +function openDeprecateModal(owner, name, isDeprecated) { + deprecateToolOwner = owner; + deprecateToolName = name; + const modal = document.getElementById('deprecate-modal'); + const title = document.getElementById('deprecate-modal-title'); + const btn = document.getElementById('deprecate-submit-btn'); + + if (modal) { + if (title) { + title.textContent = isDeprecated ? `Restore ${name}` : `Deprecate ${name}`; + } + if (btn) { + btn.textContent = isDeprecated ? 'Restore Tool' : 'Deprecate Tool'; + btn.classList.toggle('bg-red-600', !isDeprecated); + btn.classList.toggle('bg-green-600', isDeprecated); + } + modal.classList.remove('hidden'); + } +} + +function closeDeprecateModal() { + const modal = document.getElementById('deprecate-modal'); + if (modal) modal.classList.add('hidden'); + deprecateToolOwner = ''; + deprecateToolName = ''; +} + +// ============================================ +// Dashboard - Settings +// ============================================ + +function resendVerification() { + fetch('/api/v1/me/resend-verification', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }).then(response => { + if (response.ok) { + showToast('Verification email sent!', 'success'); + } else { + showToast('Failed to send verification email', 'error'); + } + }).catch(() => { + showToast('Failed to send verification email', 'error'); + }); +} + +function confirmDeleteAccount() { + if (confirm('Are you absolutely sure you want to delete your account? This will permanently delete all your tools and cannot be undone.')) { + const confirmInput = prompt('Type "DELETE" to confirm:'); + if (confirmInput === 'DELETE') { + fetch('/api/v1/me', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json' + } + }).then(response => { + if (response.ok) { + window.location.href = '/'; + } else { + showToast('Failed to delete account', 'error'); + } + }).catch(() => { + showToast('Failed to delete account', 'error'); + }); + } + } +} + +// ============================================ +// Search Functionality +// ============================================ + +const searchInput = document.getElementById('search-input'); +const searchResults = document.getElementById('search-results'); + +if (searchInput) { + searchInput.addEventListener('input', debounce(function() { + const query = this.value.trim(); + if (query.length < 2) { + searchResults.innerHTML = '

Start typing to search...

'; + return; + } + + fetch(`/api/v1/tools/search?q=${encodeURIComponent(query)}&limit=10`) + .then(response => response.json()) + .then(data => { + if (data.data && data.data.length > 0) { + searchResults.innerHTML = data.data.map(tool => ` + +
${tool.owner}/${tool.name}
+
${tool.description || 'No description'}
+
+ `).join(''); + } else { + searchResults.innerHTML = '

No tools found

'; + } + }) + .catch(() => { + searchResults.innerHTML = '

Search failed

'; + }); + }, 300)); +} diff --git a/src/smarttools/web/templates/pages/community.html b/src/smarttools/web/templates/pages/community.html new file mode 100644 index 0000000..d6cd8f5 --- /dev/null +++ b/src/smarttools/web/templates/pages/community.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} + +{% block title %}Community - SmartTools{% endblock %} + +{% block meta_description %}Connect with the SmartTools community, share tools, and collaborate on new ideas.{% endblock %} + +{% block content %} +
+ +
+
+

Community

+

+ Collaboration over competition. Share tools, ask questions, and build together. +

+
+
+ +
+ +
+
+

Discussions

+

+ Ask for help, share feedback, and discover best practices. +

+ + Coming soon + + + + +
+ +
+

Contributor Spotlight

+

+ Celebrate creators who publish tools and help others succeed. +

+ + Coming soon + + + + +
+ +
+

Project Showcase

+

+ See how teams use SmartTools in real projects and workflows. +

+ + Coming soon + + + + +
+
+ + +
+

Get Involved

+

+ Want to contribute tools, documentation, or tutorials? We’d love to hear from you. +

+ +
+
+
+{% endblock %} diff --git a/src/smarttools/web/templates/pages/donate.html b/src/smarttools/web/templates/pages/donate.html new file mode 100644 index 0000000..52f23af --- /dev/null +++ b/src/smarttools/web/templates/pages/donate.html @@ -0,0 +1,71 @@ +{% extends "base.html" %} + +{% block title %}Support SmartTools - Donate{% endblock %} + +{% block meta_description %}Support SmartTools development and help keep AI tools accessible for everyone.{% endblock %} + +{% block content %} +
+ +
+
+

Support SmartTools

+

+ Your support keeps the registry running and funds new features for the community. +

+
+
+ +
+ +
+
+

Infrastructure

+

+ Keep the registry fast, reliable, and available for everyone. +

+
+
+

Open Access

+

+ Fund future hosting of shared AI models and public demos. +

+
+
+

Community Growth

+

+ Support tutorials, examples, and recognition for contributors. +

+
+
+ + +
+

Choose a Way to Contribute

+

+ Placeholder links for donation providers. Replace with real endpoints when ready. +

+ +
+ + +
+

How funds are used

+
    +
  • Registry hosting, backups, and monitoring
  • +
  • Documentation, tutorials, and example projects
  • +
  • Ongoing development and community support
  • +
+
+
+
+{% endblock %}