0x0x230x

Untitled

Mar 3rd, 2025
22
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.70 KB | None | 0 0
  1. import { ImageResponse } from 'next/og';
  2. import {
  3. get24HourLeaderboard,
  4. getLeaderboard,
  5. } from '@/lib/actions/leaderboard';
  6. import { env } from '@/lib/config/env';
  7. import { cookies } from 'next/headers';
  8.  
  9. // Image metadata
  10. export const alt = 'GIG.BOT Leaderboard';
  11. export const size = {
  12. width: 1200,
  13. height: 630,
  14. };
  15. export const contentType = 'image/png';
  16.  
  17. export const dynamic = 'force-dynamic';
  18.  
  19. // Image generation
  20. export default async function TwitterImage() {
  21. try {
  22. await cookies();
  23. const timestamp = Date.now().toString();
  24. // Fetch leaderboard data
  25. const leaderboardData = await get24HourLeaderboard();
  26. console.log('twitter image leaderboardData', leaderboardData[0]);
  27. // Get top 10 users
  28. const topUsers = leaderboardData.slice(0, 10);
  29.  
  30. // Split into two columns
  31. const leftColumnUsers = topUsers.slice(0, 5);
  32. const rightColumnUsers = topUsers.slice(5, 10);
  33.  
  34. // Medal emojis for top 3 positions
  35. const medals = ['🥇', '🥈', '🥉'];
  36.  
  37. return new ImageResponse(
  38. (
  39. <div
  40. style={{
  41. display: 'flex',
  42. flexDirection: 'column',
  43. width: '100%',
  44. height: '100%',
  45. backgroundColor: '#0F0F11',
  46. backgroundImage: `url(${
  47. env.NEXT_PUBLIC_APP_BASE_URL
  48. }leaderboard-og-image.png?t=${new Date().getTime()})`,
  49. backgroundSize: 'cover',
  50. backgroundPosition: 'center',
  51. padding: '30px 40px 40px',
  52. fontFamily: 'sans-serif',
  53. color: 'white',
  54. }}
  55. >
  56. {/* Header - Logo and Title */}
  57. <div
  58. style={{
  59. display: 'flex',
  60. alignItems: 'center',
  61. justifyContent: 'space-between',
  62. width: '100%',
  63. marginBottom: '20px',
  64. }}
  65. >
  66. <div style={{ display: 'flex', alignItems: 'center' }}>
  67. <div
  68. style={{
  69. display: 'flex',
  70. marginRight: '12px',
  71. backgroundColor: '#1F1534',
  72. padding: '8px',
  73. borderRadius: '12px',
  74. }}
  75. >
  76. <img
  77. src={`${env.NEXT_PUBLIC_APP_BASE_URL}gigbot-logo.svg?t=${timestamp}`}
  78. alt="GIG.BOT"
  79. width="42"
  80. height="42"
  81. />
  82. </div>
  83. <div
  84. style={{
  85. display: 'flex',
  86. fontSize: '40px',
  87. fontWeight: 'bold',
  88. }}
  89. >
  90. <span
  91. style={{
  92. display: 'block',
  93. color: '#855DCD',
  94. }}
  95. >
  96. GIG
  97. </span>
  98. <span
  99. style={{
  100. display: 'block',
  101. color: '#FFFFFF',
  102. }}
  103. >
  104. .BOT
  105. </span>
  106. </div>
  107. </div>
  108.  
  109. {/* Leaderboard Title */}
  110. <div
  111. style={{
  112. display: 'flex',
  113. fontSize: '24px',
  114. fontWeight: 'bold',
  115. color: '#FFFFFF',
  116. backgroundColor: '#1F1534',
  117. padding: '8px 16px',
  118. borderRadius: '12px',
  119. border: '1px solid #2A2A3C',
  120. }}
  121. >
  122. <span
  123. style={{
  124. display: 'block',
  125. color: '#FFFFFF',
  126. }}
  127. >
  128. TOP 10 LEADERBOARD
  129. </span>
  130. </div>
  131. </div>
  132.  
  133. {/* Two column layout for leaderboard */}
  134. <div
  135. style={{
  136. display: 'flex',
  137. width: '100%',
  138. gap: '20px',
  139. flexGrow: 1,
  140. }}
  141. >
  142. {/* Left Column */}
  143. <div
  144. style={{
  145. display: 'flex',
  146. flexDirection: 'column',
  147. width: '50%',
  148. gap: '10px',
  149. }}
  150. >
  151. {leftColumnUsers.map((user, index) => (
  152. <div
  153. key={`left-${index}`}
  154. style={{
  155. display: 'flex',
  156. alignItems: 'center',
  157. padding: '12px',
  158. backgroundColor: index === 0 ? '#261B40' : '#1F1534',
  159. borderRadius: '14px',
  160. border:
  161. index === 0 ? '1px solid #855DCD' : '1px solid #2A2A3C',
  162. }}
  163. >
  164. {/* Position/Medal */}
  165. <div
  166. style={{
  167. display: 'flex',
  168. alignItems: 'center',
  169. justifyContent: 'center',
  170. width: '32px',
  171. height: '32px',
  172. marginRight: '12px',
  173. backgroundColor: index < 3 ? '#261B40' : '#1F1534',
  174. borderRadius: '8px',
  175. }}
  176. >
  177. {index < 3 ? (
  178. <div
  179. style={{
  180. display: 'flex',
  181. fontSize: '20px',
  182. }}
  183. >
  184. <span style={{ display: 'block' }}>
  185. {medals[index]}
  186. </span>
  187. </div>
  188. ) : (
  189. <div
  190. style={{
  191. display: 'flex',
  192. fontSize: '16px',
  193. fontWeight: 'bold',
  194. color: '#FFFFFF',
  195. }}
  196. >
  197. {index + 1}
  198. </div>
  199. )}
  200. </div>
  201.  
  202. {/* User Avatar and Username */}
  203. <div
  204. style={{
  205. display: 'flex',
  206. alignItems: 'center',
  207. }}
  208. >
  209. {/* User Avatar */}
  210. <div
  211. style={{
  212. display: 'flex',
  213. justifyContent: 'center',
  214. alignItems: 'center',
  215. width: '32px',
  216. height: '32px',
  217. borderRadius: '8px',
  218. backgroundColor: '#2A2A3C',
  219. marginRight: '10px',
  220. overflow: 'hidden',
  221. }}
  222. >
  223. <img
  224. src={
  225. user.pfp_url
  226. ? `${user.pfp_url}?t=${timestamp}`
  227. : `${env.NEXT_PUBLIC_APP_BASE_URL}profile_icon_v2.svg?t=${timestamp}`
  228. }
  229. alt={user.username || 'User'}
  230. width="32"
  231. height="32"
  232. style={{
  233. display: 'block',
  234. objectFit: 'cover',
  235. width: '100%',
  236. height: '100%',
  237. }}
  238. />
  239. </div>
  240.  
  241. <span
  242. style={{
  243. display: 'block',
  244. fontSize: '16px',
  245. fontWeight: 'bold',
  246. color: '#EBEDF0',
  247. }}
  248. >
  249. @{user.username || `user${index + 1}`}
  250. </span>
  251. </div>
  252.  
  253. {/* Spacer to push score to the right */}
  254. <div style={{ display: 'flex', flex: 1 }}></div>
  255.  
  256. {/* Token Amount and USD Value */}
  257. <div
  258. style={{
  259. display: 'flex',
  260. flexDirection: 'column',
  261. alignItems: 'flex-end',
  262. }}
  263. >
  264. <span
  265. style={{
  266. display: 'block',
  267. fontSize: '16px',
  268. fontWeight: 'bold',
  269. color: '#FFFFFF',
  270. }}
  271. >
  272. {(() => {
  273. // Get the main token (highest value) or use DEGEN as fallback
  274. const mainToken =
  275. user.claimed_tokens && user.token_metadata
  276. ? Object.entries(
  277. user.claimed_tokens as Record<string, string>,
  278. )
  279. .sort(
  280. ([, a], [, b]) =>
  281. parseFloat(b) - parseFloat(a),
  282. )
  283. .filter(([, value]) => parseFloat(value) > 0)[0]
  284. : null;
  285.  
  286. // Format the token value
  287. const tokenValue = mainToken
  288. ? parseFloat(mainToken[1]).toLocaleString(undefined, {
  289. maximumFractionDigits: 0,
  290. })
  291. : (Number(user.total_usd_value) || 0).toLocaleString(
  292. undefined,
  293. { maximumFractionDigits: 0 },
  294. );
  295.  
  296. // Get token symbol
  297. const tokenSymbol = mainToken ? mainToken[0] : 'DEGEN';
  298.  
  299. return `${tokenValue} ${tokenSymbol}`;
  300. })()}
  301. </span>
  302. <span
  303. style={{
  304. display: 'block',
  305. fontSize: '14px',
  306. color: '#76787A',
  307. }}
  308. >
  309. (${Math.round(Number(user.total_usd_value) || 0)})
  310. </span>
  311. </div>
  312. </div>
  313. ))}
  314. </div>
  315.  
  316. {/* Right Column */}
  317. <div
  318. style={{
  319. display: 'flex',
  320. flexDirection: 'column',
  321. width: '50%',
  322. gap: '10px',
  323. }}
  324. >
  325. {rightColumnUsers.map((user, index) => (
  326. <div
  327. key={`right-${index}`}
  328. style={{
  329. display: 'flex',
  330. alignItems: 'center',
  331. padding: '12px',
  332. backgroundColor: '#1F1534',
  333. borderRadius: '14px',
  334. border: '1px solid #2A2A3C',
  335. }}
  336. >
  337. {/* Position */}
  338. <div
  339. style={{
  340. display: 'flex',
  341. alignItems: 'center',
  342. justifyContent: 'center',
  343. width: '32px',
  344. height: '32px',
  345. marginRight: '12px',
  346. backgroundColor: '#1F1534',
  347. borderRadius: '8px',
  348. }}
  349. >
  350. <div
  351. style={{
  352. display: 'flex',
  353. fontSize: '16px',
  354. fontWeight: 'bold',
  355. color: '#FFFFFF',
  356. }}
  357. >
  358. {index + 6}
  359. </div>
  360. </div>
  361.  
  362. {/* User Avatar and Username */}
  363. <div
  364. style={{
  365. display: 'flex',
  366. alignItems: 'center',
  367. }}
  368. >
  369. {/* User Avatar */}
  370. <div
  371. style={{
  372. display: 'flex',
  373. justifyContent: 'center',
  374. alignItems: 'center',
  375. width: '32px',
  376. height: '32px',
  377. borderRadius: '8px',
  378. backgroundColor: '#2A2A3C',
  379. marginRight: '10px',
  380. overflow: 'hidden',
  381. }}
  382. >
  383. <img
  384. src={
  385. user.pfp_url
  386. ? `${user.pfp_url}?t=${timestamp}`
  387. : `${env.NEXT_PUBLIC_APP_BASE_URL}profile_icon_v2.svg?t=${timestamp}`
  388. }
  389. alt={user.username || 'User'}
  390. width="32"
  391. height="32"
  392. style={{
  393. display: 'block',
  394. objectFit: 'cover',
  395. width: '100%',
  396. height: '100%',
  397. }}
  398. />
  399. </div>
  400.  
  401. <span
  402. style={{
  403. display: 'block',
  404. fontSize: '16px',
  405. fontWeight: 'bold',
  406. color: '#EBEDF0',
  407. }}
  408. >
  409. @{user.username || `user${index + 6}`}
  410. </span>
  411. </div>
  412.  
  413. {/* Spacer to push score to the right */}
  414. <div style={{ display: 'flex', flex: 1 }}></div>
  415.  
  416. {/* Token Amount and USD Value */}
  417. <div
  418. style={{
  419. display: 'flex',
  420. flexDirection: 'column',
  421. alignItems: 'flex-end',
  422. }}
  423. >
  424. <span
  425. style={{
  426. display: 'block',
  427. fontSize: '16px',
  428. fontWeight: 'bold',
  429. color: '#FFFFFF',
  430. }}
  431. >
  432. {(() => {
  433. // Get the main token (highest value) or use DEGEN as fallback
  434. const mainToken =
  435. user.claimed_tokens && user.token_metadata
  436. ? Object.entries(
  437. user.claimed_tokens as Record<string, string>,
  438. )
  439. .sort(
  440. ([, a], [, b]) =>
  441. parseFloat(b) - parseFloat(a),
  442. )
  443. .filter(([, value]) => parseFloat(value) > 0)[0]
  444. : null;
  445.  
  446. // Format the token value
  447. const tokenValue = mainToken
  448. ? parseFloat(mainToken[1]).toLocaleString(undefined, {
  449. maximumFractionDigits: 0,
  450. })
  451. : (Number(user.total_usd_value) || 0).toLocaleString(
  452. undefined,
  453. { maximumFractionDigits: 0 },
  454. );
  455.  
  456. // Get token symbol
  457. const tokenSymbol = mainToken ? mainToken[0] : 'DEGEN';
  458.  
  459. return `${tokenValue} ${tokenSymbol}`;
  460. })()}
  461. </span>
  462. <span
  463. style={{
  464. display: 'block',
  465. fontSize: '14px',
  466. color: '#76787A',
  467. }}
  468. >
  469. (${Math.round(Number(user.total_usd_value) || 0)})
  470. </span>
  471. </div>
  472. </div>
  473. ))}
  474. </div>
  475. </div>
  476.  
  477. {/* Footer */}
  478. <div
  479. style={{
  480. display: 'flex',
  481. alignItems: 'center',
  482. justifyContent: 'center',
  483. marginTop: '20px',
  484. }}
  485. >
  486. <div
  487. style={{
  488. display: 'block',
  489. fontSize: '14px',
  490. color: '#76787A',
  491. padding: '6px 12px',
  492. backgroundColor: '#1F1534',
  493. borderRadius: '8px',
  494. border: '1px solid #2A2A3C',
  495. }}
  496. >
  497. gig.bot • 24h leaderboard
  498. </div>
  499. </div>
  500. </div>
  501. ),
  502. {
  503. ...size,
  504. headers: {
  505. 'Cache-Control':
  506. 'no-store, no-cache, must-revalidate, proxy-revalidate',
  507. Pragma: 'no-cache',
  508. Expires: '0',
  509. },
  510. },
  511. );
  512. } catch (error) {
  513. console.error('Error generating Twitter image:', error);
  514.  
  515. // Get timestamp for the fallback image
  516. const timestamp = Date.now().toString();
  517.  
  518. // Return a simple fallback image
  519. return new ImageResponse(
  520. (
  521. <div
  522. style={{
  523. display: 'flex',
  524. flexDirection: 'column',
  525. alignItems: 'center',
  526. justifyContent: 'center',
  527. width: '100%',
  528. height: '100%',
  529. backgroundColor: '#0F0F11',
  530. padding: '40px',
  531. fontFamily: 'sans-serif',
  532. color: 'white',
  533. }}
  534. >
  535. <div
  536. style={{
  537. display: 'flex',
  538. fontSize: '32px',
  539. fontWeight: 'bold',
  540. }}
  541. >
  542. <span style={{ display: 'block' }}>
  543. GIG.BOT Leaderboard • {timestamp}
  544. </span>
  545. </div>
  546. </div>
  547. ),
  548. {
  549. ...size,
  550. headers: {
  551. 'Cache-Control':
  552. 'no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate',
  553. 'CDN-Cache-Control': 'no-store, no-cache',
  554. 'Vercel-CDN-Cache-Control': 'no-store, no-cache',
  555. Pragma: 'no-cache',
  556. Expires: '0',
  557. 'Surrogate-Control': 'no-store',
  558. },
  559. },
  560. );
  561. }
  562. }
  563.  
Advertisement
Add Comment
Please, Sign In to add comment