11import { 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
66interface SeededGmailDraft {
77 companyName : string ;
@@ -98,18 +98,51 @@ const ACTIVE_GMAIL_DEALS: SeededGmailDraft[] = [
9898] ;
9999
100100export 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 & Booking OS</ h1 >
161+ < h1 style = { { margin : 0 , fontSize : 28 } } >
162+ CodingCat.dev Revenue & Booking OS ({ envLabel } )
163+ </ h1 >
129164 < p style = { { margin : "4px 0 0" , color : "#666" , fontSize : 14 } } >
130- Unified Sanity App SDK Workspace · Project < code > hfh83o0w</ code > (Production)
165+ Unified Sanity App SDK Workspace · Project < code > { projectId } </ code > · 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 & 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 & 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 ↗
336+ Open Podcast Recording Booking ↗
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 ↗
344+ Open Sponsor Discovery & Deal Room ↗
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 & 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 ↗
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 ↗
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 ↗
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 ↗
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 ↗
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 ↗
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 ↗
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 ↗
473+ </ a >
474+ ) }
475+ { item . reddit && (
476+ < a href = { item . reddit } target = "_blank" rel = "noreferrer" style = { { color : "#ea580c" } } >
477+ Reddit ↗
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