Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { env } from '@/lib/config/env';
- import { gigsService } from '@/lib/services/GigsService';
- import { ImageResponse } from 'next/og';
- import { formatUnits } from 'viem';
- import { coinGeckoService } from '@/lib/services/price-feed/CoinGeckoService';
- import { ETH_ADDRESS } from '@/lib/constants/common';
- const width = 1200;
- const height = 800;
- const gigbotLogo = `${env.NEXT_PUBLIC_APP_BASE_URL}gigbot-logo.svg`;
- const bgImage = `${env.NEXT_PUBLIC_APP_BASE_URL}og-bg-image.svg`;
- const baseLogo = `https://raw.githubusercontent.com/base/brand-kit/refs/heads/main/logo/in-product/Base_Network_Logo.svg`;
- const ethLogo = `${env.NEXT_PUBLIC_APP_BASE_URL}eth.svg`;
- function formatTokenAmount(amount: number, decimals: number): string {
- const formatted = formatUnits(BigInt(amount), decimals);
- return formatted.toLocaleString();
- }
- // Helper function to render embedded content for OG image
- function renderEmbeddedContent(gig: any) {
- const actionParams = gig.action_params || {};
- // Post
- if (actionParams.post) {
- const post = actionParams.post;
- const postText = (post.text || '').replace(/\n/g, ' ');
- return (
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- width: '100%',
- minHeight: 120,
- padding: 0,
- height: 220,
- // background: 'yellow',
- }}
- >
- <div
- style={{
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'flex-start',
- alignItems: 'center',
- gap: 18,
- marginBottom: 12,
- width: '100%',
- }}
- >
- {post.author?.pfp ? (
- <img
- src={post.author?.pfp}
- alt={post.author?.username}
- width={48}
- height={48}
- style={{ borderRadius: 12, objectFit: 'cover', flexShrink: 0 }}
- />
- ) : (
- <div
- style={{
- width: 48,
- height: 48,
- borderRadius: '50%',
- background: '#232136',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- fontSize: 32,
- fontWeight: 900,
- color: '#A78BFA', // nice purple
- flexShrink: 0,
- border: '2px solid #393552',
- }}
- >
- {post.author?.username?.[0]?.toUpperCase() || 'U'}
- </div>
- )}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- maxWidth: 300,
- minWidth: 0,
- }}
- >
- <span
- style={{
- fontWeight: 900,
- fontSize: 32,
- color: '#e5e7eb',
- letterSpacing: 0.5,
- maxWidth: 300,
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- background: 'rgba(37,35,59,0.18)',
- borderRadius: 6,
- padding: '2px 8px',
- }}
- >
- {post.author?.displayName || post.author?.username}
- </span>
- <span
- style={{
- fontSize: 18,
- color: '#e5e7eb',
- maxWidth: 300,
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- marginTop: 2,
- }}
- >
- @{post.author?.username}
- </span>
- </div>
- </div>
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 30,
- fontWeight: 500,
- wordBreak: 'break-word',
- whiteSpace: 'pre-line',
- lineHeight: 1.4,
- maxWidth: '100%',
- overflowWrap: 'break-word',
- height: 170,
- overflow: 'hidden',
- display: '-webkit-box',
- WebkitLineClamp: 3,
- WebkitBoxOrient: 'vertical',
- textOverflow: 'ellipsis',
- }}
- >
- {postText}
- </span>
- </div>
- );
- }
- // Profile
- if (actionParams.profile) {
- const profile = actionParams.profile;
- const bioText = (profile.bio || '').replace(/\n/g, ' ');
- return (
- <div
- style={{
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'flex-start',
- width: '100%',
- minHeight: 220,
- padding: 0,
- height: 220,
- gap: 12,
- }}
- >
- <img
- src={profile.pfp}
- alt={profile.username}
- width={160}
- height={160}
- style={{ borderRadius: 28, objectFit: 'cover', marginBottom: 18 }}
- />
- <div style={{ display: 'flex', flexDirection: 'column' }}>
- <span
- style={{
- fontWeight: 800,
- fontSize: 34,
- color: '#e5e7eb',
- letterSpacing: 0.5,
- marginBottom: 6,
- textAlign: 'center',
- maxWidth: 400,
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- }}
- >
- @{profile.username}
- </span>
- <span
- style={{
- fontSize: 32,
- color: '#e5e7eb',
- fontWeight: 500,
- textAlign: 'center',
- maxWidth: 400,
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- marginBottom: 18,
- }}
- >
- {profile.displayName}
- </span>
- </div>
- </div>
- );
- }
- // Channel
- if (actionParams.channel) {
- const channel = actionParams.channel;
- const descText = (channel.description || '').replace(/\n/g, ' ');
- return (
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- width: '100%',
- minHeight: 120,
- padding: 0,
- height: 340,
- }}
- >
- <div
- style={{
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center',
- gap: 18,
- marginBottom: 12,
- width: '100%',
- }}
- >
- {channel.image_url && (
- <img
- src={channel.image_url}
- alt={channel.name}
- width={48}
- height={48}
- style={{ borderRadius: 12, objectFit: 'cover', flexShrink: 0 }}
- />
- )}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- maxWidth: 300,
- minWidth: 0,
- }}
- >
- <span
- style={{
- fontWeight: 900,
- fontSize: 32,
- color: '#e5e7eb',
- letterSpacing: 0.5,
- maxWidth: 300,
- overflow: 'hidden',
- textOverflow: 'ellipsis',
- whiteSpace: 'nowrap',
- background: 'rgba(37,35,59,0.18)',
- borderRadius: 6,
- padding: '2px 8px',
- }}
- >
- {channel.name}
- </span>
- </div>
- </div>
- {channel.description && (
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 30,
- fontWeight: 500,
- wordBreak: 'break-word',
- whiteSpace: 'pre-line',
- lineHeight: 1.4,
- maxWidth: '100%',
- overflowWrap: 'break-word',
- height: 200,
- overflow: 'hidden',
- display: '-webkit-box',
- WebkitLineClamp: 5,
- WebkitBoxOrient: 'vertical',
- textOverflow: 'ellipsis',
- }}
- >
- {descText}
- </span>
- )}
- </div>
- );
- }
- return null;
- }
- export async function GET(
- request: Request,
- { params }: { params: Promise<{ id: string }> },
- ) {
- const { id } = await params;
- const gigId = Number(id);
- const gig = await gigsService.getGig(gigId);
- if (!gig || !gig.production) {
- return new ImageResponse(
- (
- <div
- style={{
- width,
- height,
- position: 'relative',
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- fontFamily: 'Inter',
- color: 'white',
- }}
- >
- <img
- src={bgImage}
- width={width}
- height={height}
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- objectFit: 'cover',
- zIndex: 0,
- }}
- />
- <div
- style={{
- position: 'absolute',
- top: 32,
- left: 32,
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center',
- zIndex: 2,
- }}
- >
- <img
- src={gigbotLogo}
- alt="Gigbot Logo"
- width={64}
- height={64}
- style={{ display: 'block' }}
- />
- <div
- style={{
- display: 'flex',
- fontSize: 40,
- fontWeight: 900,
- letterSpacing: 2,
- marginLeft: 12,
- }}
- >
- <span style={{ color: '#855DCD', marginRight: 4 }}>GIG</span>
- <span style={{ color: '#fff' }}>.BOT</span>
- </div>
- </div>
- <div
- style={{
- zIndex: 2,
- backgroundColor: 'rgba(163, 110, 253, 0.20)',
- padding: 48,
- borderRadius: 24,
- fontSize: 40,
- fontWeight: 600,
- color: '#855DCD',
- border: '1px solid #594798',
- }}
- >
- Gig not found
- </div>
- </div>
- ),
- { width, height },
- );
- }
- // Fetch USD price for the token
- let usdPrice = 0;
- try {
- const prices = await coinGeckoService.getTokensPrices();
- const price =
- prices[gig.token.coingecko_id || ''] ||
- prices[gig.token.pair_address || ''];
- if (price) {
- usdPrice = Number(price);
- }
- } catch (e) {
- console.error('Failed to fetch USD price', e);
- }
- const formattedAmount = formatTokenAmount(gig.amount, gig.token.decimals);
- const durationInDays = Math.ceil(
- (gig.end_time_ms - gig.start_time_ms) / (1000 * 60 * 60 * 24),
- );
- const howToEarn = gig.how_to_earn?.split('\n').filter(Boolean).join(' ');
- // Calculate USD value for the split info
- const amountInUnits = Number(
- formatUnits(BigInt(gig.amount), gig.token.decimals),
- );
- const usdTotal = usdPrice * amountInUnits;
- const hasEmbeddedContent = !!renderEmbeddedContent(gig);
- const mockedHowToEarn = howToEarn + howToEarn + howToEarn + howToEarn;
- const tokenLogo =
- gig.token.address.toLowerCase() === ETH_ADDRESS
- ? ethLogo
- : gig.token.image_url;
- return new ImageResponse(
- (
- <div
- style={{
- width,
- height,
- position: 'relative',
- display: 'flex',
- flexDirection: 'column',
- fontFamily: 'Inter',
- color: 'white',
- }}
- >
- <img
- src={bgImage}
- width={width}
- height={height}
- style={{
- position: 'absolute',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- objectFit: 'cover',
- zIndex: 0,
- }}
- />
- <div
- style={{
- position: 'absolute',
- top: 32,
- left: 32,
- display: 'flex',
- flexDirection: 'row',
- alignItems: 'center',
- zIndex: 2,
- }}
- >
- <img
- src={env.NEXT_PUBLIC_APP_BASE_URL + 'gigbot-logo.svg'}
- alt="Gigbot Logo"
- width={64}
- height={64}
- style={{ display: 'block' }}
- />
- <div
- style={{
- display: 'flex',
- fontSize: 48,
- fontWeight: 900,
- letterSpacing: 2,
- marginLeft: 12,
- }}
- >
- <span style={{ color: '#855DCD', marginRight: 4 }}>GIG</span>
- <span style={{ color: '#fff' }}>.BOT</span>
- </div>
- </div>
- <div
- style={{
- zIndex: 2,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- justifyContent: 'center',
- flexGrow: 1,
- padding: '48px 64px',
- marginTop: 70,
- }}
- >
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- backgroundColor: 'rgba(163, 110, 253, 0.20)',
- backdropFilter: 'blur(12px)',
- WebkitBackdropFilter: 'blur(12px)',
- padding: 20,
- borderRadius: 24,
- width: 1150,
- height: 650,
- alignItems: 'flex-start',
- border: '1px solid #594798',
- gap: 32,
- }}
- >
- {/* Row 1: Token + Base logos and Title (combined) */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'flex-start',
- alignItems: 'center',
- position: 'relative',
- width: '100%',
- gap: 70,
- }}
- >
- {/* Token + Base logos */}
- <div
- style={{
- display: 'flex',
- position: 'relative',
- width: 96,
- height: 96,
- justifyContent: 'flex-start',
- alignItems: 'center',
- }}
- >
- <img
- src={baseLogo}
- alt="Base logo"
- width={100}
- height={100}
- style={{
- position: 'absolute',
- left: 56,
- top: 0,
- zIndex: 1,
- }}
- />
- <img
- src={tokenLogo}
- alt="Token logo"
- width={100}
- height={100}
- style={{
- position: 'absolute',
- left: 0,
- top: 0,
- zIndex: 2,
- borderRadius: '50%',
- }}
- />
- </div>
- {/* Title */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'flex-start',
- alignItems: 'flex-start',
- flexWrap: 'wrap',
- gap: 0,
- color: '#e5e7eb',
- fontFamily: 'Inter',
- fontSize: 62,
- fontStyle: 'normal',
- fontWeight: 600,
- lineHeight: 'normal',
- letterSpacing: 0.353,
- textAlign: 'left',
- }}
- >
- <div
- style={{
- display: 'flex',
- gap: 8,
- alignItems: 'center',
- textShadow:
- '1px 0 #fff, -1px 0 #fff, 0 1px #fff, 0 -1px #fff',
- }}
- >
- <span>
- Earn {formattedAmount} ${gig.token.symbol}
- </span>
- </div>
- <span
- style={{
- marginTop: 4,
- fontSize: 49,
- fontWeight: 500,
- color: '#e5e7eb',
- letterSpacing: 0.5,
- textShadow:
- '1px 0 #fff, -1px 0 #fff, 0 1px #fff, 0 -1px #fff',
- }}
- >
- {`~$${usdTotal.toLocaleString(undefined, {
- maximumFractionDigits: 2,
- })} split to all earners`}
- </span>
- </div>
- </div>
- {/* Row with two 50% containers */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'row',
- width: '100%',
- gap: 0,
- marginTop: 10,
- position: 'relative',
- minHeight: 300,
- }}
- >
- {!hasEmbeddedContent ? (
- <div
- style={{
- position: 'absolute',
- left: '50%',
- top: '65%',
- transform: 'translate(-50%, -50%)',
- width: '100%',
- borderRadius: 24,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- zIndex: 2,
- height: 450,
- textShadow:
- '1px 0 #fff, -1px 0 #fff, 0 1px #fff, 0 -1px #fff',
- }}
- >
- <div
- style={{
- height: 300,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- justifyContent: 'flex-start',
- gap: 12,
- padding: 12,
- width: '100%',
- marginTop: 40,
- }}
- >
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 36,
- fontWeight: 900,
- marginBottom: 12,
- letterSpacing: 0.5,
- fontFamily: 'Inter',
- textShadow:
- '1px 0 #855DCD, -1px 0 #855DCD, 0 1px #855DCD, 0 -1px #855DCD',
- }}
- >
- HOW TO EARN
- </span>
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 30,
- fontWeight: 500,
- wordBreak: 'break-word',
- whiteSpace: 'pre-line',
- lineHeight: 1.4,
- maxWidth: '100%',
- overflowWrap: 'break-word',
- height: 170,
- display: '-webkit-box',
- WebkitLineClamp: 4,
- WebkitBoxOrient: 'vertical',
- textOverflow: 'ellipsis',
- zIndex: 100,
- letterSpacing: 1,
- }}
- >
- {howToEarn && howToEarn.trim().length > 0 ? (
- howToEarn
- ) : (
- <span style={{ color: '#b6b6b6', fontStyle: 'italic' }}>
- No instructions provided
- </span>
- )}
- </span>
- </div>
- <div
- style={{
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'flex-end',
- width: '100%',
- marginTop: 0,
- }}
- >
- {/* GIG TYPE */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- minWidth: 0,
- }}
- >
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 36,
- fontWeight: 900,
- marginBottom: 10,
- letterSpacing: 0.5,
- }}
- >
- GIG TYPE
- </span>
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- gap: 14,
- }}
- >
- {gig.gig_type?.display?.[gig.platform]?.icon && (
- <img
- src={gig.gig_type.display[gig.platform].icon}
- alt="gig type icon"
- width={34}
- height={34}
- style={{ borderRadius: 6, background: '#232136' }}
- />
- )}
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 32,
- fontWeight: 600,
- textTransform: 'uppercase',
- letterSpacing: 0.5,
- }}
- >
- {gig.gig_type?.display?.[gig.platform]?.label || ''}
- </span>
- </div>
- </div>
- {/* OFFER ENDS */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-end',
- minWidth: 0,
- textAlign: 'right',
- }}
- >
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 36,
- fontWeight: 900,
- marginBottom: 10,
- letterSpacing: 0.5,
- textShadow:
- '1px 0 #fff, -1px 0 #fff, 0 1px #fff, 0 -1px #fff',
- }}
- >
- OFFER ENDS
- </span>
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 32,
- fontWeight: 600,
- letterSpacing: 0.5,
- }}
- >
- {new Date(gig.end_time_ms).toLocaleString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- hour12: true,
- })}
- </span>
- </div>
- </div>
- </div>
- ) : (
- <>
- <div
- style={{
- width: '45%',
- borderRadius: 16,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- boxSizing: 'border-box',
- gap: 0,
- padding: '8px 0 8px 8px',
- // background: 'red',
- }}
- >
- {/* HOW TO EARN section */}
- <div
- style={{
- // background: 'yellow',
- display: 'flex',
- flexDirection: 'column',
- height: 200,
- }}
- >
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 36,
- fontWeight: 900,
- marginBottom: 12,
- letterSpacing: 0.5,
- textShadow:
- '1px 0 #fff, -1px 0 #fff, 0 1px #fff, 0 -1px #fff',
- }}
- >
- HOW TO EARN
- </span>
- {howToEarn ? (
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 30,
- fontWeight: 500,
- wordBreak: 'break-word',
- whiteSpace: 'pre-line',
- lineHeight: 1.4,
- maxWidth: '100%',
- overflowWrap: 'break-word',
- minHeight: 60,
- overflow: 'hidden',
- display: '-webkit-box',
- WebkitLineClamp: 3,
- WebkitBoxOrient: 'vertical',
- textOverflow: 'ellipsis',
- marginBottom: 4,
- marginTop: 28,
- height: 260,
- }}
- >
- {howToEarn}
- </span>
- ) : (
- <span
- style={{
- color: '#b6b6b6',
- fontSize: 32,
- fontStyle: 'italic',
- minHeight: 60,
- marginBottom: 32,
- display: 'flex',
- alignItems: 'center',
- }}
- >
- No instructions provided
- </span>
- )}
- </div>
- {/* GIG TYPE only, now at the bottom left */}
- <div
- style={{
- marginTop: 105,
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'space-between',
- alignItems: 'flex-end',
- width: '100%',
- gap: 32,
- // background: 'blue',
- }}
- >
- {/* GIG TYPE */}
- <div
- style={{
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- minWidth: 0,
- }}
- >
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 36,
- fontWeight: 900,
- marginBottom: 10,
- letterSpacing: 0.5,
- textShadow:
- '1px 0 #fff, -1px 0 #fff, 0 1px #fff, 0 -1px #fff',
- }}
- >
- GIG TYPE
- </span>
- <div
- style={{
- display: 'flex',
- alignItems: 'center',
- gap: 14,
- }}
- >
- {gig.gig_type?.display?.[gig.platform]?.icon && (
- <img
- src={gig.gig_type.display[gig.platform].icon}
- alt="gig type icon"
- width={34}
- height={34}
- style={{ borderRadius: 6, background: '#232136' }}
- />
- )}
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 32,
- fontWeight: 600,
- textTransform: 'uppercase',
- letterSpacing: 0.5,
- }}
- >
- {gig.gig_type?.display?.[gig.platform]?.label || ''}
- </span>
- </div>
- </div>
- </div>
- </div>
- {/* Spacer for visible gap */}
- <div style={{ width: '10%' }} />
- {/* Right: Embedded Content (post/profile/channel) if present */}
- <div
- style={{
- width: '45%',
- borderRadius: 16,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-start',
- justifyContent: 'flex-start',
- boxSizing: 'border-box',
- gap: 0,
- padding: '8px 0 8px 8px',
- marginTop: 0,
- height: '100%',
- // background: 'red',
- }}
- >
- {renderEmbeddedContent(gig)}
- {/* OFFER ENDS now at the bottom right */}
- <div
- style={{
- marginTop: 85,
- // background: 'blue',
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'flex-end',
- minWidth: 0,
- textAlign: 'right',
- width: '100%',
- }}
- >
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 36,
- fontWeight: 900,
- marginBottom: 10,
- letterSpacing: 0.5,
- textShadow:
- '1px 0 #fff, -1px 0 #fff, 0 1px #fff, 0 -1px #fff',
- }}
- >
- OFFER ENDS
- </span>
- <span
- style={{
- color: '#e5e7eb',
- fontSize: 32,
- fontWeight: 600,
- letterSpacing: 0.5,
- }}
- >
- {new Date(gig.end_time_ms).toLocaleString('en-US', {
- month: 'short',
- day: 'numeric',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- hour12: true,
- })}
- </span>
- </div>
- </div>
- </>
- )}
- </div>
- </div>
- </div>
- </div>
- ),
- {
- width,
- height,
- },
- );
- }
Advertisement
Add Comment
Please, Sign In to add comment