Add analytics link to page headers, event tracking to calculator, update deploy script

- Add analytics.html to STATIC_FILES in deploy.sh
- Add trackEvent/getVisitorId to script.js with page_view, show_results, copy_email hooks
- Add "Analytics →" nav link to index.html and market.html headers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-17 16:15:12 +00:00
parent 6bedd5c074
commit 8843af6402
4 changed files with 40 additions and 3 deletions

View file

@ -13,7 +13,7 @@ ENV_FILE="${REPO_DIR}/server/.env"
COMPOSE_FILE="${REPO_DIR}/docker-compose.yml"
# Static files to deploy to Apache web root
STATIC_FILES=(index.html auth.js script.js config.json market.html market-script.js)
STATIC_FILES=(index.html auth.js script.js config.json market.html market-script.js analytics.html)
# ── Colours ───────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'

View file

@ -215,6 +215,7 @@
</div>
</div>
<div class="flex items-center gap-3">
<a href="analytics.html" class="text-xs text-brand-500 hover:underline">Analytics →</a>
<div id="userInfo" class="hidden flex items-center gap-2">
<span id="userDisplay" class="text-sm font-medium text-gray-700 dark:text-gray-300"></span>
<button onclick="signOut()" class="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Sign out">

View file

@ -35,7 +35,9 @@
<h1 class="text-xl font-bold text-brand-700 dark:text-brand-400">SLA Brief Advisor</h1>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">eCom Content Factory</p>
</div>
<button id="darkToggle" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Toggle dark mode">
<div class="flex items-center gap-3">
<a href="analytics.html" class="text-xs text-brand-500 hover:underline">Analytics →</a>
<button id="darkToggle" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Toggle dark mode">
<svg id="sunIcon" class="w-5 h-5 hidden" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
<svg id="moonIcon" class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
</button>

View file

@ -29,6 +29,36 @@ let currentStep = 1;
let activeStages = [false, false, false, false, false, false, false, false];
let lastCalculationData = null;
// ---- Usage Tracking ----
const TRACK_API = (() => {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') return null;
const base = location.pathname.replace(/\/[^/]*$/, '');
return `${base}/api/events`;
})();
async function getVisitorId() {
const raw = [
navigator.language,
screen.width + 'x' + screen.height,
screen.colorDepth,
Intl.DateTimeFormat().resolvedOptions().timeZone,
navigator.hardwareConcurrency || '',
].join('|');
const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(raw));
return Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('');
}
function trackEvent(event, metadata = {}) {
if (!TRACK_API) return;
getVisitorId().then(visitor_id => {
fetch(TRACK_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ event, page: 'calculator', visitor_id, metadata }),
}).catch(() => {});
});
}
// ---- Bootstrap ----
document.addEventListener('DOMContentLoaded', async () => {
await initAuth();
@ -37,6 +67,7 @@ document.addEventListener('DOMContentLoaded', async () => {
initStepper();
initDatePickers();
bindEvents();
trackEvent('page_view');
});
async function loadConfig() {
@ -664,6 +695,8 @@ function intVal(id) {
// ---- Render Results ----
function renderResults(data) {
lastCalculationData = data;
const briefType = document.getElementById('briefType').selectedOptions[0]?.text;
trackEvent('show_results', { briefType });
// Verdict banner
const banner = document.getElementById('verdictBanner');
@ -948,8 +981,9 @@ function csvEscape(str) {
// ---- Copy for Email (Rich HTML) ----
function copyForEmail() {
if (!lastCalculationData) return;
const data = lastCalculationData;
const briefType = document.getElementById('briefType').selectedOptions[0]?.text || '';
trackEvent('copy_email', { briefType });
const data = lastCalculationData;
// Inline styles for Outlook compatibility
const tbl = 'border-collapse:collapse;width:100%;font-family:Calibri,Arial,sans-serif;font-size:13px;';