Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/app/api/access-request/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,32 @@ export async function POST(req: Request) {
text: `${requesterName} (${requesterRole}) from ${requesterFirm} wants access.\nEmail: ${requesterEmail}\nLinkedIn: ${requesterLinkedIn}\nStartup ID: ${startupId}`
});

try {
await fetch("https://data.pendo.io/data/track", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-pendo-integration-key": "4e86eb3f-3d3e-40c7-827d-b05f554e14be",
},
body: JSON.stringify({
type: "track",
event: "access_request_submitted",
visitorId: requesterEmail,
accountId: startupId === "general_access" ? "system" : startupId,
timestamp: Date.now(),
properties: {
startupId: startupId || "general_access",
requesterRole,
requesterFirm: requesterFirm || "Independent",
hasLinkedIn: !!requesterLinkedIn,
isGeneralAccess: startupId === "general_access",
},
}),
});
} catch (e) {
console.error("Pendo track error:", e);
}

return NextResponse.json({ success: true, requestId: newRequest.id });

} catch (error: any) {
Expand Down
24 changes: 24 additions & 0 deletions src/app/api/competitions/verify/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,29 @@ export async function GET(req: NextRequest) {
data: { emailVerified: true, verifyToken: null },
});

try {
await fetch("https://data.pendo.io/data/track", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-pendo-integration-key": "4e86eb3f-3d3e-40c7-827d-b05f554e14be",
},
body: JSON.stringify({
type: "track",
event: "competition_vote_verified",
visitorId: vote.email || "system",
accountId: "system",
timestamp: Date.now(),
properties: {
voteId: vote.id,
category: vote.category,
tokenAge: String(age),
},
}),
});
} catch (e) {
console.error("Pendo track error:", e);
}

return NextResponse.json({ ok: true });
}
27 changes: 27 additions & 0 deletions src/app/api/paystack/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,33 @@ export async function GET(request: Request) {
},
});

try {
await fetch("https://data.pendo.io/data/track", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-pendo-integration-key": "4e86eb3f-3d3e-40c7-827d-b05f554e14be",
},
body: JSON.stringify({
type: "track",
event: "card_payment_completed",
visitorId: data.data?.customer?.email || "system",
accountId: startupId,
timestamp: Date.now(),
properties: {
startupId,
tier: packageValue,
packageValue,
paymentReference: reference,
chain: "paystack",
pinnedDuration: String(durationMinutes),
},
}),
});
} catch (e) {
console.error("Pendo track error:", e);
}

return NextResponse.redirect(
new URL(`/pricing?status=success&tier=${packageValue}`, request.url)
);
Expand Down
26 changes: 26 additions & 0 deletions src/app/api/paystack/initialize/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ export async function POST(request: Request) {
);
}

try {
await fetch("https://data.pendo.io/data/track", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-pendo-integration-key": "4e86eb3f-3d3e-40c7-827d-b05f554e14be",
},
body: JSON.stringify({
type: "track",
event: "card_payment_initiated",
visitorId: email,
accountId: startupId,
timestamp: Date.now(),
properties: {
startupId,
email,
packageValue,
amountNGN: String(amountKobo / 100),
paymentReference: data.data.reference,
},
}),
});
} catch (e) {
console.error("Pendo track error:", e);
}

return NextResponse.json({
authorizationUrl: data.data.authorization_url,
reference: data.data.reference,
Expand Down
25 changes: 25 additions & 0 deletions src/app/api/reviews/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ export async function POST(req: NextRequest) {
},
});

try {
await fetch("https://data.pendo.io/data/track", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-pendo-integration-key": "4e86eb3f-3d3e-40c7-827d-b05f554e14be",
},
body: JSON.stringify({
type: "track",
event: "review_submitted",
visitorId: email,
accountId: startupId,
timestamp: Date.now(),
properties: {
startupId,
rating: String(rating),
commentLength: String(comment.trim().length),
reviewerName: name,
},
}),
});
} catch (e) {
console.error("Pendo track error:", e);
}

return NextResponse.json({
success: true,
review: {
Expand Down
10 changes: 10 additions & 0 deletions src/components/CompetitionsClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ function VoteModal({
});
const data = await res.json();
if (!res.ok) { toast.error(data.error || "Something went wrong."); return; }
if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("competition_vote_cast", {
category,
votedForName: item.name,
hasHackathonProjectId: item.type === "hackathon",
hasAiAgentId: item.type === "agent",
voterEmail: email,
});
}

setSent(true);
onSuccess(item.id);
} catch {
Expand Down
8 changes: 8 additions & 0 deletions src/components/GlobalSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ export default function GlobalSearch() {
const handleSearch = (e?: React.FormEvent) => {
if (e) e.preventDefault();
if (!query.trim()) return;

if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("registry_search_executed", {
query: query.trim(),
searchSource: "global_search_bar",
});
}

router.push(`/registry?search=${encodeURIComponent(query)}`);
};

Expand Down
11 changes: 10 additions & 1 deletion src/components/OnboardingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,17 @@ export function OnboardingForm({ userId, defaultName, defaultEmail }: Props) {
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Something went wrong");

if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("onboarding_completed", {
role: form.role,
hasTwitterHandle: !!form.twitterHandle,
hasBio: !!form.bio,
displayName: form.name,
});
}

toast.success("Profile saved. Let's list your product.");

window.location.href = "/submit";
} catch (err: any) {
toast.error(err.message);
Expand Down
25 changes: 24 additions & 1 deletion src/components/PaymentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ export function PaymentModal({ startupId, status, onClose, onSuccess }: PaymentM
});

if (!res.ok) throw new Error("Database sync failed.");

if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("solana_payment_completed", {
startupId,
txHash: signature,
packageValue: selectedPackage.value,
tier: selectedPackage.value,
paymentAmount: String(selectedPackage.price),
currency: "SOL",
});
}

toast.success("Solana boost active!", { id: toastId });
onSuccess?.(await res.json());
onClose();
Expand Down Expand Up @@ -187,7 +199,18 @@ export function PaymentModal({ startupId, status, onClose, onSuccess }: PaymentM
<div className="grid grid-cols-1 gap-3">
{AdminConfig.PIN_PACKAGES.filter((p) => p.price > 0).map((pkg) => (
<button key={pkg.value}
onClick={() => { setSelectedPackage(pkg); setStep("method"); }}
onClick={() => {
setSelectedPackage(pkg);
if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("boost_package_selected", {
startupId,
selectedPackage: pkg.value,
packagePrice: String(pkg.price),
selectedTier: pkg.value,
});
}
setStep("method");
}}
className="p-4 rounded-2xl text-left transition-all w-full"
style={{ border: "1px solid rgba(255,255,255,0.06)", background: "rgba(255,255,255,0.02)", cursor: "pointer" }}
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.borderColor = "var(--accent)"; (e.currentTarget as HTMLElement).style.background = "rgba(91,43,255,0.05)"; }}
Expand Down
19 changes: 19 additions & 0 deletions src/components/StartupPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ export default function StartupPageClient({ startup }: Props) {
try {
await navigator.clipboard.writeText(pageUrl);
setCopied(true);

if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("product_shared", {
startupId: startup.id,
startupName: startup.name,
shareSource: "startup_detail_page",
startupCategory: startup.category,
});
}

toast.success("Link copied — share it everywhere.");
setTimeout(() => setCopied(false), 2000);
} catch {
Expand Down Expand Up @@ -175,6 +185,15 @@ export default function StartupPageClient({ startup }: Props) {
rel="noopener noreferrer"
className="ap-btn-secondary flex items-center gap-2"
style={{ padding: "0.6rem 1.25rem" }}
onClick={() => {
if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("product_shared_on_twitter", {
startupId: startup.id,
startupName: startup.name,
startupCategory: startup.category,
});
}
}}
>
<RiTwitterXLine className="w-4 h-4" />
Share on X
Expand Down
25 changes: 25 additions & 0 deletions src/components/SubmitForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ export default function SubmitStartup() {
logoUrl: data.logoUrl || prev.logoUrl,
}));
setScraped(true);

if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("website_metadata_scraped", {
scrapedUrl: url,
hasOgTitle: !!data.name,
hasOgDescription: !!data.tagline,
hasOgImage: !!data.bannerUrl,
hasFavicon: !!data.logoUrl,
scrapeSuccess: true,
});
}

toast.success("Metadata pulled from your site.");
} catch {
toast.error("Scrape failed. Fill in manually.");
Expand Down Expand Up @@ -339,6 +351,19 @@ export default function SubmitStartup() {

track("Product Submitted");

if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("product_submitted", {
category: formData.category,
hasWebsite: !!formData.website,
hasTwitter: !!formData.twitter,
hasPitchDeck: !!formData.pitchDeckUrl,
hasBannerUrl: !!formData.bannerUrl,
hasLogoUrl: !!formData.logoUrl,
wasScraped: scraped,
founderName: formData.founderName,
});
}

confetti({
particleCount: 150,
spread: 70,
Expand Down
11 changes: 11 additions & 0 deletions src/components/submit/AgentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ export function AgentForm() {
body: JSON.stringify({ ...form, userId: session?.user?.id }),
});
if (!res.ok) { const err = await res.json(); toast.error(err.error || "Submission failed."); return; }
if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("ai_agent_submitted", {
category: form.category,
hasAgentUrl: !!form.agentUrl,
hasGithubUrl: !!form.githubUrl,
hasInstallGuide: !!form.installGuide,
hasUsageThreadUrl: !!form.usageThreadUrl,
agentName: form.name,
});
}

confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } });
toast.success("AI Agent submitted! Live now.");
router.push("/agents");
Expand Down
8 changes: 8 additions & 0 deletions src/components/submit/HackathonForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ export function HackathonForm() {
body: JSON.stringify({ ...form, userId: session?.user?.id }),
});
if (!res.ok) { const err = await res.json(); toast.error(err.error || "Submission failed."); return; }
if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("hackathon_project_submitted", {
category: form.category,
hasGithubUrl: !!form.githubUrl,
projectName: form.name,
});
}

confetti({ particleCount: 120, spread: 80, origin: { y: 0.6 } });
toast.success("Hackathon build submitted! Live now.");
router.push("/hackathon");
Expand Down
9 changes: 8 additions & 1 deletion src/components/submit/SubmitGate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,14 @@ function TypePicker({ onSelect }: { onSelect: (t: SubmitType) => void }) {
initial={{ opacity: 0, x: -16 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.4, delay: i * 0.08, ease: [0.16, 1, 0.3, 1] }}
onClick={() => onSelect(type.id)}
onClick={() => {
if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("submission_type_selected", {
submissionType: type.id,
});
}
onSelect(type.id);
}}
className="group w-full flex items-center gap-5 p-5 rounded-2xl text-left transition-all duration-200"
style={{
background: "var(--bg-2)",
Expand Down
12 changes: 12 additions & 0 deletions src/hooks/useBoost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ export function useBoost() {

if (!res.ok) throw new Error("Registry sync failed.");

if (typeof window !== "undefined" && window.pendo) {
window.pendo.track("boost_payment_completed", {
startupId,
chain: "base",
txHash: boostTx,
packageValue,
tier: packageValue,
paymentAmount: String(price),
currency: "USDC",
});
}

toast.success("Boost active!", { id: toastId });
onSuccess?.(await res.json());

Expand Down