// apps/admin/src/app/(admin)/ads/page.tsx // D3RO Voice Admin CRM — Ad Mediation & Monetization Console import { Box, Typography } from '@mui/material' import { DoubleBezelCard, TactileBadge } from '@d3ro/ui/components/ds' import { C, FONT_SANS, FONT_MONO, panelSx, tableSx, statusBadgeSx } from '@/lib/console-theme' import { requireManager } from '@/lib/admin-guard' import { MEDIATION_ROSTER, fetchAdRewardStats, type AdRewardStats } from '@/lib/ad-monetization' import { isSupabaseAdminConfigured } from '@/lib/supabase-admin' export const dynamic = 'force-dynamic' const FORMAT_LABEL: Record = { banner_dock: 'Bottom Dock', rewarded_video: 'Rewarded Video', export_sponsor: 'Export Sponsor', audio_chime: 'Audio Chime' } function formatDateTime(value: string): string { const parsed = new Date(value) return Number.isNaN(parsed.getTime()) ? '—' : parsed.toISOString().replace('T', ' ').slice(0, 16) } function HeaderBar(): React.ReactElement { return ( Ad Mediation & Monetization FAIL-CLOSED SANDBOX Mediation waterfall roster, rewarded token grants, network integration state ) } function RewardKpis({ stats }: { stats: AdRewardStats }): React.ReactElement { const cards = [ { title: `Rewarded Claims (${stats.windowDays}d)`, value: stats.totalClaims.toLocaleString(), subtext: 'Verified rewarded-video completions' }, { title: `Tokens Granted (${stats.windowDays}d)`, value: stats.totalRewardTokens.toLocaleString(), subtext: 'Cloud AI tokens issued via ad rewards' }, { title: `Unique Claimants (${stats.windowDays}d)`, value: stats.uniqueClaimants.toLocaleString(), subtext: 'Distinct accounts that redeemed rewards' }, { title: 'Live Ad Networks', value: `0 / ${MEDIATION_ROSTER.length}`, subtext: 'All adapters fail-closed until SDK contracts land' } ] return ( {cards.map((card) => ( {card.title} {card.value} {card.subtext} ))} ) } function MediationRosterPanel(): React.ReactElement { return ( Mediation Waterfall Roster NO LIVE BIDS 데스크톱 미디에이션 엔진에 등록된 어댑터 구성입니다. 공식 SDK/인증 계약이 연결되기 전까지 모든 네트워크는 입찰 없이 fail-closed로 동작하며, 데모 크리에이티브와 가짜 수익 수치는 표시하지 않습니다. Network Adapter ID Formats Floor eCPM Integration {MEDIATION_ROSTER.map((network) => ( {network.name} {network.networkId} {network.formats.map((format) => FORMAT_LABEL[format] ?? format).join(' · ')} ${network.floorEcpm.toFixed(2)} NOT INTEGRATED ))} ) } function RewardBreakdownPanel({ stats }: { stats: AdRewardStats }): React.ReactElement { return ( Rewarded Grants by Network {stats.byNetwork.length === 0 ? ( 최근 {stats.windowDays}일간 검증된 리워드 클레임이 없습니다. ) : ( Network Claims Tokens {stats.byNetwork.map((summary) => ( {summary.network} {summary.claims.toLocaleString()} +{summary.rewardTokens.toLocaleString()} ))} )} Recent Verified Claims {stats.recentClaims.length === 0 ? ( 표시할 클레임이 없습니다. ) : ( Verified At (UTC) Network Placement Tokens {stats.recentClaims.map((claim) => ( {formatDateTime(claim.verifiedAt)} {claim.network} {claim.placement} +{claim.rewardTokens.toLocaleString()} ))} )} ) } function RewardsUnavailablePanel({ reason }: { reason: string }): React.ReactElement { return ( Rewarded 토큰 지급 통계를 불러올 수 없습니다. {reason} ) } export default async function AdminAdsPage(): Promise { await requireManager() let stats: AdRewardStats | null = null let statsError: string | null = null if (!isSupabaseAdminConfigured()) { statsError = 'SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY 환경변수가 설정되지 않아 ad_reward_claims 데이터에 연결할 수 없습니다. 환경변수를 설정하면 실데이터가 표시됩니다.' } else { try { stats = await fetchAdRewardStats() } catch (error) { statsError = error instanceof Error ? error.message : 'Unknown ad reward stats error' } } return ( {stats && } {stats ? : } ) }