feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped

This commit is contained in:
Yun Chan 2026-08-20 11:12:05 +09:00
parent 5cd1de6859
commit 708e20f747
406 changed files with 42464 additions and 6199 deletions

View file

@ -0,0 +1,316 @@
// apps/desktop/src/renderer/components/ads/RewardedQuotaModal.tsx
// Dynamic Rewarded Video Modal with Multi-Network Video Auction (Unity/AppLovin/Playwire/ElevenLabs)
import React, { useState, useEffect, useCallback } from 'react'
import { Modal, Box, Typography, Button, LinearProgress } from '@mui/material'
import { PlayCircle, Sparkles, Clock, X, CheckCircle2 } from 'lucide-react'
import { d3roPalette, d3roFontSans, d3roFontMono, d3roRadius, d3roShadow } from '@d3ro/ui/theme'
import type { AdCreativePayload } from '@d3ro/core/types'
interface RewardedQuotaModalProps {
open: boolean
onClose: () => void
onRewardClaimed: (addedTokens: number) => void
}
const DEFAULT_VIDEO_CREATIVE: AdCreativePayload = {
id: 'elevenlabs_video_default',
networkId: 'direct_sponsor',
networkName: 'ElevenLabs Direct Partner',
title: 'ElevenLabs Voice Synthesizer',
description: 'Human-like AI audio generation & voice cloning for developers and creators.',
ctaText: 'Explore Voice AI',
clickUrl: 'https://elevenlabs.io',
sponsorTag: 'Rewarded Sponsor',
advertiserName: 'ElevenLabs',
bidEcpm: 18.0,
format: 'rewarded_video',
rewardTokens: 50,
durationSeconds: 15,
}
export function RewardedQuotaModal({
open,
onClose,
onRewardClaimed,
}: RewardedQuotaModalProps): React.ReactElement {
const [creative, setCreative] = useState<AdCreativePayload>(DEFAULT_VIDEO_CREATIVE)
const [timeLeft, setTimeLeft] = useState(15)
const [completed, setCompleted] = useState(false)
const fetchRewardedAuction = useCallback(async () => {
try {
if (window.electronAPI?.ads?.requestAuction) {
const res = await window.electronAPI.ads.requestAuction({
placement: 'rewarded_video_quota',
format: 'rewarded_video',
floorEcpm: 4.0,
})
if (res.success && res.data?.winner) {
setCreative(res.data.winner)
setTimeLeft(res.data.winner.durationSeconds || 15)
// Record video impression
window.electronAPI.ads.recordImpression({
adId: res.data.winner.id,
format: 'rewarded_video',
network: res.data.winner.networkId,
networkName: res.data.winner.networkName,
earnedEcpm: res.data.winningBidEcpm,
})
}
}
} catch {
// fallback
}
}, [])
useEffect(() => {
if (!open) {
setTimeLeft(15)
setCompleted(false)
return
}
fetchRewardedAuction()
const interval = setInterval(() => {
setTimeLeft((prev) => {
if (prev <= 1) {
clearInterval(interval)
setCompleted(true)
return 0
}
return prev - 1
})
}, 1000)
return () => clearInterval(interval)
}, [open, fetchRewardedAuction])
const handleClaim = async () => {
let tokens = 50
if (window.electronAPI?.ads?.claimReward) {
const res = await window.electronAPI.ads.claimReward({
adId: creative.id,
networkId: creative.networkId as string,
})
if (res.success && res.data?.tokensAdded) {
tokens = res.data.tokensAdded
}
}
onRewardClaimed(tokens)
onClose()
}
const duration = creative.durationSeconds || 15
const progressPercent = ((duration - timeLeft) / duration) * 100
return (
<Modal open={open} onClose={onClose}>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: { xs: '90%', sm: 480 },
bgcolor: d3roPalette.bg.modal,
borderRadius: d3roRadius.modal,
border: `1px solid ${d3roPalette.glass.hairline}`,
boxShadow: d3roShadow.modal,
p: 3,
outline: 'none',
}}
>
{/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2.5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Box
sx={{
width: 32,
height: 32,
borderRadius: '8px',
bgcolor: 'rgba(56, 189, 248, 0.15)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<PlayCircle size={18} color={d3roPalette.accent.light} />
</Box>
<Typography sx={{ fontFamily: d3roFontSans, fontWeight: 700, fontSize: '15px', color: d3roPalette.text.primary }}>
Sponsor Video Refill +50 Free AI Tokens
</Typography>
</Box>
<Box onClick={onClose} sx={{ cursor: 'pointer', color: d3roPalette.text.dimLabel, '&:hover': { color: d3roPalette.text.primary } }}>
<X size={18} />
</Box>
</Box>
{/* Video Ad Screen with Authentic Unity / AppLovin Test Mode UI */}
<Box
sx={{
height: 220,
borderRadius: d3roRadius.inner,
bgcolor: '#000000',
border: `1px solid ${d3roPalette.glass.hairline}`,
position: 'relative',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
p: 3,
textAlign: 'center',
overflow: 'hidden',
}}
>
{/* Unity / AppLovin Official Test Watermark Ribbon */}
<Box
sx={{
position: 'absolute',
top: 10,
left: 10,
display: 'flex',
alignItems: 'center',
gap: 1,
zIndex: 2,
}}
>
<Typography
sx={{
fontFamily: d3roFontMono,
fontSize: '9px',
fontWeight: 900,
color: '#fff',
bgcolor: '#2563eb',
px: 1,
py: 0.3,
borderRadius: '4px',
letterSpacing: '0.5px',
}}
>
UNITY ADS TEST MODE
</Typography>
<Typography
sx={{
fontFamily: d3roFontMono,
fontSize: '9px',
color: 'rgba(255, 255, 255, 0.6)',
}}
>
Placement: Rewarded_Tokens_Refill
</Typography>
</Box>
<Box
sx={{
position: 'absolute',
top: 10,
right: 10,
display: 'flex',
alignItems: 'center',
gap: 0.5,
color: '#fff',
bgcolor: 'rgba(0, 0, 0, 0.6)',
px: 1,
py: 0.3,
borderRadius: '4px',
fontFamily: d3roFontMono,
fontSize: '11px',
fontWeight: 700,
zIndex: 2,
}}
>
<Clock size={12} />
{completed ? 'Reward Ready' : `00:${timeLeft.toString().padStart(2, '0')}`}
</Box>
{/* Test Commercial Creative Content */}
<Box
sx={{
width: 56,
height: 56,
borderRadius: '14px',
bgcolor: 'rgba(37, 99, 235, 0.25)',
border: `1px solid rgba(59, 130, 246, 0.5)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mb: 1.5,
boxShadow: '0 0 20px rgba(37, 99, 235, 0.4)',
}}
>
<Sparkles size={28} color="#60a5fa" />
</Box>
<Typography sx={{ fontFamily: d3roFontSans, fontWeight: 800, fontSize: '15px', color: '#ffffff' }}>
{creative.title}
</Typography>
<Typography sx={{ fontFamily: d3roFontSans, fontSize: '11px', color: 'rgba(255, 255, 255, 0.75)', mt: 0.5, maxWidth: 360 }}>
{creative.description}
</Typography>
{/* Video Progress Bar at bottom */}
<Box sx={{ position: 'absolute', bottom: 0, left: 0, right: 0 }}>
<LinearProgress
variant="determinate"
value={progressPercent}
sx={{
height: 5,
bgcolor: 'rgba(255, 255, 255, 0.1)',
'& .MuiLinearProgress-bar': {
bgcolor: completed ? '#22c55e' : '#3b82f6',
},
}}
/>
</Box>
</Box>
{/* Footer Actions */}
<Box sx={{ mt: 3, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography sx={{ fontFamily: d3roFontMono, fontSize: '11px', color: d3roPalette.text.dimLabel }}>
Reward: <span style={{ color: d3roPalette.tag.greenText, fontWeight: 700 }}>+50 Cloud AI Tokens</span>
</Typography>
{completed ? (
<Button
variant="contained"
onClick={handleClaim}
startIcon={<CheckCircle2 size={16} />}
sx={{
fontFamily: d3roFontSans,
fontWeight: 700,
fontSize: '12px',
textTransform: 'none',
bgcolor: d3roPalette.tag.greenText,
color: '#000',
px: 2.5,
py: 0.8,
borderRadius: '8px',
'&:hover': { bgcolor: '#4ade80' },
}}
>
Claim +50 Free Tokens
</Button>
) : (
<Button
disabled
sx={{
fontFamily: d3roFontSans,
fontSize: '12px',
textTransform: 'none',
color: 'rgba(255, 255, 255, 0.3)',
bgcolor: 'rgba(255, 255, 255, 0.05)',
px: 2,
py: 0.8,
borderRadius: '8px',
}}
>
Watch to Complete ({timeLeft}s)
</Button>
)}
</Box>
</Box>
</Modal>
)
}