/* global React */ // ============================================================ // Pro / monetization — entitlement store, AI quota, upgrade modal // ============================================================ const { useState: useStatePro, useEffect: useEffectPro } = React; const PRO_KEY = "rb_pro_v1"; const AI_KEY = "rb_ai_used_v1"; const AI_LIMIT = 3; const PRO_PRICE = "₹" + (((window.RB_CONFIG && window.RB_CONFIG.PRO_PRICE_INR) || 599)); // curated "premium" template set — shown with a PRO badge in the gallery const PRO_TEMPLATES = new Set([ "spectrum", "onyx", "atlas", "prism", "vertex", "summit", "quill", "meridian", "ember", "terminal", "canvas", "globe", ]); function isProTemplate(id) { return PRO_TEMPLATES.has(id); } const Pro = { // Entitlement: in live mode it's the Supabase-backed flag; otherwise localStorage (demo). is() { if (window.RB_LIVE) return window.__rbProActive === true; try { return localStorage.getItem(PRO_KEY) === "1"; } catch (e) { return false; } }, // Demo-only local activation (used when no backend is configured). activate() { try { localStorage.setItem(PRO_KEY, "1"); } catch (e) {} this._emit(); }, cancel() { try { localStorage.removeItem(PRO_KEY); } catch (e) {} this._emit(); }, price: PRO_PRICE, aiLimit: AI_LIMIT, aiUsed() { try { return parseInt(localStorage.getItem(AI_KEY) || "0", 10) || 0; } catch (e) { return 0; } }, aiRemaining() { return this.is() ? Infinity : Math.max(0, AI_LIMIT - this.aiUsed()); }, canUseAI() { return this.is() || this.aiUsed() < AI_LIMIT; }, recordAI() { if (this.is()) return; try { localStorage.setItem(AI_KEY, String(this.aiUsed() + 1)); } catch (e) {} this._emit(); }, // central gate: returns true if AI may run; otherwise opens the paywall guardAI() { if (this.canUseAI()) return true; this.requestUpgrade("ai"); return false; }, requestUpgrade(reason) { window.dispatchEvent(new CustomEvent("rb-upgrade", { detail: { reason: reason || "default" } })); }, _emit() { window.dispatchEvent(new Event("rb-pro-change")); }, // ---- call a Supabase edge function ----------------------- async _invoke(fn, body) { const { data, error } = await window.sb.functions.invoke(fn, { body: body || {} }); if (error) throw error; if (data && data.error) throw new Error(data.error); return data; }, // ---- Razorpay recurring subscription checkout ------------ // Demo mode (no keys): instantly activate locally. // Live mode: sign-in → create subscription → Razorpay mandate → verify → refresh. async startCheckout(reason) { if (!window.RB_LIVE || !window.sb) { this.activate(); return { demo: true }; } if (!window.RBAuth || !window.RBAuth.user()) { window.requestAuth({ mode: "signup", next: () => Pro.startCheckout(reason) }); return { needAuth: true }; } if (!window.Razorpay) { window.alert("Payment library failed to load. Check your connection."); return { error: true }; } let sub; try { sub = await this._invoke("razorpay-create-subscription", { reason: reason || "default" }); } catch (e) { window.alert("Couldn't start checkout: " + (e.message || e)); return { error: true }; } return new Promise((resolve) => { const rzp = new window.Razorpay({ key: window.RB_CONFIG.RAZORPAY_KEY_ID, subscription_id: sub.subscription_id, name: "get-resume.com", description: "Pro subscription — " + Pro.price + "/mo", prefill: { email: (window.RBAuth.user() || {}).email || "" }, theme: { color: "#111111" }, handler: async (resp) => { try { await Pro._invoke("razorpay-verify", { razorpay_payment_id: resp.razorpay_payment_id, razorpay_subscription_id: resp.razorpay_subscription_id, razorpay_signature: resp.razorpay_signature, }); await window.refreshEntitlement(); resolve({ ok: true }); } catch (e) { window.alert("Payment verification failed: " + (e.message || e)); resolve({ ok: false }); } }, modal: { ondismiss: () => resolve({ dismissed: true }) }, }); rzp.open(); }); }, // Cancel: server-side in live mode, local in demo mode. async cancelRemote() { if (window.RB_LIVE && window.sb) { try { await Pro._invoke("razorpay-cancel", {}); } catch (e) { window.alert("Couldn't cancel: " + (e.message || e)); } await window.refreshEntitlement(); } else { this.cancel(); } }, }; // keep the class in sync so CSS can hide the watermark for Pro users function syncProBodyClass() { try { document.body.classList.toggle("rb-pro", Pro.is()); } catch (e) {} } window.addEventListener("rb-pro-change", syncProBodyClass); setTimeout(syncProBodyClass, 0); // hook: re-render a component when Pro state / AI usage changes function useProState() { const [, force] = useStatePro(0); useEffectPro(() => { const h = () => force((x) => x + 1); window.addEventListener("rb-pro-change", h); return () => window.removeEventListener("rb-pro-change", h); }, []); return { isPro: Pro.is(), aiUsed: Pro.aiUsed(), aiRemaining: Pro.aiRemaining(), aiLimit: Pro.aiLimit, status: window.__rbProStatus || null, // active | pending | halted | cancelled | … until: window.__rbProUntil || null, // current period end (ISO) nextCharge: window.__rbProNextCharge || null, // next auto-debit (ISO) }; } // ---- plain-text résumé export (the free tier) ------------------------------ function resumeToText(r) { if (!r) return ""; const L = []; const rule = () => L.push("─".repeat(46)); const nm = (s) => (window.skillName ? window.skillName(s) : (s && s.name) || s); L.push((r.name || "").toUpperCase()); if (r.title) L.push(r.title); const contact = [r.email, r.phone, r.location, r.website].filter(Boolean).join(" · "); if (contact) L.push(contact); L.push(""); if (r.summary && r.summary.trim()) { L.push("SUMMARY"); rule(); L.push(r.summary.trim()); L.push(""); } if ((r.experience || []).length) { L.push("EXPERIENCE"); rule(); r.experience.forEach((e) => { const dates = [e.start, e.end].filter(Boolean).join(" – "); const head = [e.role, e.company].filter(Boolean).join(" · "); L.push(dates ? `${head} (${dates})` : head); if (e.location) L.push(e.location); (e.bullets || []).filter((b) => b && b.trim()).forEach((b) => L.push(" • " + b.trim())); L.push(""); }); } if ((r.skills || []).length) { L.push("SKILLS"); rule(); L.push(r.skills.map(nm).filter(Boolean).join(", ")); L.push(""); } if ((r.education || []).length) { L.push("EDUCATION"); rule(); r.education.forEach((e) => { const dates = [e.start, e.end].filter(Boolean).join(" – "); const head = [e.degree, e.school].filter(Boolean).join(" · "); L.push(dates ? `${head} (${dates})` : head); }); L.push(""); } // any extra array-based sections (projects, awards, certifications, languages, interests…) const handled = new Set(["name", "title", "email", "phone", "location", "website", "summary", "experience", "skills", "education", "photo", "order"]); Object.keys(r).forEach((k) => { if (handled.has(k)) return; const v = r[k]; if (!Array.isArray(v) || !v.length) return; L.push(k.toUpperCase()); rule(); v.forEach((item) => { if (typeof item === "string") L.push(" • " + item); else if (item && typeof item === "object") { const t = item.title || item.name || item.label || ""; const d = item.detail || item.org || item.issuer || item.level || ""; L.push(" • " + [t, d].filter(Boolean).join(" — ")); } }); L.push(""); }); return L.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n"; } async function copyText(text) { try { await navigator.clipboard.writeText(text); return true; } catch (e) { try { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.focus(); ta.select(); const ok = document.execCommand("copy"); ta.remove(); return ok; } catch (_) { return false; } } } // ============================================================ // Upgrade modal — reason-aware headline, ₹999/mo, benefit list // ============================================================ const UP_COPY = { pdf: { badge: "Pro feature", h: "Download your résumé as a polished PDF", p: "Your résumé is free to build and edit. Upgrade to Pro to download the print-ready, branding-free PDF — exactly as you see it." }, cover: { badge: "Pro feature", h: "Download your cover letter as a PDF", p: "Cover-letter PDF export is part of Pro — beautifully matched to your résumé template and ready to send." }, ai: { badge: "You're out of free AI", h: "Keep writing with unlimited AI", p: "You've used your free AI rewrites. Go Pro for unlimited AI on every bullet, summary and cover letter." }, template: { badge: "Premium template", h: "Unlock every premium template", p: "This design is part of the Pro collection. Upgrade to use and download all premium templates, branding-free." }, nav: { badge: "get-resume.com Pro", h: "Everything you need to land the role", p: "Unlimited AI, print-ready PDFs, premium templates and more — for one simple monthly price." }, default: { badge: "get-resume.com Pro", h: "Go Pro and get the full studio", p: "Unlock PDF downloads, unlimited AI and every premium template." }, }; const PRO_BENEFITS = [ ["download", "Unlimited PDF downloads", "Résumés & cover letters, print-ready"], ["infinity", "Unlimited AI writing", "Rewrite, quantify & draft as much as you like"], ["layers", "All premium templates", "Every design in the studio"], ["mail", "Cover-letter PDF export", "Matched to your résumé, on one document"], ["star", "Branding-free exports", "Nothing but your résumé on the page"], ["folder", "Multiple saved résumés", "Tailor a version for every application"], ]; const STATUS_LABEL = { active: "Active", authenticated: "Active", pending: "Payment pending", halted: "Paused — payment failed", cancelled: "Cancelling at period end", completed: "Completed", expired: "Expired", created: "Awaiting first payment", }; function fmtDate(iso) { if (!iso) return null; try { return new Date(iso).toLocaleDateString(undefined, { day: "numeric", month: "short", year: "numeric" }); } catch (e) { return null; } } function UpgradeModal({ reason, onClose }) { const manage = reason === "manage"; const copy = UP_COPY[reason] || UP_COPY.default; const [done, setDone] = useStatePro(false); const { status, until, nextCharge } = useProState(); useEffectPro(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, []); const [busy, setBusy] = useStatePro(false); const subscribe = async () => { if (busy) return; setBusy(true); const r = await Pro.startCheckout(reason); setBusy(false); if (r && (r.ok || r.demo)) { setDone(true); setTimeout(onClose, 1500); } else if (r && r.needAuth) { onClose(); } // auth modal will resume checkout }; if (manage) { return (
{ if (e.target === e.currentTarget) onClose(); }}>
You're on Pro

get-resume.com Pro is active

Unlimited PDFs, unlimited AI, every premium template — all unlocked.

{Pro.price}/ month {STATUS_LABEL[status] || "Renews monthly"}
{(until || nextCharge) && (
{status === "cancelled" ? <>Access until {fmtDate(until)} — won't renew. : nextCharge ? <>Next charge {fmtDate(nextCharge)}. : until ? <>Current period ends {fmtDate(until)}. : null}
)}
    {PRO_BENEFITS.map(([ic, t]) => { const Ic = Icons[ic] || Icons.check; return
  • {t}
  • ; })}
{status !== "cancelled" && ( )}
); } return (
{ if (e.target === e.currentTarget) onClose(); }}>
{done ? (

Welcome to Pro

Everything's unlocked. Go grab that PDF.

) : ( <>
{copy.badge}

{copy.h}

{copy.p}

{Pro.price}/ monthCancel anytime
    {PRO_BENEFITS.map(([ic, t, sub]) => { const Ic = Icons[ic] || Icons.check; return (
  • {t}{sub && {sub}}
  • ); })}

Free to build, edit & copy as text — always. By subscribing you agree to our .

)}
); } // Single gate mounted once at the app root; listens for upgrade requests. function UpgradeGate() { const [state, setState] = useStatePro(null); // { reason } | null useEffectPro(() => { const h = (e) => setState({ reason: (e.detail && e.detail.reason) || "default" }); window.addEventListener("rb-upgrade", h); return () => window.removeEventListener("rb-upgrade", h); }, []); if (!state) return null; return setState(null)} />; } Object.assign(window, { Pro, useProState, UpgradeModal, UpgradeGate, resumeToText, copyText, isProTemplate, PRO_TEMPLATES, });