Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { ImageResponse } from 'next/og';
- import {
- get24HourLeaderboard,
- getLeaderboard,
- } from '@/lib/actions/leaderboard';
- import { env } from '@/lib/config/env';
- import { cookies } from 'next/headers';
- // Image metadata
- export const alt = 'GIG.BOT Leaderboard';
- export const size = {
- width: 1200,
- height: 630,
- };
- export const contentType = 'image/png';
- export const dynamic = 'force-dynamic';
- // Image generation
- export default async function TwitterImage() {
- try {
- await cookies();
- const timestamp = Date.now().toString();
- // Fetch leaderboard data
- const leaderboardData = await get24HourLeaderboard();
- console.log('twitter image leaderboardData', leaderboardData[0]);
- // Get top 10 users
- const topUsers = leaderboardData.slice(0, 10);
- // Split into two columns
- const leftColumnUsers = topUsers.slice(0, 5);
- const rightColumnUsers = topUsers.slice(5, 10);
- // Medal emojis for top 3 positions
- const medals = ['🥇', '🥈', '🥉'];
- return new ImageResponse(
- (
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- width: '100%',
- height: '100%',
- backgroundColor: '#0F0F11',
- backgroundImage: `url(${
- env.NEXT_PUBLIC_APP_BASE_URL
- }leaderboard-og-image.png?t=${new Date().getTime()})`,
- backgroundSize: 'cover',
- backgroundPosition: 'center',
- padding: '30px 40px 40px',
- fontFamily: 'sans-serif',
- color: 'white',
- }}
- >
- {/* Header - Logo and Title */}
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'space-between',
- width: '100%',
- marginBottom: '20px',
- }}
- >
- <div style={{ display: 'flex', alignItems: 'center' }}>
- <div
- style={{
- display: 'flex',
- marginRight: '12px',
- backgroundColor: '#1F1534',
- padding: '8px',
- borderRadius: '12px',
- }}
- >
- <img
- src={`${env.NEXT_PUBLIC_APP_BASE_URL}gigbot-logo.svg?t=${timestamp}`}
- alt="GIG.BOT"
- width="42"
- height="42"
- />
- </div>
- <div
- style={{
- display: 'flex',
- fontSize: '40px',
- fontWeight: 'bold',
- }}
- >
- <span
- style={{
- display: 'block',
- color: '#855DCD',
- }}
- >
- GIG
- </span>
- <span
- style={{
- display: 'block',
- color: '#FFFFFF',
- }}
- >
- .BOT
- </span>
- </div>
- </div>
- {/* Leaderboard Title */}
- <div
- style={{
- display: 'flex',
- fontSize: '24px',
- fontWeight: 'bold',
- color: '#FFFFFF',
- backgroundColor: '#1F1534',
- padding: '8px 16px',
- borderRadius: '12px',
- border: '1px solid #2A2A3C',
- }}
- >
- <span
- style={{
- display: 'block',
- color: '#FFFFFF',
- }}
- >
- TOP 10 LEADERBOARD
- </span>
- </div>
- </div>
- {/* Two column layout for leaderboard */}
- <div
- style={{
- display: 'flex',
- width: '100%',
- gap: '20px',
- flexGrow: 1,
- }}
- >
- {/* Left Column */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- width: '50%',
- gap: '10px',
- }}
- >
- {leftColumnUsers.map((user, index) => (
- <div
- key={`left-${index}`}
- style={{
- display: 'flex',
- alignItems: 'center',
- padding: '12px',
- backgroundColor: index === 0 ? '#261B40' : '#1F1534',
- borderRadius: '14px',
- border:
- index === 0 ? '1px solid #855DCD' : '1px solid #2A2A3C',
- }}
- >
- {/* Position/Medal */}
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- width: '32px',
- height: '32px',
- marginRight: '12px',
- backgroundColor: index < 3 ? '#261B40' : '#1F1534',
- borderRadius: '8px',
- }}
- >
- {index < 3 ? (
- <div
- style={{
- display: 'flex',
- fontSize: '20px',
- }}
- >
- <span style={{ display: 'block' }}>
- {medals[index]}
- </span>
- </div>
- ) : (
- <div
- style={{
- display: 'flex',
- fontSize: '16px',
- fontWeight: 'bold',
- color: '#FFFFFF',
- }}
- >
- {index + 1}
- </div>
- )}
- </div>
- {/* User Avatar and Username */}
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- }}
- >
- {/* User Avatar */}
- <div
- style={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- width: '32px',
- height: '32px',
- borderRadius: '8px',
- backgroundColor: '#2A2A3C',
- marginRight: '10px',
- overflow: 'hidden',
- }}
- >
- <img
- src={
- user.pfp_url
- ? `${user.pfp_url}?t=${timestamp}`
- : `${env.NEXT_PUBLIC_APP_BASE_URL}profile_icon_v2.svg?t=${timestamp}`
- }
- alt={user.username || 'User'}
- width="32"
- height="32"
- style={{
- display: 'block',
- objectFit: 'cover',
- width: '100%',
- height: '100%',
- }}
- />
- </div>
- <span
- style={{
- display: 'block',
- fontSize: '16px',
- fontWeight: 'bold',
- color: '#EBEDF0',
- }}
- >
- @{user.username || `user${index + 1}`}
- </span>
- </div>
- {/* Spacer to push score to the right */}
- <div style={{ display: 'flex', flex: 1 }}></div>
- {/* Token Amount and USD Value */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-end',
- }}
- >
- <span
- style={{
- display: 'block',
- fontSize: '16px',
- fontWeight: 'bold',
- color: '#FFFFFF',
- }}
- >
- {(() => {
- // Get the main token (highest value) or use DEGEN as fallback
- const mainToken =
- user.claimed_tokens && user.token_metadata
- ? Object.entries(
- user.claimed_tokens as Record<string, string>,
- )
- .sort(
- ([, a], [, b]) =>
- parseFloat(b) - parseFloat(a),
- )
- .filter(([, value]) => parseFloat(value) > 0)[0]
- : null;
- // Format the token value
- const tokenValue = mainToken
- ? parseFloat(mainToken[1]).toLocaleString(undefined, {
- maximumFractionDigits: 0,
- })
- : (Number(user.total_usd_value) || 0).toLocaleString(
- undefined,
- { maximumFractionDigits: 0 },
- );
- // Get token symbol
- const tokenSymbol = mainToken ? mainToken[0] : 'DEGEN';
- return `${tokenValue} ${tokenSymbol}`;
- })()}
- </span>
- <span
- style={{
- display: 'block',
- fontSize: '14px',
- color: '#76787A',
- }}
- >
- (${Math.round(Number(user.total_usd_value) || 0)})
- </span>
- </div>
- </div>
- ))}
- </div>
- {/* Right Column */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- width: '50%',
- gap: '10px',
- }}
- >
- {rightColumnUsers.map((user, index) => (
- <div
- key={`right-${index}`}
- style={{
- display: 'flex',
- alignItems: 'center',
- padding: '12px',
- backgroundColor: '#1F1534',
- borderRadius: '14px',
- border: '1px solid #2A2A3C',
- }}
- >
- {/* Position */}
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- width: '32px',
- height: '32px',
- marginRight: '12px',
- backgroundColor: '#1F1534',
- borderRadius: '8px',
- }}
- >
- <div
- style={{
- display: 'flex',
- fontSize: '16px',
- fontWeight: 'bold',
- color: '#FFFFFF',
- }}
- >
- {index + 6}
- </div>
- </div>
- {/* User Avatar and Username */}
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- }}
- >
- {/* User Avatar */}
- <div
- style={{
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- width: '32px',
- height: '32px',
- borderRadius: '8px',
- backgroundColor: '#2A2A3C',
- marginRight: '10px',
- overflow: 'hidden',
- }}
- >
- <img
- src={
- user.pfp_url
- ? `${user.pfp_url}?t=${timestamp}`
- : `${env.NEXT_PUBLIC_APP_BASE_URL}profile_icon_v2.svg?t=${timestamp}`
- }
- alt={user.username || 'User'}
- width="32"
- height="32"
- style={{
- display: 'block',
- objectFit: 'cover',
- width: '100%',
- height: '100%',
- }}
- />
- </div>
- <span
- style={{
- display: 'block',
- fontSize: '16px',
- fontWeight: 'bold',
- color: '#EBEDF0',
- }}
- >
- @{user.username || `user${index + 6}`}
- </span>
- </div>
- {/* Spacer to push score to the right */}
- <div style={{ display: 'flex', flex: 1 }}></div>
- {/* Token Amount and USD Value */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-end',
- }}
- >
- <span
- style={{
- display: 'block',
- fontSize: '16px',
- fontWeight: 'bold',
- color: '#FFFFFF',
- }}
- >
- {(() => {
- // Get the main token (highest value) or use DEGEN as fallback
- const mainToken =
- user.claimed_tokens && user.token_metadata
- ? Object.entries(
- user.claimed_tokens as Record<string, string>,
- )
- .sort(
- ([, a], [, b]) =>
- parseFloat(b) - parseFloat(a),
- )
- .filter(([, value]) => parseFloat(value) > 0)[0]
- : null;
- // Format the token value
- const tokenValue = mainToken
- ? parseFloat(mainToken[1]).toLocaleString(undefined, {
- maximumFractionDigits: 0,
- })
- : (Number(user.total_usd_value) || 0).toLocaleString(
- undefined,
- { maximumFractionDigits: 0 },
- );
- // Get token symbol
- const tokenSymbol = mainToken ? mainToken[0] : 'DEGEN';
- return `${tokenValue} ${tokenSymbol}`;
- })()}
- </span>
- <span
- style={{
- display: 'block',
- fontSize: '14px',
- color: '#76787A',
- }}
- >
- (${Math.round(Number(user.total_usd_value) || 0)})
- </span>
- </div>
- </div>
- ))}
- </div>
- </div>
- {/* Footer */}
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- marginTop: '20px',
- }}
- >
- <div
- style={{
- display: 'block',
- fontSize: '14px',
- color: '#76787A',
- padding: '6px 12px',
- backgroundColor: '#1F1534',
- borderRadius: '8px',
- border: '1px solid #2A2A3C',
- }}
- >
- gig.bot • 24h leaderboard
- </div>
- </div>
- </div>
- ),
- {
- ...size,
- headers: {
- 'Cache-Control':
- 'no-store, no-cache, must-revalidate, proxy-revalidate',
- Pragma: 'no-cache',
- Expires: '0',
- },
- },
- );
- } catch (error) {
- console.error('Error generating Twitter image:', error);
- // Get timestamp for the fallback image
- const timestamp = Date.now().toString();
- // Return a simple fallback image
- return new ImageResponse(
- (
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- width: '100%',
- height: '100%',
- backgroundColor: '#0F0F11',
- padding: '40px',
- fontFamily: 'sans-serif',
- color: 'white',
- }}
- >
- <div
- style={{
- display: 'flex',
- fontSize: '32px',
- fontWeight: 'bold',
- }}
- >
- <span style={{ display: 'block' }}>
- GIG.BOT Leaderboard • {timestamp}
- </span>
- </div>
- </div>
- ),
- {
- ...size,
- headers: {
- 'Cache-Control':
- 'no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate',
- 'CDN-Cache-Control': 'no-store, no-cache',
- 'Vercel-CDN-Cache-Control': 'no-store, no-cache',
- Pragma: 'no-cache',
- Expires: '0',
- 'Surrogate-Control': 'no-store',
- },
- },
- );
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment