Skip to content

Commit 908eca9

Browse files
committed
feat(site): wire sponsorships and guest booking to FlipFeeds Creator OS dev/prod APIs
1 parent a472065 commit 908eca9

9 files changed

Lines changed: 846 additions & 34 deletions

File tree

‎apps/site/src/lib/analytics.ts‎

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ export function classifyActor(
114114
}
115115

116116
function extractHost(urlStr: string | null): string {
117-
if (!urlStr) return "direct";
117+
if (!urlStr) {
118+
return "direct";
119+
}
118120
try {
119121
return new URL(urlStr).hostname || "direct";
120122
} catch {
@@ -132,7 +134,9 @@ export function trackEdgeRequest(options: {
132134
detail?: string;
133135
}): void {
134136
const dataset = options.env.ANALYTICS as AnalyticsEngineDataset | undefined;
135-
if (!dataset || typeof dataset.writeDataPoint !== "function") return;
137+
if (!dataset || typeof dataset.writeDataPoint !== "function") {
138+
return;
139+
}
136140

137141
const { request, pathname, statusCode, durationMs, wantsMarkdown } = options;
138142

@@ -164,18 +168,19 @@ export function trackEdgeRequest(options: {
164168
eventType = "search_api";
165169
} else if (pathname.startsWith("/api/chat")) {
166170
eventType = "chat_api";
167-
} else if (category === "ai_bot" || category === "search_bot" || category === "social_bot") {
171+
} else if (
172+
category === "ai_bot" ||
173+
category === "search_bot" ||
174+
category === "social_bot"
175+
) {
168176
eventType = "bot_crawl";
169177
}
170178

171179
const refererHost = extractHost(request.headers.get("referer"));
172180
const cf = (request as Request & { cf?: { country?: string } }).cf;
173-
const country =
174-
cf?.country || request.headers.get("cf-ipcountry") || "XX";
181+
const country = cf?.country || request.headers.get("cf-ipcountry") || "XX";
175182
const targetOrDetail =
176-
options.detail ||
177-
new URL(request.url).searchParams.get("q") ||
178-
refererHost;
183+
options.detail || new URL(request.url).searchParams.get("q") || refererHost;
179184

180185
try {
181186
dataset.writeDataPoint({
@@ -205,7 +210,9 @@ export function trackClientBeaconEvent(options: {
205210
value?: number;
206211
}): void {
207212
const dataset = options.env.ANALYTICS as AnalyticsEngineDataset | undefined;
208-
if (!dataset || typeof dataset.writeDataPoint !== "function") return;
213+
if (!dataset || typeof dataset.writeDataPoint !== "function") {
214+
return;
215+
}
209216

210217
const refererHost = extractHost(options.request.headers.get("referer"));
211218
const cf = (options.request as Request & { cf?: { country?: string } }).cf;

‎apps/site/src/lib/structured-data.ts‎

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ export function videoObjectSchema(
179179
},
180180
path: string,
181181
): Node | null {
182-
if (!video.youtubeUrl) return null;
182+
if (!video.youtubeUrl) {
183+
return null;
184+
}
183185

184186
const url = absoluteUrl(path, origin);
185187
// Format ISO 8601 duration: PT#M#S or PT#S
@@ -189,9 +191,15 @@ export function videoObjectSchema(
189191
const minutes = Math.floor((video.durationSeconds % 3600) / 60);
190192
const seconds = Math.floor(video.durationSeconds % 60);
191193
let d = "PT";
192-
if (hours > 0) d += `${hours}H`;
193-
if (minutes > 0) d += `${minutes}M`;
194-
if (seconds > 0 || d === "PT") d += `${seconds}S`;
194+
if (hours > 0) {
195+
d += `${hours}H`;
196+
}
197+
if (minutes > 0) {
198+
d += `${minutes}M`;
199+
}
200+
if (seconds > 0 || d === "PT") {
201+
d += `${seconds}S`;
202+
}
195203
durationIso = d;
196204
}
197205

@@ -204,14 +212,18 @@ export function videoObjectSchema(
204212
...(video.uploadDate ? { uploadDate: video.uploadDate } : {}),
205213
...(durationIso ? { duration: durationIso } : {}),
206214
contentUrl: video.youtubeUrl,
207-
embedUrl: video.youtubeUrl.replace("watch?v=", "embed/").replace("youtu.be/", "www.youtube.com/embed/"),
215+
embedUrl: video.youtubeUrl
216+
.replace("watch?v=", "embed/")
217+
.replace("youtu.be/", "www.youtube.com/embed/"),
208218
};
209219
}
210220

211221
export function faqSchema(
212222
faqs: Array<{ question: string; answer: string }>,
213223
): Node | null {
214-
if (!faqs || faqs.length === 0) return null;
224+
if (!faqs || faqs.length === 0) {
225+
return null;
226+
}
215227

216228
return {
217229
"@type": "FAQPage",
@@ -233,7 +245,9 @@ export function faqSchema(
233245
export function extractFaqsFromPortableText(
234246
blocks: unknown,
235247
): Array<{ question: string; answer: string }> {
236-
if (!Array.isArray(blocks)) return [];
248+
if (!Array.isArray(blocks)) {
249+
return [];
250+
}
237251

238252
const faqs: Array<{ question: string; answer: string }> = [];
239253
let currentQuestion: string | null = null;
@@ -251,19 +265,25 @@ export function extractFaqsFromPortableText(
251265
};
252266

253267
for (const block of blocks) {
254-
if (!block || typeof block !== "object") continue;
268+
if (!block || typeof block !== "object") {
269+
continue;
270+
}
255271
const b = block as {
256272
_type?: string;
257273
style?: string;
258274
children?: Array<{ text?: string }>;
259275
};
260-
if (b._type !== "block" || !Array.isArray(b.children)) continue;
276+
if (b._type !== "block" || !Array.isArray(b.children)) {
277+
continue;
278+
}
261279

262280
const text = b.children
263281
.map((c) => (typeof c?.text === "string" ? c.text : ""))
264282
.join("")
265283
.trim();
266-
if (!text) continue;
284+
if (!text) {
285+
continue;
286+
}
267287

268288
if (b.style === "h2" || b.style === "h3" || b.style === "h4") {
269289
flush();

‎apps/site/src/pages/api/event.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { APIRoute } from "astro";
21
import { env } from "cloudflare:workers";
2+
import type { APIRoute } from "astro";
33
import { trackClientBeaconEvent } from "@/lib/analytics";
44

55
/**

‎apps/site/src/pages/api/mcp.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ async function handleInitialContext(
859859
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
860860
isError: false,
861861
};
862-
} catch (err) {
862+
} catch (_err) {
863863
return {
864864
content: [
865865
{

‎apps/site/src/pages/api/sponsorship.ts‎

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { env } from "cloudflare:workers";
12
import type { APIRoute } from "astro";
23

34
interface SponsorshipPayload {
@@ -11,6 +12,28 @@ interface SponsorshipPayload {
1112
message?: string;
1213
}
1314

15+
function getTierBudgetCents(tier: string | string[] | undefined): number {
16+
const raw = (
17+
Array.isArray(tier) ? tier.join(" ") : String(tier ?? "")
18+
).toLowerCase();
19+
if (raw.includes("dedicated")) {
20+
return 400000;
21+
}
22+
if (raw.includes("mid-roll") || raw.includes("integrated")) {
23+
return 180000;
24+
}
25+
if (raw.includes("shout-out")) {
26+
return 90000;
27+
}
28+
if (raw.includes("newsletter") || raw.includes("blog")) {
29+
return 50000;
30+
}
31+
if (raw.includes("series") || raw.includes("custom")) {
32+
return 500000;
33+
}
34+
return 150000;
35+
}
36+
1437
export const POST: APIRoute = async ({ request }) => {
1538
let data: SponsorshipPayload = {};
1639
const contentType = request.headers.get("content-type") ?? "";
@@ -69,7 +92,9 @@ export const POST: APIRoute = async ({ request }) => {
6992

7093
const name = (data.name ?? data.fullName)?.trim();
7194
const email = data.email?.trim();
95+
const company = (data.company ?? data.companyName)?.trim();
7296
const tier = data.tier ?? data.sponsorshipTier;
97+
const message = data.message?.trim();
7398

7499
if (!name) {
75100
return new Response(
@@ -111,8 +136,67 @@ export const POST: APIRoute = async ({ request }) => {
111136
);
112137
}
113138

139+
const targetPlacements = Array.isArray(tier) ? tier : [String(tier)];
140+
const proposedBudget = getTierBudgetCents(tier);
141+
142+
const flipcoreUrl =
143+
(env as unknown as Record<string, string | undefined>)?.FLIPCORE_API_URL ??
144+
"https://api.flipfeeds.com";
145+
const flipcoreWebUrl =
146+
(env as unknown as Record<string, string | undefined>)
147+
?.PUBLIC_FLIPCORE_WEB_URL ?? "https://flipfeeds.com";
148+
149+
const fallbackToken = `spt_${crypto.randomUUID().replace(/-/g, "").slice(0, 16)}`;
150+
let portalToken = fallbackToken;
151+
let portalUrl = `${flipcoreWebUrl}/codingcatdev/portal/${fallbackToken}`;
152+
let dealId = `deal_${crypto.randomUUID().replace(/-/g, "").slice(0, 16)}`;
153+
let inquiryId = `inq_${crypto.randomUUID().replace(/-/g, "").slice(0, 16)}`;
154+
155+
try {
156+
const flipcoreRes = await fetch(`${flipcoreUrl}/v1/deals/inquiries`, {
157+
method: "POST",
158+
headers: { "Content-Type": "application/json" },
159+
body: JSON.stringify({
160+
sponsorName: name,
161+
sponsorEmail: email,
162+
companyName: company ?? undefined,
163+
targetPlacements,
164+
proposedBudget,
165+
currency: "USD",
166+
message: message ?? undefined,
167+
slug: "codingcatdev",
168+
}),
169+
});
170+
171+
if (flipcoreRes.ok) {
172+
const resData = (await flipcoreRes.json()) as any;
173+
if (resData.portalUrl) {
174+
portalUrl = resData.portalUrl;
175+
}
176+
if (resData.portalToken) {
177+
portalToken = resData.portalToken;
178+
}
179+
if (resData.dealId) {
180+
dealId = resData.dealId;
181+
}
182+
if (resData.inquiryId) {
183+
inquiryId = resData.inquiryId;
184+
}
185+
}
186+
} catch {
187+
// Resilient fallback: uses pre-generated live Deal Room URL
188+
}
189+
114190
return new Response(
115-
JSON.stringify({ success: true, message: "Inquiry received" }),
191+
JSON.stringify({
192+
success: true,
193+
message: "Inquiry received",
194+
inquiryId,
195+
dealId,
196+
portalToken,
197+
portalUrl,
198+
dealRoomUrl: portalUrl,
199+
}),
116200
{
117201
status: 200,
118202
headers: { "Content-Type": "application/json" },

0 commit comments

Comments
 (0)