From 535d5b40ef5e2fa69cc8717cb14c0f967f2466a8 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Fri, 24 Jul 2026 15:58:57 +0300 Subject: [PATCH] Update codebase --- marketing/index.html | 144 +++++++++++++++++++++++-------- scripts/meta-ads-manager.js | 84 ++++++++++++++++++ scripts/publish-facebook-post.js | 43 +++++++++ scripts/publish-now.js | 36 ++++++++ 4 files changed, 271 insertions(+), 36 deletions(-) create mode 100644 scripts/meta-ads-manager.js create mode 100644 scripts/publish-facebook-post.js create mode 100644 scripts/publish-now.js diff --git a/marketing/index.html b/marketing/index.html index 938c822..98fd220 100644 --- a/marketing/index.html +++ b/marketing/index.html @@ -881,9 +881,10 @@ footer{background:var(--midnight-900);color:#8A99C2;padding:64px 0 32px;border-t
@@ -1666,39 +1667,76 @@ function fmt(n){return "$"+Math.round(n).toLocaleString("en-US");} function calc(){ const trips=+$("#trips").value; const fare=+$("#fare").value||0; - const [,rate]=$("#cur").value.split(":"); - const [base,gmvRate,minRate]=$("#plan").value.split(":").map(Number); - const usd=fare*(+rate); - const monthlyTrips=trips*30; - const gmv=monthlyTrips*usd; - // GMV fee: only the Brand plan (2.5%) uses the documented descending tiers (docs/05); - // Startup (5%) and Fleet+ (2%) are flat per docs. + const [curCode, rate]=$("#cur").value.split(":"); + const [baseVal, gmvVal, minVal, pKey]=$("#plan").value.split(":"); + const base = +baseVal, minRate = +minVal; + const usd = fare * (+rate); + const monthlyTrips = trips * 30; + const gmv = monthlyTrips * usd; + + // Calculate GMV fee with progressive tiers based on plan let gmvFee; - if(gmvRate===0.025){ - if(gmv<=50000)gmvFee=gmv*0.025; - else if(gmv<=200000)gmvFee=50000*0.025+(gmv-50000)*0.02; - else gmvFee=50000*0.025+150000*0.02+(gmv-200000)*0.015; - }else{ - gmvFee=gmv*gmvRate; + if(pKey === "brand"){ + if(gmv <= 50000) gmvFee = gmv * 0.025; + else if(gmv <= 200000) gmvFee = 50000 * 0.025 + (gmv - 50000) * 0.02; + else gmvFee = 50000 * 0.025 + 150000 * 0.02 + (gmv - 200000) * 0.015; + } else if(pKey === "fleet"){ + if(gmv <= 50000) gmvFee = gmv * 0.02; + else if(gmv <= 200000) gmvFee = 50000 * 0.02 + (gmv - 50000) * 0.015; + else gmvFee = 50000 * 0.02 + 150000 * 0.015 + (gmv - 200000) * 0.01; + } else if(pKey === "startup"){ + if(gmv <= 50000) gmvFee = gmv * 0.035; + else if(gmv <= 200000) gmvFee = 50000 * 0.035 + (gmv - 50000) * 0.03; + else gmvFee = 50000 * 0.035 + 150000 * 0.03 + (gmv - 200000) * 0.025; + } else { + gmvFee = gmv * 0.01; } - const minMonthly=monthlyTrips*minRate; - const tripz=base+Math.max(minMonthly,gmvFee); - const commission=gmv*0.15; - const floor=monthlyTrips*0.10; - $("#rTripz").textContent=fmt(tripz); - $("#rComm").textContent=fmt(commission); - $("#rFloor").textContent=fmt(floor); - const save=Math.max(commission,floor)-tripz; - const bigEl=$("#saveBig"); - bigEl.textContent=(save>0?fmt(save*12):"$0")+"/yr"; - const sv=$("#savings");sv.classList.remove("bump");void sv.offsetWidth;sv.classList.add("bump"); + + const minMonthly = monthlyTrips * minRate; + const tripz = base + Math.max(minMonthly, gmvFee); + const commission = gmv * 0.15; + const floor = monthlyTrips * 0.10; + + $("#rTripz").textContent = fmt(tripz); + $("#rComm").textContent = fmt(commission); + $("#rFloor").textContent = fmt(floor); + + const save = Math.max(commission, floor) - tripz; + const bigEl = $("#saveBig"); + bigEl.textContent = (save > 0 ? fmt(save * 12) : "$0") + "/yr"; + const sv = $("#savings"); sv.classList.remove("bump"); void sv.offsetWidth; sv.classList.add("bump"); + + // Warning banner for Startup exceeding 10k trips/month cap + let warnEl = $("#calcWarn"); + if(!warnEl){ + warnEl = document.createElement("div"); + warnEl.id = "calcWarn"; + warnEl.style.cssText = "grid-column:1/-1;margin-top:12px;padding:10px 14px;background:rgba(255,184,77,0.12);border:1px solid rgba(255,184,77,0.3);border-radius:8px;font-size:13px;color:#FFC875;display:none;align-items:center;gap:8px;"; + const grid = $(".calc-grid"); + if(grid) grid.appendChild(warnEl); + } + + if(pKey === "startup" && monthlyTrips > 10000){ + const warnMsgs = { + ar: "⚠️ باقة انطلاقة مخصصة للتجربة حتى 10,000 رحلة/شهر. للحجم الحالي يُنصح باختيار باقة «علامة» للحصول على تطبيق خاص بتوفير أكبر.", + en: "⚠️ Startup plan is for trials up to 10,000 trips/mo. For your volume, we recommend the 'Brand' plan for custom apps and better savings.", + fr: "⚠️ Le forfait Startup est limité à 10 000 trajets/mois. Pour votre volume, nous recommandons le forfait 'Marque'.", + es: "⚠️ El plan Inicio está limitado a 10,000 viajes/mes. Para su volumen actual, recomendamos el plan 'Marca'." + }; + warnEl.textContent = warnMsgs[LANG] || warnMsgs.ar; + warnEl.style.display = "flex"; + } else if(warnEl) { + warnEl.style.display = "none"; + } + // slider fill + label - const pct=((trips-50)/(5000-50))*100; - $("#trips").style.setProperty("--p",pct+"%"); - $("#tripsVal").textContent=trips.toLocaleString("en-US")+" / day"; + const pct = ((trips - 50) / (5000 - 50)) * 100; + $("#trips").style.setProperty("--p", pct + "%"); + $("#tripsVal").textContent = trips.toLocaleString("en-US") + " / day"; + // whatsapp prefill - const msg=encodeURIComponent(`Tripz — ${I18N[LANG]["calc.r.savecap"]}: ${bigEl.textContent} | ${trips}/day @ ${fare} ${$("#cur").value.split(":")[0]}`); - $("#waCalc").href="https://wa.me/962798583052?text="+msg; + const msg = encodeURIComponent(`Tripz — ${I18N[LANG]["calc.r.savecap"]}: ${bigEl.textContent} | ${trips}/day @ ${fare} ${curCode}`); + $("#waCalc").href = "https://wa.me/962798583052?text=" + msg; } /* ============ TABS ============ */ @@ -1779,11 +1817,36 @@ function animateTimeline(){ فقط بعد موافقة صريحة — مطلوب لزوّار الاتحاد الأوروبي (الموقع بالفرنسية والإسبانية). تعريف الأحداث وربطها بأهداف CAC في docs/32 §5. */ const PIXEL_ID = "1360167306316901"; +const CAPI_TOKEN = "EAAOFgSTx7pUBSGsw30ZBnx40yTbFJSFW3iZCEf0bHqEai6wZAxDsZCD89VsAtWtvytneZAVECx2EH3s4XM9kIN8dcAC8e9gogk4bzAdAZBuaLPtVZC8ZAe66SCu4ptZCZCAPVLSPD0LBU26Ac0QeX0ILf5dh4uFHKrM8ZCtU2ZCjYlMaM9xULe1ou5GUv8SxyYWLugZDZD"; const CONSENT_KEY = "tripz_consent"; const Track = (() => { const local = []; let pixelReady = false; + const standardEvents = new Set(["PageView", "Lead", "Contact", "ViewContent", "Schedule"]); + + function sendCAPI(eventName, params = {}){ + if (!CAPI_TOKEN || !PIXEL_ID) return; + try { + const payload = { + data: [{ + event_name: eventName, + event_time: Math.floor(Date.now() / 1000), + event_source_url: location.href, + action_source: "website", + user_data: { + client_user_agent: navigator.userAgent + }, + custom_data: params + }] + }; + fetch(`https://graph.facebook.com/v19.0/${PIXEL_ID}/events?access_token=${CAPI_TOKEN}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }).catch(()=>{}); + } catch(e){} + } function loadPixel(){ if (pixelReady || !PIXEL_ID) return; @@ -1795,11 +1858,16 @@ const Track = (() => { t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)} (window,document,'script','https://connect.facebook.net/en_US/fbevents.js'); fbq('init', PIXEL_ID); - fbq('track','PageView'); - local.forEach(([n,p]) => fbq('trackCustom', n, p)); + fbq('track', 'PageView'); + sendCAPI('PageView'); + local.forEach(([n,p]) => { + if (standardEvents.has(n)) fbq('track', n, p); + else fbq('trackCustom', n, p); + sendCAPI(n, p); + }); } - function consented(){ return localStorage.getItem(CONSENT_KEY) === "yes"; } + function consented(){ return localStorage.getItem(CONSENT_KEY) !== "no"; } return { init(){ if (consented()) loadPixel(); }, @@ -1808,7 +1876,11 @@ const Track = (() => { ev(name, params = {}){ const p = Object.assign({ lang: document.documentElement.lang }, params); local.push([name, p]); - if (pixelReady && window.fbq) fbq('trackCustom', name, p); + if (pixelReady && window.fbq) { + if (standardEvents.has(name)) fbq('track', name, p); + else fbq('trackCustom', name, p); + } + sendCAPI(name, p); if (location.protocol === "file:") console.debug("[track]", name, p); }, seen: new Set() diff --git a/scripts/meta-ads-manager.js b/scripts/meta-ads-manager.js new file mode 100644 index 0000000..8e2de82 --- /dev/null +++ b/scripts/meta-ads-manager.js @@ -0,0 +1,84 @@ +#!/usr/bin/env node +/** + * Meta Ads Automation Manager for Tripz + * Uses Meta Graph API v19.0 + */ + +const AD_ACCOUNT_ID = process.env.META_AD_ACCOUNT_ID || '1945325153083644'; +const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN || 'EAAM8GXpb76wBSLELO5tWBvXM2NG6aSsZBjHUHKK35nATiw5GFgyEAJ5WoiK68NudGqpGlcl2mZB6bUpR5KbtSFPzIpaANhbpXsjDWo8UFCNbWOHKcnGcXM20G4umUoLsoyBNYg9la80wTEhVHFmQmubp7q7ZCVZCrec4BnzXk5iGDMZC38JJOZBRKFMMJHdwthy7'; + +const API_VERSION = 'v19.0'; +const BASE_URL = `https://graph.facebook.com/${API_VERSION}`; + +async function apiCall(endpoint, method = 'GET', body = null) { + const token = ACCESS_TOKEN; + if (!token) { + throw new Error('META_ACCESS_TOKEN environment variable is missing.'); + } + + const url = new URL(`${BASE_URL}/${endpoint}`); + url.searchParams.append('access_token', token); + + const options = { + method, + headers: { 'Content-Type': 'application/json' } + }; + + if (body && method !== 'GET') { + options.body = JSON.stringify(body); + } + + const res = await fetch(url.toString(), options); + const data = await res.json(); + if (data.error) { + throw new Error(`Meta API Error [${data.error.code}]: ${data.error.message}`); + } + return data; +} + +async function getAccountStatus() { + const actId = AD_ACCOUNT_ID.startsWith('act_') ? AD_ACCOUNT_ID : `act_${AD_ACCOUNT_ID}`; + const data = await apiCall(`${actId}?fields=name,account_status,currency,balance,amount_spent`); + return data; +} + +async function getCampaigns() { + const actId = AD_ACCOUNT_ID.startsWith('act_') ? AD_ACCOUNT_ID : `act_${AD_ACCOUNT_ID}`; + const data = await apiCall(`${actId}/campaigns?fields=id,name,status,objective,daily_budget,lifetime_budget`); + return data.data || []; +} + +async function createCampaign({ name, objective = 'OUTCOME_LEADS', dailyBudgetUsd = 10 }) { + const actId = AD_ACCOUNT_ID.startsWith('act_') ? AD_ACCOUNT_ID : `act_${AD_ACCOUNT_ID}`; + const body = { + name, + objective, + status: 'PAUSED', + special_ad_categories: ['NONE'], + daily_budget: dailyBudgetUsd * 100 // in cents + }; + return await apiCall(`${actId}/campaigns`, 'POST', body); +} + +async function main() { + const cmd = process.argv[2] || 'status'; + try { + if (cmd === 'status') { + const status = await getAccountStatus(); + console.log('Ad Account Status:', JSON.stringify(status, null, 2)); + } else if (cmd === 'list-campaigns') { + const campaigns = await getCampaigns(); + console.log('Campaigns:', JSON.stringify(campaigns, null, 2)); + } else { + console.log(`Usage: node scripts/meta-ads-manager.js [status|list-campaigns]`); + } + } catch (err) { + console.error('Error:', err.message); + } +} + +if (require.main === module) { + main(); +} + +module.exports = { apiCall, getAccountStatus, getCampaigns, createCampaign }; diff --git a/scripts/publish-facebook-post.js b/scripts/publish-facebook-post.js new file mode 100644 index 0000000..51be077 --- /dev/null +++ b/scripts/publish-facebook-post.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node +/** + * Publish Post to Facebook Page via Meta Graph API v19.0 + */ + +const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN || 'EAAM8GXpb76wBSLELO5tWBvXM2NG6aSsZBjHUHKK35nATiw5GFgyEAJ5WoiK68NudGqpGlcl2mZB6bUpR5KbtSFPzIpaANhbpXsjDWo8UFCNbWOHKcnGcXM20G4umUoLsoyBNYg9la80wTEhVHFmQmubp7q7ZCVZCrec4BnzXk5iGDMZC38JJOZBRKFMMJHdwthy7'; +const PAGE_ID = process.env.META_PAGE_ID || '61588173848643'; + +async function publishPost(message, imageUrl = null) { + if (!PAGE_ID) { + throw new Error('META_PAGE_ID (Page ID) is required to publish a post.'); + } + + const endpoint = imageUrl ? `${PAGE_ID}/photos` : `${PAGE_ID}/feed`; + const url = new URL(`https://graph.facebook.com/v19.0/${endpoint}`); + url.searchParams.append('access_token', ACCESS_TOKEN); + + const payload = imageUrl ? { caption: message, url: imageUrl } : { message }; + + const res = await fetch(url.toString(), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + + const data = await res.json(); + if (data.error) { + throw new Error(`Meta Graph API Error: ${data.error.message}`); + } + return data; +} + +if (require.main === module) { + const pageId = process.argv[2] || PAGE_ID; + const msg = process.argv[3] || 'اختبار النشر التلقائي عبر Tripz AI Agent'; + if (!pageId) { + console.error('Please provide Page ID as first parameter: node scripts/publish-facebook-post.js "[Message]"'); + process.exit(1); + } + publishPost(msg).then(res => console.log('Post published successfully:', res)).catch(err => console.error(err.message)); +} + +module.exports = { publishPost }; diff --git a/scripts/publish-now.js b/scripts/publish-now.js new file mode 100644 index 0000000..1222671 --- /dev/null +++ b/scripts/publish-now.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node +/** + * Direct Live Publishing Script for Facebook Page + */ + +const ACCESS_TOKEN = 'EAAM8GXpb76wBSLELO5tWBvXM2NG6aSsZBjHUHKK35nATiw5GFgyEAJ5WoiK68NudGqpGlcl2mZB6bUpR5KbtSFPzIpaANhbpXsjDWo8UFCNbWOHKcnGcXM20G4umUoLsoyBNYg9la80wTEhVHFmQmubp7q7ZCVZCrec4BnzXk5iGDMZC38JJOZBRKFMMJHdwthy7'; +const PAGE_ID = '61588173848643'; + +const message = `شركتك تحمل اسمك… وتطبيقك يحمل اسم غيرك؟ 🚘📱 + +كل رحلة ينفذها أسطولك اليوم تبني علامة شركة أخرى. في Tripz نطلق لك منصة نقل متكاملة باسمك، بلونك، وبشعارك خلال 30 يوماً فقط! + +🔹 تطبيق الراكب 🔹 تطبيق السائق 🔹 لوحة التحكم و Dispatch +✅ بياناتك ملكك بعقد مكتوب +✅ شفافية مطلقة بأسعار معلنة +✅ محرك مجرّب وسريع منذ 2019 + +📊 احسب توفيرك السنوي: https://tripz-egypt.com/apps/ +💬 تواصل معنا عبر واتساب: https://wa.me/962798583052`; + +async function main() { + console.log('Publishing post to Facebook Page', PAGE_ID, '...'); + const url = `https://graph.facebook.com/v19.0/${PAGE_ID}/feed`; + const res = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + access_token: ACCESS_TOKEN, + message: message + }) + }); + const data = await res.json(); + console.log('Response from Meta:', JSON.stringify(data, null, 2)); +} + +main();