TafadzwaTaps commited on
Commit
a41c679
·
1 Parent(s): 4bc490b

fix: latest updates

Browse files
Files changed (8) hide show
  1. app.py +3 -6
  2. config.py +9 -8
  3. db.py +3 -3
  4. frontend/api.js +10 -22
  5. frontend/checkout.html +5 -0
  6. frontend/index.html +2 -1
  7. frontend/pro.html +81 -6
  8. paypal.py +2 -2
app.py CHANGED
@@ -44,8 +44,8 @@ import db as DB
44
 
45
  _PLAN_LIMITS = {
46
  "free": {
47
- "daily_scans": 5,
48
- "daily_repos": 2,
49
  "history_limit": 10,
50
  "ai_depth": "basic",
51
  "repo_scan": True,
@@ -402,10 +402,7 @@ async def ai_enrich(text: str, findings: list, depth: str = "full") -> dict:
402
  return {"explanation": "AI unavailable after retries.", "fixes": []}
403
 
404
  # ---- ROUTES ----
405
- @app.get("/api/dev/mode")
406
- def dev_mode_status():
407
- # config.py removed — DEV_MODE no longer applicable
408
- return {"dev_mode": False, "status": "PRODUCTION MODE"}
409
 
410
  @app.post("/auth/register")
411
  def register(req: RegisterRequest, request: Request):
 
44
 
45
  _PLAN_LIMITS = {
46
  "free": {
47
+ "daily_scans": 10, # Improved free tier: 10 file scans per day
48
+ "daily_repos": 2, # 2 repository scans per day
49
  "history_limit": 10,
50
  "ai_depth": "basic",
51
  "repo_scan": True,
 
402
  return {"explanation": "AI unavailable after retries.", "fixes": []}
403
 
404
  # ---- ROUTES ----
405
+
 
 
 
406
 
407
  @app.post("/auth/register")
408
  def register(req: RegisterRequest, request: Request):
config.py CHANGED
@@ -1,10 +1,11 @@
1
- import os
 
 
 
 
 
2
 
3
- # ============================================================
4
- # CONFIG — single source of truth
5
- # DEV_MODE=true in .env → all plan gates unlocked
6
- # DEV_MODE=false (or unset) → production gating enforced
7
- # ============================================================
8
 
9
- _raw = os.getenv("DEV_MODE", "true").lower().strip()
10
- DEV_MODE: bool = _raw in ("1", "true", "yes", "on")
 
1
+ """
2
+ config.py — Application configuration
3
+ ========================================
4
+ DEV_MODE has been removed. All plan gates are enforced in production.
5
+ Configuration is read from environment variables only.
6
+ """
7
 
8
+ import os
 
 
 
 
9
 
10
+ # App base URL for redirect links (e.g. PayPal return URLs)
11
+ APP_BASE_URL: str = os.getenv("APP_BASE_URL", "http://localhost:3000")
db.py CHANGED
@@ -202,8 +202,8 @@ def check_and_increment_scan(user_id: str) -> tuple[bool, str]:
202
  update_user(user_id, {"scans_today": 1, "last_scan_date": today})
203
  return True, "ok"
204
 
205
- if count >= 1:
206
- return False, "Free plan allows 1 scan per day. Upgrade to Pro for unlimited scans."
207
 
208
  update_user(user_id, {"scans_today": count + 1})
209
  return True, "ok"
@@ -465,7 +465,7 @@ def _plan_limits_for(plan: str) -> dict:
465
  """Return scan limits for a given effective plan."""
466
  LIMITS = {
467
  "free": {
468
- "daily_scans": 5,
469
  "daily_repos": 2,
470
  "history_limit": 10,
471
  "ai_depth": "basic",
 
202
  update_user(user_id, {"scans_today": 1, "last_scan_date": today})
203
  return True, "ok"
204
 
205
+ if count >= 10:
206
+ return False, "Free plan allows 10 scans per day. Upgrade to Pro for unlimited scans."
207
 
208
  update_user(user_id, {"scans_today": count + 1})
209
  return True, "ok"
 
465
  """Return scan limits for a given effective plan."""
466
  LIMITS = {
467
  "free": {
468
+ "daily_scans": 10, # Improved free tier
469
  "daily_repos": 2,
470
  "history_limit": 10,
471
  "ai_depth": "basic",
frontend/api.js CHANGED
@@ -19,21 +19,14 @@ function clearAuth() {
19
  // ── Plan helpers ───────────────────────────────────────────
20
  // Source of truth: localStorage values set by getMe() after every page load.
21
  // pro_trial counts as full Pro access.
22
- let _userPlan = window.DEV_MODE ? "enterprise" : (localStorage.getItem("user_plan") || "free");
23
  let _userLimits = null;
24
  try {
25
- if (window.DEV_MODE) {
26
- _userLimits = { daily_scans:-1, history_limit:9999, ai_depth:"full",
27
- repo_scan:true, pdf_download:true, advanced_ai:true, api_access:true,
28
- cve_enrichment:true, scheduled_scans:true, json_export:true };
29
- } else {
30
- const s = localStorage.getItem("user_limits");
31
- if (s) _userLimits = JSON.parse(s);
32
- }
33
  } catch {}
34
 
35
  function isProUser() {
36
- if (window.DEV_MODE) return true;
37
  if (localStorage.getItem("is_pro") === "true") return true;
38
  if (localStorage.getItem("trial_active") === "true") return true;
39
  const p = localStorage.getItem("user_plan") || "free";
@@ -50,14 +43,12 @@ function getTrialDaysLeft() {
50
  }
51
 
52
  function getUserPlan() {
53
- if (window.DEV_MODE) return "enterprise";
54
  return _userPlan;
55
  }
56
 
57
  function getUserLimits() { return _userLimits; }
58
 
59
  function cachePlanData(plan, limits) {
60
- if (window.DEV_MODE) return;
61
  _userPlan = plan;
62
  _userLimits = limits;
63
  localStorage.setItem("user_plan", plan);
@@ -65,7 +56,6 @@ function cachePlanData(plan, limits) {
65
  }
66
 
67
  function canAccessFeature(feature) {
68
- if (window.DEV_MODE) return true;
69
  if (isProUser()) return true;
70
  // Free plan has limited access
71
  const freeFeatures = { repo_scan: true, basic_scan: true };
@@ -195,8 +185,8 @@ async function analyzeCode(text) {
195
  body: JSON.stringify({ text })
196
  });
197
  const data = await safeJson(res);
198
- // Only cache plan data in production mode
199
- if (!window.DEV_MODE && data.plan && data.usage_limit) {
200
  cachePlanData(data.plan, null);
201
  document.dispatchEvent(new CustomEvent("planUpdated", { detail: data }));
202
  }
@@ -234,13 +224,11 @@ async function getMe() {
234
  try {
235
  const res = await apiRequest("/api/me");
236
  const data = await safeJson(res);
237
- if (!window.DEV_MODE) {
238
- if (data?.plan) localStorage.setItem("user_plan", data.plan);
239
- if (data?.is_pro !== undefined) localStorage.setItem("is_pro", data.is_pro ? "true" : "false");
240
- if (data?.trial_active !== undefined) localStorage.setItem("trial_active", data.trial_active ? "true" : "false");
241
- if (data?.days_left !== undefined) localStorage.setItem("trial_days_left", String(data.days_left));
242
- cachePlanData(data.plan, data.limits || null);
243
- }
244
  return data;
245
  } catch (err) {
246
  if (err.message.includes("404")) {
 
19
  // ── Plan helpers ───────────────────────────────────────────
20
  // Source of truth: localStorage values set by getMe() after every page load.
21
  // pro_trial counts as full Pro access.
22
+ let _userPlan = localStorage.getItem("user_plan") || "free";
23
  let _userLimits = null;
24
  try {
25
+ const s = localStorage.getItem("user_limits");
26
+ if (s) _userLimits = JSON.parse(s);
 
 
 
 
 
 
27
  } catch {}
28
 
29
  function isProUser() {
 
30
  if (localStorage.getItem("is_pro") === "true") return true;
31
  if (localStorage.getItem("trial_active") === "true") return true;
32
  const p = localStorage.getItem("user_plan") || "free";
 
43
  }
44
 
45
  function getUserPlan() {
 
46
  return _userPlan;
47
  }
48
 
49
  function getUserLimits() { return _userLimits; }
50
 
51
  function cachePlanData(plan, limits) {
 
52
  _userPlan = plan;
53
  _userLimits = limits;
54
  localStorage.setItem("user_plan", plan);
 
56
  }
57
 
58
  function canAccessFeature(feature) {
 
59
  if (isProUser()) return true;
60
  // Free plan has limited access
61
  const freeFeatures = { repo_scan: true, basic_scan: true };
 
185
  body: JSON.stringify({ text })
186
  });
187
  const data = await safeJson(res);
188
+ // Cache plan data from response for usage counters
189
+ if (data.plan && data.usage_limit) {
190
  cachePlanData(data.plan, null);
191
  document.dispatchEvent(new CustomEvent("planUpdated", { detail: data }));
192
  }
 
224
  try {
225
  const res = await apiRequest("/api/me");
226
  const data = await safeJson(res);
227
+ if (data?.plan) localStorage.setItem("user_plan", data.plan);
228
+ if (data?.is_pro !== undefined) localStorage.setItem("is_pro", data.is_pro ? "true" : "false");
229
+ if (data?.trial_active !== undefined) localStorage.setItem("trial_active", data.trial_active ? "true" : "false");
230
+ if (data?.days_left !== undefined) localStorage.setItem("trial_days_left", String(data.days_left));
231
+ cachePlanData(data.plan, data.limits || null);
 
 
232
  return data;
233
  } catch (err) {
234
  if (err.message.includes("404")) {
frontend/checkout.html CHANGED
@@ -275,6 +275,11 @@ function esc(s){ return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;')
275
  document.getElementById('paySub').innerHTML =
276
  '<span style="color:var(--warning);"><i class="bi bi-lock me-1"></i>Pro plan required</span> — upgrade below to access all Pro features.';
277
  }
 
 
 
 
 
278
 
279
  // Pre-fill email
280
  var email = localStorage.getItem('user_email') || '';
 
275
  document.getElementById('paySub').innerHTML =
276
  '<span style="color:var(--warning);"><i class="bi bi-lock me-1"></i>Pro plan required</span> — upgrade below to access all Pro features.';
277
  }
278
+ if (p.get('reason') === 'trial_expired') {
279
+ document.getElementById('payTitle').textContent = 'Your Trial Has Ended';
280
+ document.getElementById('paySub').innerHTML =
281
+ 'Your 30-day free trial has expired. Upgrade to Pro at <strong>$1.99/month</strong> to continue unlimited scanning, PDF reports, and all advanced features.';
282
+ }
283
 
284
  // Pre-fill email
285
  var email = localStorage.getItem('user_email') || '';
frontend/index.html CHANGED
@@ -234,7 +234,8 @@
234
  <div class="price-amount">$0</div>
235
  <div class="price-period">forever free</div>
236
  <hr style="border-color:var(--border);margin:20px 0;">
237
- <div class="price-feature"><i class="bi bi-check-lg"></i> 5 scans / day</div>
 
238
  <div class="price-feature"><i class="bi bi-check-lg"></i> AI code analysis</div>
239
  <div class="price-feature"><i class="bi bi-check-lg"></i> CVE lookup</div>
240
  <div class="price-feature"><i class="bi bi-check-lg"></i> API access</div>
 
234
  <div class="price-amount">$0</div>
235
  <div class="price-period">forever free</div>
236
  <hr style="border-color:var(--border);margin:20px 0;">
237
+ <div class="price-feature"><i class="bi bi-check-lg"></i> 10 scans / day</div>
238
+ <div class="price-feature"><i class="bi bi-check-lg"></i> 2 repo scans / day</div>
239
  <div class="price-feature"><i class="bi bi-check-lg"></i> AI code analysis</div>
240
  <div class="price-feature"><i class="bi bi-check-lg"></i> CVE lookup</div>
241
  <div class="price-feature"><i class="bi bi-check-lg"></i> API access</div>
frontend/pro.html CHANGED
@@ -920,24 +920,99 @@ function setMode(mode) {
920
  var sideEl = document.getElementById('sidebarEmail');
921
  if (sideEl && email) sideEl.textContent = email;
922
 
923
- // Load /auth/me to get email and confirm is_pro
924
- fetch(API + '/auth/me', { headers: authHeaders() })
925
- .then(function(r) { return r.json(); })
 
 
 
926
  .then(function(raw) {
 
927
  var d = raw.data || raw;
 
928
  if (d.email) {
929
  if (sideEl) sideEl.textContent = d.email;
930
  localStorage.setItem('user_email', d.email);
931
  }
932
- if (!d.is_pro) {
933
- window.location.replace('checkout.html?reason=pro_required');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
934
  }
 
 
 
935
  })
936
- .catch(function(){});
 
 
 
937
 
938
  setupZipDrop();
939
  })();
940
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
941
  function doLogout() {
942
  localStorage.clear();
943
  window.location.href = 'login.html';
 
920
  var sideEl = document.getElementById('sidebarEmail');
921
  if (sideEl && email) sideEl.textContent = email;
922
 
923
+ // Load /api/me to confirm access and get trial/plan info
924
+ fetch(API + '/api/me', { headers: authHeaders() })
925
+ .then(function(r) {
926
+ if (r.status === 401) { window.location.replace('login.html'); return null; }
927
+ return r.json();
928
+ })
929
  .then(function(raw) {
930
+ if (!raw) return;
931
  var d = raw.data || raw;
932
+
933
  if (d.email) {
934
  if (sideEl) sideEl.textContent = d.email;
935
  localStorage.setItem('user_email', d.email);
936
  }
937
+
938
+ var plan = d.plan || 'free';
939
+ var isPro = d.is_pro || false;
940
+ var trialActive = d.trial_active || false;
941
+ var daysLeft = d.days_left || 0;
942
+ var trialEnd = d.trial_end || '';
943
+
944
+ // Cache plan data for api.js helpers
945
+ localStorage.setItem('user_plan', plan);
946
+ localStorage.setItem('is_pro', isPro ? 'true' : 'false');
947
+ localStorage.setItem('trial_active', trialActive ? 'true' : 'false');
948
+ localStorage.setItem('trial_days_left', String(daysLeft));
949
+
950
+ // Access control: pro, pro_trial (active) and enterprise may enter
951
+ var hasAccess = (plan === 'pro' || plan === 'enterprise') ||
952
+ (plan === 'pro_trial' && trialActive);
953
+
954
+ if (!hasAccess) {
955
+ // Trial expired or plain free user — redirect gracefully
956
+ var reason = (plan === 'pro_trial' && !trialActive) ? 'trial_expired' : 'pro_required';
957
+ window.location.replace('checkout.html?reason=' + reason);
958
+ return;
959
+ }
960
+
961
+ // Show trial countdown banner if on active trial
962
+ if (plan === 'pro_trial' && trialActive && daysLeft > 0) {
963
+ showTrialBanner(daysLeft, trialEnd);
964
  }
965
+
966
+ // Update the plan badge in the topbar
967
+ updatePlanBadge(plan, trialActive, daysLeft);
968
  })
969
+ .catch(function(e) {
970
+ console.warn('Auth check failed', e);
971
+ // Do NOT redirect on network errors — let the user keep working
972
+ });
973
 
974
  setupZipDrop();
975
  })();
976
 
977
+ function showTrialBanner(daysLeft, trialEnd) {
978
+ var urgency = daysLeft <= 3 ? '#f43f5e' : daysLeft <= 7 ? '#fb923c' : '#00ffa3';
979
+ var banner = document.createElement('div');
980
+ banner.id = 'trialBanner';
981
+ banner.style.cssText = [
982
+ 'position:fixed;bottom:16px;left:50%;transform:translateX(-50%);',
983
+ 'background:rgba(15,23,42,.95);border:1px solid ' + urgency + '44;',
984
+ 'border-radius:12px;padding:10px 20px;display:flex;align-items:center;gap:12px;',
985
+ 'font-size:12px;color:var(--text-muted);z-index:9000;',
986
+ 'box-shadow:0 8px 32px rgba(0,0,0,.5);backdrop-filter:blur(10px);',
987
+ 'animation:popIn .3s ease both;'
988
+ ].join('');
989
+ banner.innerHTML =
990
+ '<i class="bi bi-clock" style="color:' + urgency + ';font-size:14px;"></i>' +
991
+ '<span>Your <strong style="color:#fff;">Pro Trial</strong> ends in ' +
992
+ '<strong style="color:' + urgency + ';">' + daysLeft + ' day' + (daysLeft === 1 ? '' : 's') + '</strong>' +
993
+ (trialEnd ? ' · ' + trialEnd : '') + '</span>' +
994
+ '<a href="checkout.html" style="background:linear-gradient(135deg,#5b7bfe,#4361ee);color:#fff;' +
995
+ 'border-radius:8px;padding:5px 12px;font-size:11px;font-weight:600;text-decoration:none;' +
996
+ 'white-space:nowrap;">Upgrade — $1.99/mo</a>' +
997
+ '<button onclick="document.getElementById(\'trialBanner\').remove()" style="' +
998
+ 'background:none;border:none;color:var(--text-faint);cursor:pointer;font-size:16px;line-height:1;' +
999
+ 'padding:0 0 0 4px;">&times;</button>';
1000
+ document.body.appendChild(banner);
1001
+ }
1002
+
1003
+ function updatePlanBadge(plan, trialActive, daysLeft) {
1004
+ var badge = document.querySelector('.pro-badge-tag');
1005
+ if (!badge) return;
1006
+ if (plan === 'pro_trial' && trialActive) {
1007
+ badge.textContent = 'Trial · ' + daysLeft + 'd left';
1008
+ badge.style.color = daysLeft <= 3 ? '#f43f5e' : daysLeft <= 7 ? '#fb923c' : '#00ffa3';
1009
+ } else if (plan === 'enterprise') {
1010
+ badge.textContent = 'Enterprise';
1011
+ } else {
1012
+ badge.textContent = 'Pro';
1013
+ }
1014
+ }
1015
+
1016
  function doLogout() {
1017
  localStorage.clear();
1018
  window.location.href = 'login.html';
paypal.py CHANGED
@@ -38,9 +38,9 @@ MODE = os.getenv("PAYPAL_MODE", "sandbox").lower() # "sandbox" | "liv
38
  APP_BASE_URL = os.getenv("APP_BASE_URL", "http://localhost:3000")
39
 
40
  # Pro plan pricing — change here to update everywhere
41
- PRO_PRICE_USD = "9.99"
42
  PRO_CURRENCY = "USD"
43
- PRO_PLAN_NAME = "SecretScan Pro — Unlimited Scans + PDF Reports"
44
 
45
  _BASE = {
46
  "sandbox": "https://api-m.sandbox.paypal.com",
 
38
  APP_BASE_URL = os.getenv("APP_BASE_URL", "http://localhost:3000")
39
 
40
  # Pro plan pricing — change here to update everywhere
41
+ PRO_PRICE_USD = "1.99"
42
  PRO_CURRENCY = "USD"
43
+ PRO_PLAN_NAME = "SafeAIScan Pro — Unlimited Scans + PDF Reports · $1.99/mo"
44
 
45
  _BASE = {
46
  "sandbox": "https://api-m.sandbox.paypal.com",