-
Notifications
You must be signed in to change notification settings - Fork 519
Expand file tree
/
Copy pathad-banner.tsx
More file actions
119 lines (110 loc) · 3.19 KB
/
ad-banner.tsx
File metadata and controls
119 lines (110 loc) · 3.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import open from 'open'
import React, { useCallback, useEffect, useState } from 'react'
import { Button } from './button'
import { useTerminalDimensions } from '../hooks/use-terminal-dimensions'
import { useTheme } from '../hooks/use-theme'
import { logger } from '../utils/logger'
import type { AdResponse } from '../hooks/use-gravity-ad'
interface AdBannerProps {
ad: AdResponse
}
const extractDomain = (url: string): string => {
try {
const parsed = new URL(url)
return parsed.hostname.replace(/^www\./, '')
} catch {
return url
}
}
export const AdBanner: React.FC<AdBannerProps> = ({ ad }) => {
useEffect(() => {
logger.info(
{ adText: ad.adText?.substring(0, 50), hasClickUrl: !!ad.clickUrl },
'[gravity] Rendering AdBanner',
)
}, [ad])
const theme = useTheme()
const { separatorWidth, terminalWidth } = useTerminalDimensions()
const [isLinkHovered, setIsLinkHovered] = useState(false)
const handleClick = useCallback(() => {
if (ad.clickUrl) {
open(ad.clickUrl).catch((err) => {
logger.error(err, 'Failed to open ad link')
})
}
}, [ad.clickUrl])
// Use 'url' field for display domain (the actual destination)
const domain = extractDomain(ad.url)
// Use title as CTA
const ctaText = ad.title
// Calculate available width for ad text
// Account for: padding (2), "Ad" label with space (3)
const maxTextWidth = separatorWidth - 5
return (
<box
style={{
width: '100%',
flexDirection: 'column',
}}
>
{/* Horizontal divider line */}
<text style={{ fg: theme.muted }}>{'─'.repeat(terminalWidth)}</text>
{/* Top line: ad text + Ad label */}
<box
style={{
width: '100%',
paddingLeft: 1,
paddingRight: 1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
}}
>
<text
style={{
fg: theme.foreground,
flexShrink: 1,
maxWidth: maxTextWidth,
}}
>
{ad.adText}
</text>
<text style={{ fg: theme.muted, flexShrink: 0 }}>Ad</text>
</box>
{/* Bottom line: button, domain, credits */}
<box
style={{
width: '100%',
paddingLeft: 1,
paddingRight: 1,
flexDirection: 'row',
flexWrap: 'wrap',
columnGap: 2,
alignItems: 'center',
}}
>
{ctaText && (
<Button
onClick={handleClick}
onMouseOver={() => setIsLinkHovered(true)}
onMouseOut={() => setIsLinkHovered(false)}
>
<text
style={{
fg: theme.name === 'light' ? '#ffffff' : theme.background,
bg: isLinkHovered ? theme.link : theme.muted,
}}
>
{` ${ctaText} `}
</text>
</Button>
)}
{domain && <text style={{ fg: theme.muted }}>{domain}</text>}
<box style={{ flexGrow: 1 }} />
{ad.credits != null && ad.credits > 0 && (
<text style={{ fg: theme.muted }}>+{ad.credits} credits</text>
)}
</box>
</box>
)
}