Skip to content

Commit

Permalink
Merge pull request #198 from poap-xyz/release/v1.12.0
Browse files Browse the repository at this point in the history
Release v1.12.0
  • Loading branch information
jm42 authored Apr 28, 2024
2 parents d40f818 + afab65f commit a2a5777
Show file tree
Hide file tree
Showing 18 changed files with 785 additions and 275 deletions.
49 changes: 49 additions & 0 deletions netlify/components/EventHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'https://esm.sh/react'
import { formatDate } from '../utils/date.js'
import { encodeLocation } from '../utils/event.js'

export function EventHeader({ event }) {
const eventLocation = encodeLocation(event)
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<div
style={{
display: 'flex',
fontFamily: 'Rubik-Black',
fontWeight: 900,
fontStyle: 'normal',
fontSize: '22px',
textTransform: 'uppercase',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}}
>
{event.name}
</div>
<div
style={{
display: 'flex',
fontSize: '14px',
marginTop: '-5px',
}}
>
{formatDate(event.start_date)}
</div>
{eventLocation && (
<div
style={{
display: 'flex',
fontSize: '14px',
}}
>
{eventLocation}
</div>
)}
</div>
)
}
42 changes: 42 additions & 0 deletions netlify/components/ShadowText.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'https://esm.sh/react'

export function ShadowText({ text, small }) {
return (
<div
style={{
display: 'flex',
margin: '0 auto',
}}
>
<div
style={{
display: 'flex',
position: 'relative',
marginTop: '-5px',
whiteSpace: 'nowrap',
fontFamily: 'Rubik-Black',
fontWeight: 900,
fontStyle: 'normal',
fontSize: small ? '28px' : '42px',
letterSpacing: small ? '-2px' : '-3px',
}}
>
<div
style={{
position: 'absolute',
color: '#473e6b',
transform: small ? 'translate(-1px, 2px)' : 'translate(-2px, 3px)',
}}
>{text}</div>
<div
style={{
position: 'relative',
stroke: '#5e58a5',
color: '#eac9f8',
strokeWidth: small ? '2px' : '4px',
}}
>{text}</div>
</div>
</div>
)
}
136 changes: 136 additions & 0 deletions netlify/components/Stats.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React from 'https://esm.sh/react'
import { ShadowText } from './ShadowText.jsx'

export function Stats({ supply, reservations, moments, collections }) {
const total = supply + reservations

const highlighted = {
text: String(total),
title: `collector${total === 0 ? '' : 's'}`,
}

const stats = []

if (total !== supply) {
stats.push({
text: String(supply),
title: `mint${supply === 0 ? '' : 's'}`,
})
stats.push({
text: String(reservations),
title: `reservation${reservations === 0 ? '' : 's'}`,
})
}

if (moments && moments > 0) {
stats.push({
text: String(moments),
title: `moment${moments === 0 ? '' : 's'}`,
})
}

if (collections && collections > 0) {
stats.push({
text: String(collections),
title: `collection${collections === 0 ? '' : 's'}`,
})
}

return (
<div
style={{
display: 'flex',
border: '4px solid #efeeff',
borderRadius: '16px',
marginTop: '4px',
// backgroundColor: 'white',
}}
>
<div
style={{
display: 'flex',
margin: '-4px',
}}
>
<div
style={{
display: 'flex',
padding: '4px 5px 0 5px',
backgroundColor: '#efeeff',
border: '4px solid #efeeff',
borderRadius: '16px',
alignSelf: 'center',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
textAlign: 'center',
minWidth: '60px',
gap: '1px',
}}
>
{ShadowText({ text: highlighted.text, small: false })}
<div
style={{
margin: '0 auto',
backgroundColor: 'white',
padding: '1px 3px',
borderRadius: '7px',
whiteSpace: 'nowrap',
color: 'black',
fontFamily: 'Rubik',
fontSize: '16px',
fontWeight: 500,
fontStyle: 'normal',
}}
>
{highlighted.title}
</div>
</div>
</div>
{stats.map((stat) => (
<div
style={{
display: 'flex',
marginTop: '8px',
padding: '4px 0',
border: '4px solid transparent',
borderRadius: '16px',
alignSelf: 'center',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
textAlign: 'center',
minWidth: '60px',
gap: '1px',
}}
>
{ShadowText({ text: stat.text, small: true })}
<div
style={{
margin: '0 auto',
backgroundColor: 'white',
padding: '1px 3px',
borderRadius: '7px',
whiteSpace: 'nowrap',
color: 'black',
fontFamily: 'Rubik',
fontSize: '14px',
fontWeight: 500,
fontStyle: 'normal',
}}
>
{stat.title}
</div>
</div>
</div>
))}
</div>
</div>
)
}
123 changes: 25 additions & 98 deletions netlify/edge-functions/event.js
Original file line number Diff line number Diff line change
@@ -1,90 +1,27 @@
import axios from 'https://esm.sh/axios'
import dayjs from 'https://esm.sh/dayjs'
import localizedFormat from 'https://esm.sh/dayjs/plugin/localizedFormat'
import { getEnv } from '../loaders/env.js'
import { getEventInfo } from '../loaders/api.js'
import { escapeHtml, replaceMeta } from '../utils/html.js'
import { encodeEvent, parseEventId } from '../utils/event.js'
import { appendFrame } from '../utils/frame.js'

dayjs.extend(localizedFormat)

const FAMILY_URL = 'https://poap.family'
const FAMILY_API_URL = 'https://api.poap.family'

function getQueryString(requestUrl) {
const searchParams = new URL(requestUrl).searchParams.toString()
return searchParams ? `?${searchParams}` : ''
}

function getEventId(requestUrl) {
function parseRequestUrl(requestUrl) {
const url = new URL(requestUrl)
const [, rawEventId] = url.pathname.match(/event\/([^/]+)/)
const eventId = parseInt(rawEventId)
if (isNaN(eventId)) {
throw new Error(`Event invalid Id param`)
}
return eventId
}

async function getEventInfo(eventId) {
const response = await axios.get(`${FAMILY_API_URL}/event/${eventId}?description=true&metrics=true&fresh=true`)
const event = response.data
if (
typeof event !== 'object' ||
!('event' in event) ||
typeof event.event !== 'object' ||
!('owners' in event) ||
!Array.isArray(event.owners) ||
!('metrics' in event) ||
!('emailReservations' in event.metrics) ||
typeof event.metrics.emailReservations !== 'number'
) {
throw new Error(`Event ${eventId} invalid response`)
}
return {
event: event.event,
supply: event.owners.length,
emailReservations: event.metrics.emailReservations,
}
}

function replaceMeta(html, title, description, image, url) {
return html
.replace(/<title>([^<]+)<\/title>/, `<title>$1: ${title}</title>`)
.replace(/<meta property="([^:]+):title" content="[^"]+" ?\/>/g, `<meta property="$1:title" content="${title}"/>`)
.replace(/<meta name="description" content="[^"]+" ?\/>/, `<meta name="description" content="${description}"/>`)
.replace(/<meta property="([^:]+):description" content="[^"]+" ?\/>/g, `<meta property="$1:description" content="${description}"/>`)
.replace(/<meta property="([^:]+):image" content="[^"]+" ?\/>/g, `<meta property="$1:image" content="${image}"/>`)
.replace(/<meta property="([^:]+):url" content="[^"]+" ?\/>/g, `<meta property="$1:url" content="${url}"/>`)
}

function escapeHtml(str) {
return str
.replace(/(\r\n|\n|\r)/gm, '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;')
}

function truncate(str, n = 90) {
const p = str.split('\n').shift()
if (p && p.length <= n) {
return p
}
if (str.length > n) {
return `${str.substring(0, n)}...`
}
return str
const searchParams = url.searchParams.toString()
return [rawEventId, searchParams ? `?${searchParams}` : '']
}

export default async function handler(request, context) {
const eventId = getEventId(request.url)
const queryString = getQueryString(request.url)

const response = await context.next()
const html = await response.text()

const [rawEventId, queryString] = parseRequestUrl(request.url)
const eventId = parseEventId(rawEventId)
const env = getEnv(context)

let eventInfo
try {
eventInfo = await getEventInfo(eventId)
eventInfo = await getEventInfo(eventId, env)
} catch (err) {
if (err?.response?.status === 404) {
return new Response(html, {
Expand All @@ -102,29 +39,19 @@ export default async function handler(request, context) {
})
}

const title = escapeHtml(eventInfo.event.name)
const description = escapeHtml(
`[ ${eventInfo.supply} + ${eventInfo.emailReservations} ] ` +
`${dayjs(eventInfo.event.start_date).format('ll')}` +
`${eventInfo.event.city && eventInfo.event.country
? ` | ${eventInfo.event.city}, ${eventInfo.event.country}`
: ''
}` +
`${eventInfo.event.description && typeof eventInfo.event.description === 'string'
? ` | ${truncate(eventInfo.event.description)}`
: ''
}`
)
const image = eventInfo.event.image_url
const url = `${FAMILY_URL}/event/${eventId}${queryString}`

return new Response(
replaceMeta(
html,
title,
description,
image,
url
appendFrame(
replaceMeta(
html,
escapeHtml(eventInfo.event.name),
escapeHtml(encodeEvent(eventInfo)),
`${eventInfo.event.image_url}?size=large`,
`${env.FAMILY_URL}/event/${eventId}${queryString}`
),
env,
[eventId],
eventInfo.ts,
`${env.FAMILY_URL}/event/${eventId}`
),
response
)
Expand Down
Loading

0 comments on commit a2a5777

Please sign in to comment.