Skip to content

Commit d00c3f1

Browse files
committed
Merge remote-tracking branch 'origin/dev' into integrate_clairic_extraction_model
2 parents 1062f0e + db019cc commit d00c3f1

68 files changed

Lines changed: 9651 additions & 605 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/deploy-workers.yml‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,33 @@ jobs:
6060
if [ -n "$OPENROUTER_API_KEY" ]; then
6161
echo "$OPENROUTER_API_KEY" | pnpm --filter @codingcatdev/site exec wrangler secret put OPENROUTER_API_KEY --env=""
6262
fi
63+
if [ -n "$SANITY_API_WRITE_TOKEN" ]; then
64+
echo "$SANITY_API_WRITE_TOKEN" | pnpm --filter @codingcatdev/site exec wrangler secret put SANITY_API_WRITE_TOKEN --env=""
65+
fi
66+
if [ -n "$GUEST_PORTAL_SECRET" ]; then
67+
echo "$GUEST_PORTAL_SECRET" | pnpm --filter @codingcatdev/site exec wrangler secret put GUEST_PORTAL_SECRET --env=""
68+
fi
69+
if [ -n "$GOOGLE_CLIENT_ID" ]; then
70+
echo "$GOOGLE_CLIENT_ID" | pnpm --filter @codingcatdev/site exec wrangler secret put GOOGLE_CLIENT_ID --env=""
71+
fi
72+
if [ -n "$GOOGLE_CLIENT_SECRET" ]; then
73+
echo "$GOOGLE_CLIENT_SECRET" | pnpm --filter @codingcatdev/site exec wrangler secret put GOOGLE_CLIENT_SECRET --env=""
74+
fi
75+
if [ -n "$GOOGLE_REFRESH_TOKEN" ]; then
76+
echo "$GOOGLE_REFRESH_TOKEN" | pnpm --filter @codingcatdev/site exec wrangler secret put GOOGLE_REFRESH_TOKEN --env=""
77+
fi
6378
env:
6479
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
6580
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
6681
SANITY_API_READ_TOKEN: ${{ secrets.SANITY_API_READ_TOKEN }}
82+
SANITY_API_WRITE_TOKEN: ${{ secrets.SANITY_API_WRITE_TOKEN }}
6783
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
6884
SANITY_CONTEXT_TOKEN: ${{ secrets.SANITY_CONTEXT_TOKEN }}
6985
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
86+
GUEST_PORTAL_SECRET: ${{ secrets.GUEST_PORTAL_SECRET }}
87+
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
88+
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
89+
GOOGLE_REFRESH_TOKEN: ${{ secrets.GOOGLE_REFRESH_TOKEN }}
7090

7191
deploy-production:
7292
if: github.ref == 'refs/heads/main'
@@ -114,10 +134,30 @@ jobs:
114134
if [ -n "$OPENROUTER_API_KEY" ]; then
115135
echo "$OPENROUTER_API_KEY" | pnpm --filter @codingcatdev/site exec wrangler secret put OPENROUTER_API_KEY --env production
116136
fi
137+
if [ -n "$SANITY_API_WRITE_TOKEN" ]; then
138+
echo "$SANITY_API_WRITE_TOKEN" | pnpm --filter @codingcatdev/site exec wrangler secret put SANITY_API_WRITE_TOKEN --env production
139+
fi
140+
if [ -n "$GUEST_PORTAL_SECRET" ]; then
141+
echo "$GUEST_PORTAL_SECRET" | pnpm --filter @codingcatdev/site exec wrangler secret put GUEST_PORTAL_SECRET --env production
142+
fi
143+
if [ -n "$GOOGLE_CLIENT_ID" ]; then
144+
echo "$GOOGLE_CLIENT_ID" | pnpm --filter @codingcatdev/site exec wrangler secret put GOOGLE_CLIENT_ID --env production
145+
fi
146+
if [ -n "$GOOGLE_CLIENT_SECRET" ]; then
147+
echo "$GOOGLE_CLIENT_SECRET" | pnpm --filter @codingcatdev/site exec wrangler secret put GOOGLE_CLIENT_SECRET --env production
148+
fi
149+
if [ -n "$GOOGLE_REFRESH_TOKEN" ]; then
150+
echo "$GOOGLE_REFRESH_TOKEN" | pnpm --filter @codingcatdev/site exec wrangler secret put GOOGLE_REFRESH_TOKEN --env production
151+
fi
117152
env:
118153
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
119154
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
120155
SANITY_API_READ_TOKEN: ${{ secrets.SANITY_API_READ_TOKEN }}
156+
SANITY_API_WRITE_TOKEN: ${{ secrets.SANITY_API_WRITE_TOKEN }}
121157
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
122158
SANITY_CONTEXT_TOKEN: ${{ secrets.SANITY_CONTEXT_TOKEN }}
123159
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
160+
GUEST_PORTAL_SECRET: ${{ secrets.GUEST_PORTAL_SECRET }}
161+
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
162+
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
163+
GOOGLE_REFRESH_TOKEN: ${{ secrets.GOOGLE_REFRESH_TOKEN }}

‎.github/workflows/deploy.yml‎

Lines changed: 246 additions & 8 deletions
Large diffs are not rendered by default.

‎.github/workflows/syndicate-post.yml‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ jobs:
3737

3838
- name: Syndicate Post
3939
run: |
40-
node scripts/run-syndicate-post.mjs
40+
if [ "$TARGET_DOCUMENT_ID" = "backfill-dailydev" ]; then
41+
node scripts/backfill-dailydev-and-links.mjs
42+
else
43+
node scripts/run-syndicate-post.mjs
44+
fi
4145
env:
4246
TARGET_DOCUMENT_ID: ${{ inputs.document_id }}
4347
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
@@ -48,3 +52,9 @@ jobs:
4852
PRIVATE_HASHNODE: ${{ secrets.PRIVATE_HASHNODE }}
4953
DEVTO_API_KEY: ${{ secrets.PRIVATE_DEVTO }}
5054
HASHNODE_ACCESS_TOKEN: ${{ secrets.PRIVATE_HASHNODE }}
55+
AWS_BUILDER_SESSION_TOKEN: ${{ secrets.AWS_BUILDER_SESSION_TOKEN }}
56+
DAILY_DEV_TOKEN: ${{ secrets.DAILY_DEV_TOKEN }}
57+
DAILY_DEV_SESSION_COOKIE: ${{ secrets.DAILY_DEV_SESSION_COOKIE }}
58+
GOOGLE_WORKSPACE_REFRESH_TOKEN: ${{ secrets.GOOGLE_WORKSPACE_REFRESH_TOKEN }}
59+
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
60+
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}

‎apps/ops/sanity.cli.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
11
import { defineCliConfig } from "sanity/cli";
22

3+
const dataset =
4+
process.env.SANITY_APP_DATASET ||
5+
process.env.SANITY_STUDIO_DATASET ||
6+
"production";
7+
const isDev = dataset === "dev";
8+
39
export default defineCliConfig({
410
app: {
511
organizationId: process.env.SANITY_ORGANIZATION_ID || "ovF2qiKSO",
612
entry: "./src/index.tsx",
13+
title: isDev
14+
? "CodingCat Ops & Revenue OS (Dev)"
15+
: "CodingCat Ops & Revenue OS (Production)",
16+
},
17+
deployment: {
18+
appId:
19+
process.env.SANITY_OPS_APP_ID ||
20+
(isDev ? "yiugiba2sjnhpehqmky6nulc" : "bv37w3mpgxoi5p12dg9kmp9r"),
721
},
822
});
923

24+
25+
26+

‎apps/ops/src/App.tsx‎

Lines changed: 187 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useDocuments } from "@sanity/sdk-react";
2-
import { useState } from "react";
2+
import { useEffect, useState } from "react";
33

4-
type WorkspaceTab = "crm" | "bookings" | "billing";
4+
type WorkspaceTab = "crm" | "bookings" | "billing" | "syndication";
55

66
interface SeededGmailDraft {
77
companyName: string;
@@ -98,18 +98,51 @@ const ACTIVE_GMAIL_DEALS: SeededGmailDraft[] = [
9898
];
9999

100100
export function App() {
101+
const projectId = process.env.SANITY_APP_PROJECT_ID || "hfh83o0w";
102+
const dataset = process.env.SANITY_APP_DATASET || "production";
103+
const isDev = dataset === "dev";
104+
const envLabel = isDev ? "Dev" : "Production";
105+
const siteOrigin = isDev ? "https://dev.codingcat.dev" : "https://codingcat.dev";
106+
101107
const [activeTab, setActiveTab] = useState<WorkspaceTab>("crm");
102108
const [discovering, setDiscovering] = useState(false);
103109
const [discoveredProspects, setDiscoveredProspects] = useState<Array<any>>([]);
104110

105111
const sponsorLeads = useDocuments({ documentType: "sponsorLead" });
106112
const sponsorPool = useDocuments({ documentType: "sponsorPool" });
107113
const bookings = useDocuments({ documentType: "booking" });
114+
const syndications = useDocuments({ documentType: "syndication" });
115+
const [syndicatedContent, setSyndicatedContent] = useState<Array<any>>([]);
116+
117+
useEffect(() => {
118+
const query = encodeURIComponent(
119+
`*[_type in ["post", "podcast"] && !(_id in path("drafts.**"))] | order(coalesce(date, _createdAt) desc)[0...40] {
120+
_id,
121+
_type,
122+
title,
123+
"slug": slug.current,
124+
"date": coalesce(date, _createdAt),
125+
youtube,
126+
dailydev,
127+
devto,
128+
hashnode,
129+
awsbuilder,
130+
medium,
131+
reddit
132+
}`,
133+
);
134+
fetch(`https://${projectId}.apicdn.sanity.io/v2025-09-30/data/query/${dataset}?query=${query}`)
135+
.then((r) => r.json())
136+
.then((d) => {
137+
if (Array.isArray(d?.result)) setSyndicatedContent(d.result);
138+
})
139+
.catch(() => {});
140+
}, [projectId, dataset]);
108141

109142
const handleDiscoverOutbound = async () => {
110143
setDiscovering(true);
111144
try {
112-
const res = await fetch("https://codingcat.dev/api/sponsors/outbound");
145+
const res = await fetch(`${siteOrigin}/api/sponsors/outbound`);
113146
if (res.ok) {
114147
const data = await res.json();
115148
setDiscoveredProspects(data.prospects || []);
@@ -125,9 +158,12 @@ export function App() {
125158
<div style={{ padding: 24, fontFamily: "system-ui, sans-serif", maxWidth: 1200, margin: "0 auto" }}>
126159
<header style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }}>
127160
<div>
128-
<h1 style={{ margin: 0, fontSize: 28 }}>CodingCat.dev Revenue &amp; Booking OS</h1>
161+
<h1 style={{ margin: 0, fontSize: 28 }}>
162+
CodingCat.dev Revenue &amp; Booking OS ({envLabel})
163+
</h1>
129164
<p style={{ margin: "4px 0 0", color: "#666", fontSize: 14 }}>
130-
Unified Sanity App SDK Workspace &middot; Project <code>hfh83o0w</code> (Production)
165+
Unified Sanity App SDK Workspace &middot; Project <code>{projectId}</code> &middot; Dataset{" "}
166+
<code>{dataset}</code> ({envLabel})
131167
</p>
132168
</div>
133169
<div style={{ display: "flex", gap: 8 }}>
@@ -176,6 +212,21 @@ export function App() {
176212
>
177213
3. Stripe 50/50 Billing &amp; Deal Rooms
178214
</button>
215+
<button
216+
type="button"
217+
onClick={() => setActiveTab("syndication")}
218+
style={{
219+
padding: "8px 16px",
220+
borderRadius: 8,
221+
border: "1px solid #ccc",
222+
background: activeTab === "syndication" ? "#111" : "#fff",
223+
color: activeTab === "syndication" ? "#fff" : "#111",
224+
fontWeight: 600,
225+
cursor: "pointer",
226+
}}
227+
>
228+
4. Syndication &amp; Platform Links ({syndications.data?.length || syndicatedContent.length})
229+
</button>
179230
</div>
180231
</header>
181232

@@ -277,20 +328,20 @@ export function App() {
277328
</p>
278329
<div style={{ display: "flex", gap: 12, marginTop: 16 }}>
279330
<a
280-
href="https://calendar.app.google/s3B6PEqbVsGcY81MA"
331+
href="https://codingcat.dev/book"
281332
target="_blank"
282333
rel="noreferrer"
283334
style={{ padding: "10px 16px", background: "#111", color: "#fff", borderRadius: 8, textDecoration: "none", fontSize: 13, fontWeight: 600 }}
284335
>
285-
Open Podcast Recording Schedule &nearr;
336+
Open Podcast Recording Booking &nearr;
286337
</a>
287338
<a
288-
href="https://calendar.app.google/tyGiF6dGFQeaVrN66"
339+
href="https://codingcat.dev/sponsorships#sponsorship-form"
289340
target="_blank"
290341
rel="noreferrer"
291342
style={{ padding: "10px 16px", border: "1px solid #ccc", color: "#111", borderRadius: 8, textDecoration: "none", fontSize: 13, fontWeight: 600 }}
292343
>
293-
Open Sponsor Discovery Schedule &nearr;
344+
Open Sponsor Discovery &amp; Deal Room &nearr;
294345
</a>
295346
</div>
296347
</section>
@@ -309,6 +360,133 @@ export function App() {
309360
</ul>
310361
</section>
311362
)}
363+
364+
{activeTab === "syndication" && (
365+
<section style={{ padding: 20, border: "1px solid #ddd", borderRadius: 12 }}>
366+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
367+
<div>
368+
<h2 style={{ margin: 0, fontSize: 18 }}>Cross-Platform Syndication &amp; YouTube Funnel Tracker</h2>
369+
<p style={{ margin: "4px 0 0", color: "#666", fontSize: 13 }}>
370+
Tracks canonical <code>codingcat.dev</code> URLs and outbound links across YouTube, daily.dev Squad, Dev.to, Hashnode, AWS Builder Center, Medium, and Reddit.
371+
</p>
372+
</div>
373+
<div style={{ display: "flex", gap: 8 }}>
374+
<a
375+
href="https://daily.dev/squads/codingcatdev"
376+
target="_blank"
377+
rel="noreferrer"
378+
style={{ padding: "8px 12px", border: "1px solid #ccc", borderRadius: 6, textDecoration: "none", color: "#111", fontSize: 12, fontWeight: 600 }}
379+
>
380+
daily.dev Squad &nearr;
381+
</a>
382+
<a
383+
href="https://dev.to/codingcatdev"
384+
target="_blank"
385+
rel="noreferrer"
386+
style={{ padding: "8px 12px", border: "1px solid #ccc", borderRadius: 6, textDecoration: "none", color: "#111", fontSize: 12, fontWeight: 600 }}
387+
>
388+
Dev.to Org &nearr;
389+
</a>
390+
</div>
391+
</div>
392+
<div style={{ overflowX: "auto" }}>
393+
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
394+
<thead>
395+
<tr style={{ textAlign: "left", borderBottom: "2px solid #eee" }}>
396+
<th style={{ padding: "8px 6px" }}>Date</th>
397+
<th style={{ padding: "8px 6px" }}>Title (Canonical)</th>
398+
<th style={{ padding: "8px 6px" }}>YouTube</th>
399+
<th style={{ padding: "8px 6px" }}>daily.dev Squad</th>
400+
<th style={{ padding: "8px 6px" }}>Dev.to</th>
401+
<th style={{ padding: "8px 6px" }}>Hashnode</th>
402+
<th style={{ padding: "8px 6px" }}>AWS Builder</th>
403+
<th style={{ padding: "8px 6px" }}>Medium / Reddit</th>
404+
</tr>
405+
</thead>
406+
<tbody>
407+
{syndicatedContent.map((item) => {
408+
const canonical = `${siteOrigin}/${item._type}/${item.slug}`;
409+
const hnUrl = item.hashnode
410+
? item.hashnode.startsWith("http")
411+
? item.hashnode
412+
: `https://hashnode.codingcat.dev/${item.hashnode}`
413+
: null;
414+
return (
415+
<tr key={item._id} style={{ borderBottom: "1px solid #f0f0f0" }}>
416+
<td style={{ padding: "8px 6px", whiteSpace: "nowrap", color: "#666" }}>
417+
{(item.date || "").slice(0, 10)}
418+
</td>
419+
<td style={{ padding: "8px 6px", fontWeight: 600 }}>
420+
<a href={canonical} target="_blank" rel="noreferrer" style={{ color: "#111" }}>
421+
{item.title}
422+
</a>
423+
</td>
424+
<td style={{ padding: "8px 6px" }}>
425+
{item.youtube ? (
426+
<a href={item.youtube} target="_blank" rel="noreferrer" style={{ color: "#c00", fontWeight: 600 }}>
427+
Watch &nearr;
428+
</a>
429+
) : (
430+
<span style={{ color: "#aaa" }}>-</span>
431+
)}
432+
</td>
433+
<td style={{ padding: "8px 6px" }}>
434+
{item.dailydev ? (
435+
<a href={item.dailydev} target="_blank" rel="noreferrer" style={{ color: "#7c3aed", fontWeight: 600 }}>
436+
Squad &nearr;
437+
</a>
438+
) : (
439+
<span style={{ color: "#aaa" }}>-</span>
440+
)}
441+
</td>
442+
<td style={{ padding: "8px 6px" }}>
443+
{item.devto ? (
444+
<a href={item.devto} target="_blank" rel="noreferrer" style={{ color: "#2563eb", fontWeight: 600 }}>
445+
Dev.to &nearr;
446+
</a>
447+
) : (
448+
<span style={{ color: "#aaa" }}>-</span>
449+
)}
450+
</td>
451+
<td style={{ padding: "8px 6px" }}>
452+
{hnUrl ? (
453+
<a href={hnUrl} target="_blank" rel="noreferrer" style={{ color: "#2563eb", fontWeight: 600 }}>
454+
Hashnode &nearr;
455+
</a>
456+
) : (
457+
<span style={{ color: "#aaa" }}>-</span>
458+
)}
459+
</td>
460+
<td style={{ padding: "8px 6px" }}>
461+
{item.awsbuilder ? (
462+
<a href={item.awsbuilder} target="_blank" rel="noreferrer" style={{ color: "#d97706", fontWeight: 600 }}>
463+
AWS &nearr;
464+
</a>
465+
) : (
466+
<span style={{ color: "#aaa" }}>-</span>
467+
)}
468+
</td>
469+
<td style={{ padding: "8px 6px" }}>
470+
{item.medium && (
471+
<a href={item.medium} target="_blank" rel="noreferrer" style={{ marginRight: 8 }}>
472+
Medium &nearr;
473+
</a>
474+
)}
475+
{item.reddit && (
476+
<a href={item.reddit} target="_blank" rel="noreferrer" style={{ color: "#ea580c" }}>
477+
Reddit &nearr;
478+
</a>
479+
)}
480+
{!item.medium && !item.reddit && <span style={{ color: "#aaa" }}>-</span>}
481+
</td>
482+
</tr>
483+
);
484+
})}
485+
</tbody>
486+
</table>
487+
</div>
488+
</section>
489+
)}
312490
</div>
313491
);
314492
}

0 commit comments

Comments
 (0)