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
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:
parent
5cd1de6859
commit
708e20f747
406 changed files with 42464 additions and 6199 deletions
276
apps/desktop/src/renderer/components/ads/AdBanner.tsx
Normal file
276
apps/desktop/src/renderer/components/ads/AdBanner.tsx
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
// apps/desktop/src/renderer/components/ads/AdBanner.tsx
|
||||
// Dynamic Multi-Network Dock Sponsor Banner connected to 10+ Ad Mediation Engine
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import { Box, Typography, Button } from '@mui/material'
|
||||
import { Sparkles, ExternalLink } from 'lucide-react'
|
||||
import { d3roPalette, d3roFontSans, d3roFontMono, d3roRadius, d3roShadow } from '@d3ro/ui/theme'
|
||||
import type { LicenseTier, AdCreativePayload } from '@d3ro/core/types'
|
||||
|
||||
interface AdBannerProps {
|
||||
tier: LicenseTier
|
||||
onOpenUpgradeModal: () => void
|
||||
}
|
||||
|
||||
const DEFAULT_CREATIVE: AdCreativePayload = {
|
||||
id: 'cursor_default',
|
||||
networkId: 'direct_sponsor',
|
||||
networkName: 'Direct Partner',
|
||||
title: 'Cursor AI — Next-Gen AI Code Editor',
|
||||
description: 'Build software with intelligent voice agents & lightning-speed code search.',
|
||||
ctaText: 'Learn More',
|
||||
clickUrl: 'https://cursor.com',
|
||||
sponsorTag: 'Sponsor',
|
||||
advertiserName: 'Cursor AI',
|
||||
bidEcpm: 15.5,
|
||||
format: 'banner_dock',
|
||||
}
|
||||
|
||||
export function AdBanner({ tier, onOpenUpgradeModal }: AdBannerProps): React.ReactElement | null {
|
||||
const [creative, setCreative] = useState<AdCreativePayload>(DEFAULT_CREATIVE)
|
||||
|
||||
// Fetch highest bidding ad creative via mediation auction
|
||||
const fetchAdAuction = useCallback(async () => {
|
||||
if (tier !== 'free') return
|
||||
try {
|
||||
if (window.electronAPI?.ads?.requestAuction) {
|
||||
const res = await window.electronAPI.ads.requestAuction({
|
||||
placement: 'bottom_dock_banner',
|
||||
format: 'banner_dock',
|
||||
floorEcpm: 2.0,
|
||||
})
|
||||
if (res.success && res.data?.winner) {
|
||||
setCreative(res.data.winner)
|
||||
// Record impression
|
||||
window.electronAPI.ads.recordImpression({
|
||||
adId: res.data.winner.id,
|
||||
format: 'banner_dock',
|
||||
network: res.data.winner.networkId,
|
||||
networkName: res.data.winner.networkName,
|
||||
earnedEcpm: res.data.winningBidEcpm,
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// fallback to default creative
|
||||
}
|
||||
}, [tier])
|
||||
|
||||
useEffect(() => {
|
||||
fetchAdAuction()
|
||||
// Auto-refresh ad creative every 45s for free tier
|
||||
const interval = setInterval(fetchAdAuction, 45000)
|
||||
return () => clearInterval(interval)
|
||||
}, [fetchAdAuction])
|
||||
|
||||
// Pro, Pro+, Team, Enterprise users are 100% Ad-Free
|
||||
if (tier !== 'free') {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleSponsorClick = () => {
|
||||
if (window.electronAPI?.ads?.recordClick) {
|
||||
window.electronAPI.ads.recordClick({ adId: creative.id, networkId: creative.networkId as string })
|
||||
}
|
||||
window.open(creative.clickUrl, '_blank')
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
px: 2,
|
||||
py: 1.25,
|
||||
borderRadius: d3roRadius.inner,
|
||||
bgcolor: 'rgba(17, 26, 48, 0.85)',
|
||||
backdropFilter: 'blur(16px)',
|
||||
border: `1px solid ${d3roPalette.glass.hairline}`,
|
||||
boxShadow: d3roShadow.card,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2,
|
||||
transition: 'all 0.25s ease',
|
||||
'&:hover': {
|
||||
borderColor: d3roPalette.accent.dim,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, minWidth: 0 }}>
|
||||
{/* Network-Specific Authentic Test Ad Badge / Watermark */}
|
||||
<Box
|
||||
sx={{
|
||||
width: 34,
|
||||
height: 34,
|
||||
borderRadius: '8px',
|
||||
bgcolor:
|
||||
creative.networkId === 'google_ad_manager'
|
||||
? 'rgba(234, 179, 8, 0.15)'
|
||||
: creative.networkId === 'ethical_ads'
|
||||
? 'rgba(34, 197, 94, 0.15)'
|
||||
: creative.networkId === 'carbon_ads'
|
||||
? 'rgba(249, 115, 22, 0.15)'
|
||||
: 'rgba(59, 130, 246, 0.15)',
|
||||
border: `1px solid ${
|
||||
creative.networkId === 'google_ad_manager'
|
||||
? 'rgba(234, 179, 8, 0.4)'
|
||||
: creative.networkId === 'ethical_ads'
|
||||
? 'rgba(34, 197, 94, 0.4)'
|
||||
: creative.networkId === 'carbon_ads'
|
||||
? 'rgba(249, 115, 22, 0.4)'
|
||||
: d3roPalette.accent.dim
|
||||
}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
fontSize: '11px',
|
||||
fontWeight: 800,
|
||||
fontFamily: d3roFontMono,
|
||||
color:
|
||||
creative.networkId === 'google_ad_manager'
|
||||
? '#facc15'
|
||||
: creative.networkId === 'ethical_ads'
|
||||
? '#4ade80'
|
||||
: creative.networkId === 'carbon_ads'
|
||||
? '#fb923c'
|
||||
: d3roPalette.accent.light,
|
||||
}}
|
||||
>
|
||||
{creative.networkId === 'google_ad_manager' ? (
|
||||
'GAM'
|
||||
) : creative.networkId === 'ethical_ads' ? (
|
||||
'ETH'
|
||||
) : creative.networkId === 'carbon_ads' ? (
|
||||
'CRB'
|
||||
) : creative.networkId === 'playwire' ? (
|
||||
'PLY'
|
||||
) : creative.networkId === 'applovin_max' ? (
|
||||
'MAX'
|
||||
) : (
|
||||
<Sparkles size={16} color={d3roPalette.accent.light} />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ minWidth: 0 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{/* Google / Network Official Test Ad Ribbon */}
|
||||
{creative.networkId === 'google_ad_manager' && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: '9px',
|
||||
fontWeight: 900,
|
||||
color: '#000',
|
||||
bgcolor: '#facc15',
|
||||
px: 0.6,
|
||||
py: 0.1,
|
||||
borderRadius: '3px',
|
||||
letterSpacing: '0.5px',
|
||||
}}
|
||||
>
|
||||
TEST AD
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
noWrap
|
||||
sx={{
|
||||
fontFamily: d3roFontSans,
|
||||
fontSize: '12px',
|
||||
fontWeight: 700,
|
||||
color: d3roPalette.text.primary,
|
||||
}}
|
||||
>
|
||||
{creative.title}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: d3roFontMono,
|
||||
fontSize: '9px',
|
||||
fontWeight: 700,
|
||||
color:
|
||||
creative.networkId === 'ethical_ads'
|
||||
? '#4ade80'
|
||||
: creative.networkId === 'carbon_ads'
|
||||
? '#fb923c'
|
||||
: d3roPalette.accent.light,
|
||||
bgcolor:
|
||||
creative.networkId === 'ethical_ads'
|
||||
? 'rgba(34, 197, 94, 0.12)'
|
||||
: creative.networkId === 'carbon_ads'
|
||||
? 'rgba(249, 115, 22, 0.12)'
|
||||
: 'rgba(59, 130, 246, 0.15)',
|
||||
border: `1px solid ${
|
||||
creative.networkId === 'ethical_ads'
|
||||
? 'rgba(34, 197, 94, 0.3)'
|
||||
: creative.networkId === 'carbon_ads'
|
||||
? 'rgba(249, 115, 22, 0.3)'
|
||||
: 'rgba(59, 130, 246, 0.3)'
|
||||
}`,
|
||||
px: 0.8,
|
||||
py: 0.2,
|
||||
borderRadius: '4px',
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
{creative.sponsorTag || 'Sponsor'}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
noWrap
|
||||
sx={{
|
||||
fontFamily: d3roFontSans,
|
||||
fontSize: '11px',
|
||||
color: d3roPalette.text.secondary,
|
||||
mt: 0.2,
|
||||
}}
|
||||
>
|
||||
{creative.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flexShrink: 0 }}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={handleSponsorClick}
|
||||
sx={{
|
||||
minWidth: 'auto',
|
||||
px: 1.5,
|
||||
py: 0.5,
|
||||
fontSize: '11px',
|
||||
fontFamily: d3roFontSans,
|
||||
fontWeight: 600,
|
||||
textTransform: 'none',
|
||||
color: d3roPalette.accent.light,
|
||||
bgcolor: 'rgba(59, 130, 246, 0.12)',
|
||||
border: `1px solid ${d3roPalette.accent.dim}`,
|
||||
borderRadius: '8px',
|
||||
'&:hover': {
|
||||
bgcolor: 'rgba(59, 130, 246, 0.22)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ExternalLink size={12} style={{ marginRight: 4 }} />
|
||||
{creative.ctaText || 'Learn More'}
|
||||
</Button>
|
||||
|
||||
<Typography
|
||||
onClick={onOpenUpgradeModal}
|
||||
sx={{
|
||||
fontFamily: d3roFontSans,
|
||||
fontSize: '10px',
|
||||
color: d3roPalette.text.dimLabel,
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
'&:hover': { color: d3roPalette.tag.greenText },
|
||||
}}
|
||||
>
|
||||
Remove ads with Pro
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
316
apps/desktop/src/renderer/components/ads/RewardedQuotaModal.tsx
Normal file
316
apps/desktop/src/renderer/components/ads/RewardedQuotaModal.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue