Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Static default frame #6

Merged
merged 2 commits into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 36 additions & 14 deletions src/lib/components/Seo/seo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,35 @@

$: ({ ids, ogImage, seoTitle, seoDescription } = hydrate(metadata))

function frameHtml(frame: Partial<Frame>) {
function frameHtml(frame: Partial<Frame>, ogImage: string, route: string) {
const action = routeAction()
const buttons: Frame['buttons'] = frame.buttons || [
{ label: '🔄 Refresh latest', action: 'post' },
{ label: `🔄 Refresh ${action}`, action: 'post' },
]
// we don't include tokenId in the frame context because we don't want to
// refresh the same token over and over
const { tokenId, ...state } = context

// token pages are mostly static, so we can use the dynamic image
// everything else uses our placeholder static image and will rely on the refresh button
const image = imageUrl(true)

return getFrameHtmlHead({
version: 'vNext',
postUrl: `${baseUrl}/frame?${new URLSearchParams({ context: JSON.stringify(state) }).toString()}`,
image: ogImage,
postUrl: `${baseUrl}/frame?${new URLSearchParams({ action, context: JSON.stringify(state) }).toString()}`,
image,
ogImage,
buttons,
...frame,
})

function routeAction() {
if (route === '/') return 'latest POAP'
if (route === '/token') return 'POAP'
if (route === '/account') return 'account POAPs'
if (route === '/event') return 'event POAPs'
return 'latest'
}
}

function hydrate(metadata?: POAPEventMetadata | POAPEventMetadata[]) {
Expand All @@ -52,15 +66,6 @@
seoDescription: truncateText(composeDescription(metdataArray), 155),
}

function imageUrl() {
const at = new Date().getTime()
if (context.tokenId) return `${baseUrl}/og/token/${context.tokenId}?at=${at}`
if (context.eventIds) return `${baseUrl}/og/event/${context.eventIds.join(',')}?at=${at}`
if (context.account) return `${baseUrl}/og/account/${context.account}?at=${at}`

return `${baseUrl}/images/twitter-card.png`
}

function composeTitle(metadata: POAPEventMetadata[] | undefined) {
if (!metadata || metadata.length === 0) return title
if (metadata.length === 1) {
Expand All @@ -83,6 +88,23 @@
return text.length > length ? `${text.slice(0, length - 1)}…` : text
}
}

function imageUrl(_static = false) {
const at = new Date().getTime()
const params = new URLSearchParams({
at: at.toString(),
})
if (_static) {
params.set('static', 'true')
}
const search = params.toString()

if (context.tokenId) return `${baseUrl}/og/token/${context.tokenId}?${search}`
if (context.eventIds) return `${baseUrl}/og/event/${context.eventIds.join(',')}?${search}`
if (context.account) return `${baseUrl}/og/account/${context.account}?${search}`

return `${baseUrl}/images/twitter-card.png`
}
</script>

<svelte:head>
Expand All @@ -95,5 +117,5 @@
<meta name="twitter:description" content={seoDescription} />
<meta name="twitter:image" content={ogImage} />
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html frameHtml(frame)}
{@html frameHtml(frame, ogImage, route)}
</svelte:head>
3 changes: 2 additions & 1 deletion src/routes/frame/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { RequestHandler } from './$types'
export const POST: RequestHandler = async ({ request, url, fetch }) => {
const baseUrl = import.meta.env.DEV ? 'http://localhost:5173' : 'https://vpoap.vercel.app'
const context = JSON.parse(url.searchParams.get('context') || '{}') as SeoContext
const action = url.searchParams.get('action') || 'latest'
const body = (await request.json()) as FrameActionPayload

const { isValid, message } = await validateFrameMessage(body)
Expand All @@ -19,7 +20,7 @@ export const POST: RequestHandler = async ({ request, url, fetch }) => {
version: 'vNext',
buttons: [
{
label: '🔄 Refresh latest',
label: `🔄 Refresh ${action}`,
action: 'post',
},
],
Expand Down
5 changes: 3 additions & 2 deletions src/routes/og/account/[address]/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fetchLatestAccountPOAPToken } from '$lib/server/poap'
import { tokenResponse } from '../../token/[id]/tokenResponse'
import type { RequestHandler } from './$types'

export const GET: RequestHandler = async ({ params, fetch }) => {
export const GET: RequestHandler = async ({ params, url, fetch }) => {
const address = await fetchReverseENS(params.address)
if (!address) throw error(422, `Invalid address: ${params.address}`)

Expand All @@ -28,5 +28,6 @@ export const GET: RequestHandler = async ({ params, fetch }) => {
const tokenId = data.account.tokens[0]?.id
if (!tokenId) throw error(404, 'POAP token not found for account')

return tokenResponse(tokenId, fetch)
const _static = url.searchParams.get('static') === 'true'
return tokenResponse(tokenId, fetch, _static)
}
5 changes: 3 additions & 2 deletions src/routes/og/event/[id]/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { fetchLatestEventPOAPTokens } from '$lib/server/poap'
import { tokenResponse } from '../../token/[id]/tokenResponse'
import type { RequestHandler } from './$types'

export const GET: RequestHandler = async ({ params, fetch }) => {
export const GET: RequestHandler = async ({ params, url, fetch }) => {
const ids = params.id
.split(',')
.map((id) => Number(id.trim()))
Expand All @@ -19,5 +19,6 @@ export const GET: RequestHandler = async ({ params, fetch }) => {
.sort((a, b) => Number(b.created) - Number(a.created))[0]?.id
if (!tokenId) throw error(404, 'POAP token not found for events')

return tokenResponse(tokenId, fetch)
const _static = url.searchParams.get('static') === 'true'
return tokenResponse(tokenId, fetch, _static)
}
5 changes: 3 additions & 2 deletions src/routes/og/token/[id]/+server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { tokenResponse } from './tokenResponse'
import type { RequestHandler } from './$types'

export const GET: RequestHandler = ({ params: { id }, fetch }) => {
return tokenResponse(id, fetch, true)
export const GET: RequestHandler = ({ params: { id }, url, fetch }) => {
const _static = url.searchParams.get('static') === 'true'
return tokenResponse(id, fetch, _static)
}
10 changes: 8 additions & 2 deletions src/routes/og/token/[id]/debug/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import { loadTokenData } from '../../../../token/[id]/data'
import backgroundImage from '../og-background.png?arraybuffer'
import type { PageServerLoad } from './$types'

export const load: PageServerLoad = async ({ fetch, params }) => {
export const load: PageServerLoad = async ({ fetch, url, params }) => {
const data = await loadTokenData(params.id, fetch)

if (!data.token) {
throw error(404, 'Token not found')
}

const _static = url.searchParams.has('static')
const { token, metadata, ens } = data

if (_static) {
ens.avatar = undefined
token.owner.id = '0x0000000000000000000000000000000000000000'
}

const avatar =
ens.avatar ||
renderIcon(
Expand All @@ -33,5 +38,6 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
ens,
avatar,
background,
isStatic: _static,
}
}
93 changes: 57 additions & 36 deletions src/routes/og/token/[id]/og-token.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
* @type {string}
*/
export let background
/**
* @type {boolean}
*/
export let isStatic = false
</script>

<div
Expand Down Expand Up @@ -90,40 +94,50 @@
style:text-overflow="ellipsis"
style:white-space="nowrap"
>
{metadata.name}
{#if isStatic}
Visual POAP Feed
{:else}
{metadata.name}
{/if}
</h2>
</div>
<div style:flex="1 1 0%" style:display="flex" style:overflow="hidden">
<p style:margin="0" style:overflow="hidden" style:white-space="pre-wrap">
{metadata.description.replace(/\r?\n/g, ' ')}
</p>
</div>
<div
style:display="flex"
style:align-items="center"
style:justify-content="space-between"
style:color="rgb(67, 56, 202)"
style:font-family="monospace"
>
<p style:margin="4px 0">
Token
<span style:margin-left="4px" style:font-family="monospace">
#{token.id}
</span>
</p>
<p style:margin="0">
Mint
<span style:margin-left="4px" style:font-family="monospace">
#{token.mintOrder}
</span>
</p>
<p style:margin="0">
Event
<span style:margin-left="4px" style:font-family="monospace">
#{token.event.id}
</span>
{#if isStatic}
Hit the refresh button to watch live POAPS!
{:else}
{metadata.description.replace(/\r?\n/g, ' ')}
{/if}
</p>
</div>
{#if !isStatic}
<div
style:display="flex"
style:align-items="center"
style:justify-content="space-between"
style:color="rgb(67, 56, 202)"
style:font-family="monospace"
>
<p style:margin="4px 0">
Token
<span style:margin-left="4px" style:font-family="monospace">
#{token.id}
</span>
</p>
<p style:margin="0">
Mint
<span style:margin-left="4px" style:font-family="monospace">
#{token.mintOrder}
</span>
</p>
<p style:margin="0">
Event
<span style:margin-left="4px" style:font-family="monospace">
#{token.event.id}
</span>
</p>
</div>
{/if}
<div style:display="flex" style:align-items="center" style:justify-content="space-between">
<div
style:display="flex"
Expand All @@ -135,6 +149,7 @@
<p style:margin="0" style:color="rgb(67, 56, 202)" style:font-size="14px">
{token.owner.id}
</p>

<span
style:margin="0"
style:background-color="rgb(64, 64, 64)"
Expand All @@ -146,15 +161,21 @@
style:font-size="12px"
style:line-height="16px"
>
{token.owner.tokensOwned}
{#if isStatic}
{:else}
{token.owner.tokensOwned}
{/if}
</span>
</div>
<p style:margin="0" style:text-align="right" style:font-size="14px">
{formatDistanceToNow(Number(token.created) * 1000, {
includeSeconds: true,
addSuffix: true,
})}
</p>
{#if !isStatic}
<p style:margin="0" style:text-align="right" style:font-size="14px">
{formatDistanceToNow(Number(token.created) * 1000, {
includeSeconds: true,
addSuffix: true,
})}
</p>
{/if}
</div>
</div>
<div
Expand All @@ -174,7 +195,7 @@
style:height="214px"
style:border-radius="9999px"
style:object-fit="scale-down"
src={metadata.image_url}
src={isStatic ? 'https://poap.xyz/apple-touch-icon.png' : metadata.image_url}
alt="{metadata.name} POAP image"
/>
</div>
Expand Down
11 changes: 8 additions & 3 deletions src/routes/og/token/[id]/tokenResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import backgroundImage from './og-background.png?arraybuffer'
import OgToken from './og-token.svelte'
import { svelteToPngResponse } from './svelte-to-png'

export async function tokenResponse(id: string, fetch: Fetch, disableDefaultAvatarUrl = false) {
export async function tokenResponse(id: string, fetch: Fetch, _static = false) {
try {
const props = await loadTokenData(id, fetch, disableDefaultAvatarUrl)
const props = await loadTokenData(id, fetch, true)
if (!props.token) throw error(404, 'Token not found')

if (_static) {
props.ens.avatar = undefined
props.token.owner.id = '0x0000000000000000000000000000000000000000'
}

const avatar =
props.ens.avatar ||
renderIcon(
Expand All @@ -30,7 +35,7 @@ export async function tokenResponse(id: string, fetch: Fetch, disableDefaultAvat

return await svelteToPngResponse(
OgToken,
{ ...props, avatar, background },
{ ...props, avatar, background, isStatic: _static },
{
width: 1200,
height: 630,
Expand Down
Loading