Feat: events index (#28975)

* events page

* events

* add spacing to on demand section

* sort events

* cleanup

* keep on demand open for registration

* fix events sitemap gen

* change titles

* add event

* add event

* remove fixed witdh

* text-nowrap

* reduce gaps

* watch now
This commit is contained in:
Francesco Sansalvadore authored and GitHub committed 2024-08-29 22:39:14 +00:00
1 parent 6f3f8ac494
commit 36938fb06a
18 files changed
+827 -66

No files matched your search

@@ -4,7 +4,7 @@ meta_title: 'Scale to Billions: Generative AI/Humata | 21 Aug 2024 | Webinar'
subtitle: 'Quickly and cost effectively build Generative AI applications that you can use to chat with your company’s documents.'
meta_description: 'Quickly and cost effectively build Generative AI applications that you can use to chat with your company’s documents.'
type: 'webinar'
image: 'webinars/scale-to-billions-generative-ai-humata/og.png'
onDemand: true
date: '2024-08-21T10:00:00.000-07:00'
timezone: 'America/Los_Angeles'
duration: '45 mins'
@@ -21,7 +21,7 @@ main_cta:
{
url: 'https://zoom.us/webinar/register/WN_cTcp6uv4S2OI63jDrOb1Ng',
target: '_blank',
label: 'Register now',
label: 'Watch now',
}
speakers: 'gregnr,khajvandi'
---
@@ -0,0 +1,13 @@
---
title: 'Supabase Launch Week 12 Meetup: Tenerife, España'
subtitle: 'Join the community @ Tándem & Coworking - La Orotava, Canarias'
type: 'meetup'
onDemand: false
disable_page_build: true
link: { href: https://lu.ma/5219ibrl, target: '_blank' }
date: '2024-08-30T10:00:00.000+01:00'
timezone: 'Atlantic/Canary'
duration: '2 hours'
categories:
- meetup
---
@@ -0,0 +1,13 @@
---
title: 'hack night v0.19'
subtitle: '​where miami builds cool shit.'
type: 'talk'
onDemand: false
disable_page_build: true
link: { href: https://lu.ma/x62id5w8, target: '_blank' }
date: '2024-09-05T18:00:00.000-04:00'
timezone: 'America/New_York'
duration: '5 hours'
categories:
- talk
---
@@ -0,0 +1,13 @@
---
title: 'Supabase Launch Week 12 Meetup: Bucharest, Romania'
subtitle: 'Join the community in Romania'
type: 'meetup'
onDemand: false
disable_page_build: true
link: { href: https://lu.ma/x910h8kb, target: '_blank' }
date: '2024-09-05T18:00:00.000+01:00'
timezone: 'Europe/Belgrade'
duration: '2 hours'
categories:
- meetup
---
@@ -0,0 +1,13 @@
---
title: 'Supabase Launch Week 12 Meetup: Ibadan, Nigeria'
subtitle: 'Join the community in Nigeria'
type: 'meetup'
onDemand: false
disable_page_build: true
link: { href: https://lu.ma/x910h8kb, target: '_blank' }
date: '2024-09-07T09:00:00.000+01:00'
timezone: 'Africa/Algiers'
duration: '3 hours'
categories:
- meetup
---
@@ -0,0 +1,108 @@
---
title: 'Supabase Launch Week 12 Hackathon'
meta_title: 'Build an Open Source Project over 10 days. 5 prize categories.'
subtitle: 'Build an Open Source Project over 10 days. 5 prize categories.'
meta_description: 'Build an Open Source Project over 10 days. 5 prize categories.'
type: 'hackathon'
onDemand: false
date: '2024-09-13T10:00:00.000-07:00'
timezone: 'America/Los_Angeles'
duration: '10 days'
categories:
- hackathon
main_cta:
{
url: 'https://hackathon.dev/lw12',
target: '_blank',
label: 'Submit project',
disabled_label: 'Submissions closed',
}
---
The hackathon starts on Friday, September 13th at 09:00 am PT and ends on Sunday, September 22nd at 11:59 pm PT. You could win an extremely limited edition Supabase swag and add your name to the Supabase Hackathon Hall of Fame.
For some inspiration, check out all the [winners from previous hackathons](https://supabase.com/blog/tags/hackathon).
This is the perfect excuse to "Build in a weekend, scale to billions.” Since you retain all the rights to your submissions, you can use the hackathon as a launch pad for your new Startup ideas, side projects, or indie hacks.
## Key Facts
- You have 10 days to build a new **Open Source** project using Supabase in some capacity
- Starting 09:00 am PT Friday, September 13th 2024
- The submission deadline is 11:59 pm Sunday midnight PT, September 22nd 2024
- Enter as an individual or as a team of up to 4 people
- Build whatever you want - a project, app, tool, library. Anything.
- 1-minute video containing the following:
- Name of the project
- Demonstration of the project
- How Supabase is used within the project
- [Here is an example video](https://youtu.be/KaWJQzTTx5k). We do not assess the quality of the video itself. Remember to keep it concise.
## Prizes
There are 5 categories there will be prizes for:
- Best overall project (Swag kit)
- Best use of AI (Swag kit)
- Most fun / best easter egg (Swag kit)
- Most technically impressive (Swag kit)
- Most visually pleasing (Swag kit)
There will be a winner and a runner-up prize for each category. Every team member on winning/runner-up teams gets a Supabase Launch Week swag kit.
## Submission
You should submit your project using [this form](https://hackathon.dev/lw12) before 11:59 pm Sunday midnight PT, September 22nd, 2024.
## Judges
The Supabase team will judge the winners for each category.
We will be looking for:
- creativity/inventiveness
- functions correctly/smoothly
- visually pleasing
- technically impressive
- use of Supabase features
- FUN! 😃
## Rules
- Team size 1-4 (all team members on winning teams will receive a prize)
- You cannot be on multiple teams
- One submission per team
- It's not a requirement to use AI
- All design elements, code, etc., for your project must be created **during** the event
- All entries must be Open Source (link to source code required in entry)
- Must use Supabase in some capacity
- Can be any language or framework
- You must submit before the deadline (no late entries)
- Include a link to a 1-minute demo video
## Resources
### Supabase resources to get you started
- [Real world RAG, the missing pieces for your AI application](https://www.youtube.com/watch?v=ibzlEQmgPPY)
- [Simplify complex SQL queries with Views](https://www.youtube.com/watch?v=IOYFS-2lFjU&t)
- [Row Level Security in Supabase](https://supabase.com/docs/guides/database/postgres/row-level-security)
### Community help
The Supabase Team will be taking part in the Hackathon and you'll find us live to build in our discord all week. Please join us by building in public:
- Text channel: [#hackathon](https://discord.gg/UYyweApy)
- Audio channel: [#hackathon](https://discord.gg/Vj3mTPwH)
If you need help or advice when building, find other people to join your team, or if you just want to chill and watch people build, come and join us!
[Join our Discord](https://discord.supabase.com/)
- [Previous Hackathon Prize Winners](https://supabase.com/blog/tags/hackathon)
![Discord](/images/blog/lw12/hackathon/discord.png)
## Additional Info
- Any intellectual property developed during the hackathon will belong to the team that developed it. We expect that each team will have an agreement between themselves regarding the IP, but this is not required.
- By making a submission, you grant Supabase permission to use screenshots, code snippets, and/or links to your project or content of your README on our Twitter, blog, website, email updates, and in the Supabase discord server. Supabase does not make any claims over your IP.
+3 -3
View File
@@ -50,13 +50,13 @@ const BlogListItem = ({ post }: Props) => {
})}
</div>
{post.categories && (
<div className="hidden xl:flex text-foreground-lighter">
<div className="hidden xl:flex text-foreground-lighter group-hover:text-foreground-light">
{post.categories.map(
(category, i) =>
i === 0 && (
<span
key={category}
className="text-sm border border-muted py-1 px-3 rounded-full text-center w-auto capitalize"
className="text-sm border border-muted group-hover:border-foreground-muted py-1 px-3 rounded-full text-center w-auto capitalize"
>
{category}
</span>
@@ -65,7 +65,7 @@ const BlogListItem = ({ post }: Props) => {
</div>
)}
{post.date && (
<p className="text-foreground-lighter flex-1 lg:text-right w-full">
<p className="text-foreground-lighter group-hover:text-foreground-light flex-1 lg:text-right w-full">
{dayjs(post.date).format('D MMM YYYY')}
</p>
)}
@@ -0,0 +1,69 @@
import React from 'react'
// import authors from 'lib/authors.json'
import Image from 'next/image'
import Link from 'next/link'
// import type Author from '~/types/author'
import type PostTypes from '../../types/post'
import dayjs from 'dayjs'
interface Props {
event: PostTypes
}
const EventGridItem = ({ event }: Props) => {
// const authorArray: string[] | undefined = event.author ? event.author.split(',') : []
// const author = []
// if (authorArray) {
// for (let i = 0; i < authorArray.length; i++) {
// author.push(
// authors.find((authors: Author) => {
// return authors.author_id === authorArray[i]
// })
// )
// }
// }
return (
<Link
href={event.disable_page_build ? event.url : event.path}
className="group inline-block min-w-full p-2 sm:p-4 h-full border border-transparent transition-all hover:bg-surface-200 dark:hover:bg-surface-75 rounded-xl"
>
<div className="flex flex-col space-y-2">
<div className="flex flex-col space-y-1">
<div className="border-default relative mb-3 w-full aspect-[2/1] lg:aspect-[5/3] overflow-hidden rounded-lg border shadow-sm">
{!event.thumb ? (
<div className="bg-background-alternative w-full h-full" />
) : (
<Image
fill
sizes="100%"
quality={100}
src={event.type === 'casestudy' ? event.thumb : `/images/blog/${event.thumb}`}
className="scale-100 object-cover overflow-hidden"
alt={`${event.title} thumbnail`}
/>
)}
</div>
{event.date && (
<div className="text-foreground-lighter flex items-center space-x-1.5 text-sm">
<p>{dayjs(event.date).format('D MMM YYYY')}</p>
{event.readingTime && (
<>
<p>•</p>
<p>{event.readingTime}</p>
</>
)}
</div>
)}
<h3 className="text-foreground max-w-sm text-xl">{event.title}</h3>
<p className="text-foreground-light max-w-sm text-base !mb-0">{event.description}</p>
</div>
</div>
</Link>
)
}
export default EventGridItem
@@ -0,0 +1,82 @@
import authors from 'lib/authors.json'
import Link from 'next/link'
import React from 'react'
import type Author from '~/types/author'
import type PostTypes from '~/types/post'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
import advancedFormat from 'dayjs/plugin/advancedFormat'
import { Badge } from 'ui'
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(advancedFormat)
interface Props {
event: PostTypes
}
const EventListItem = ({ event }: Props) => {
const authorArray: string[] | undefined = event?.author ? event.author.split(',') : []
const author = []
if (authorArray) {
for (let i = 0; i < authorArray.length; i++) {
author.push(
authors.find((authors: Author) => {
return authors.author_id === authorArray[i]
})
)
}
}
return (
<Link
href={event.disable_page_build ? event.link?.href ?? '' : event.path}
target={event.disable_page_build ? event.link?.target ?? '' : '_self'}
className="group flex flex-col lg:grid lg:grid-cols-9 xl:grid-cols-12 w-full py-2 sm:py-4 h-full border-b"
>
<div className="flex w-full lg:col-span-7 xl:col-span-8">
<h3 className="text-foreground text-lg group-hover:underline">{event.title}</h3>
</div>
<div className="lg:col-span-2 xl:col-span-3 flex justify-start items-center lg:grid grid-cols-2 xl:grid-cols-2 gap-2 text-sm">
{event.categories && (
<div className="hidden xl:flex gap-2 text-foreground-lighter flex-grow group-hover:text-foreground-light">
{/* {event.onDemand && (
<Badge className="group-hover:border-foreground-muted capitalize">On Demand</Badge>
)} */}
{event.categories.map(
(category, i) =>
i === 0 && (
<Badge key={category} className="group-hover:border-foreground-muted capitalize">
{category}
</Badge>
)
)}
</div>
)}
{event.date && (
<p className="text-foreground-lighter text-nowrap group-hover:text-foreground-light min-w-40 flex items-center gap-1.5 flex-1 w-full">
{dayjs(event.date)
.tz(event.timezone ?? 'America/Los_Angeles')
.format('DD MMM YYYY')}
<span className="w-px h-[16px] bg-muted" />
<span className="">
{dayjs(event.date)
.tz(event.timezone ?? 'America/Los_Angeles')
.format('hA')}
</span>
<span className="w-px h-[16px] bg-muted" />
{dayjs(event.date)
.tz(event.timezone ?? 'America/Los_Angeles')
.format('z')}
</p>
)}
</div>
</Link>
)
}
export default EventListItem
@@ -0,0 +1,202 @@
import { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useSearchParams } from 'next/navigation'
import { AnimatePresence, motion } from 'framer-motion'
import { startCase } from 'lodash'
import { useKey } from 'react-use'
import { useBreakpoint } from 'common'
import type PostTypes from '~/types/post'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
IconChevronDown,
cn,
} from 'ui'
interface Props {
allEvents: PostTypes[]
events?: PostTypes[]
setEvents: (posts: any) => void
categories: { [key: string]: number }
}
/**
* search via category if no q param
* search via category and reset q param if present
*/
function EventFilters({ allEvents, setEvents, categories }: Props) {
const [category, setCategory] = useState<string>('all')
const [searchTerm, setSearchTerm] = useState<string>('')
const [showSearchInput, setShowSearchInput] = useState<boolean>(false)
const router = useRouter()
const searchParams = useSearchParams()
const q = searchParams?.get('q')
const activeCategory = searchParams?.get('category')
const isMobile = useBreakpoint(1023)
const is2XL = useBreakpoint(1535)
useEffect(() => {
if (!q) {
handlePosts()
}
}, [category])
useEffect(() => {
if (q) {
handleSearchByText(q)
}
}, [q])
const handleReplaceRouter = () => {
if (!searchTerm && category !== 'all') {
router.query.category = category
router.replace(router, undefined, { shallow: true, scroll: false })
}
}
const handlePosts = () => {
handleReplaceRouter()
setEvents(
category === 'all'
? allEvents
: allEvents.filter((event: any) => {
const found = event.categories?.includes(category)
return found
})
)
}
useKey('Escape', () => handleSearchByText(''))
useEffect(() => {
setShowSearchInput(!isMobile)
}, [isMobile])
useEffect(() => {
if (router.isReady && q) {
setSearchTerm(q)
}
if (router.isReady && activeCategory && activeCategory !== 'all') {
setCategory(activeCategory)
}
}, [activeCategory, router.isReady, q])
const handleSearchByText = (text: string) => {
setSearchTerm(text)
searchParams?.has('q') && router.replace('/events', undefined, { shallow: true, scroll: false })
router.replace(`/events?q=${text}`, undefined, { shallow: true, scroll: false })
if (text.length < 1) router.replace('/events', undefined, { shallow: true, scroll: false })
const matches = allEvents.filter((event: any) => {
const found =
event.tags?.join(' ').replaceAll('-', ' ').includes(text.toLowerCase()) ||
event.title?.toLowerCase().includes(text.toLowerCase()) ||
event.author?.includes(text.toLowerCase())
return found
})
setEvents(matches)
}
const handleSetCategory = (category: string) => {
searchTerm && handlePosts()
searchTerm && setSearchTerm('')
setCategory(category)
category === 'all'
? router.replace('/events', undefined, { shallow: true, scroll: false })
: router.replace(`/events?category=${category}`, undefined, {
shallow: true,
scroll: false,
})
}
return (
<div className="flex flex-row items-center justify-between gap-2">
<AnimatePresence mode="wait">
{!showSearchInput && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0, transition: { duration: 0.05 } }}
className="flex lg:hidden"
>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="outline"
size="medium"
iconRight={<IconChevronDown />}
className="w-full min-w-[200px] flex justify-between items-center py-2"
>
{!activeCategory ? 'All Events' : startCase(activeCategory?.replaceAll('-', ' '))}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="start">
{Object.entries(categories).map(([category, count]) => (
<DropdownMenuItem
key={`item-${category}`}
onClick={() => handleSetCategory(category)}
className={cn(
'flex gap-0.5 items-center justify-between',
(category === 'all' && !activeCategory) || category === activeCategory
? 'text-brand-600'
: ''
)}
>
{category === 'all' ? 'All Posts' : startCase(category.replaceAll('-', ' '))}{' '}
<span
className={cn(
'flex items-center justify-center rounded-full bg-border-stronger h-[16px] aspect-square',
count > 9 && 'aspect-auto px-1'
)}
>
{count}
</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</motion.div>
)}
<div className="hidden lg:flex flex-wrap items-center flex-grow gap-2">
{Object.entries(categories).map(([category, count]) => (
<Button
key={category}
type={
category === 'all' && !searchTerm && !activeCategory
? 'default'
: category === activeCategory
? 'default'
: 'outline'
}
onClick={() => handleSetCategory(category)}
size={is2XL ? 'tiny' : 'small'}
className="rounded-full pr-1.5"
iconRight={
<span
className={cn(
'flex items-center justify-center rounded-full bg-surface-100 h-[16px] aspect-square',
count > 9 && 'aspect-auto px-1'
)}
>
{count}
</span>
}
>
{category === 'all' ? 'All' : startCase(category.replaceAll('-', ' '))}{' '}
</Button>
))}
</div>
</AnimatePresence>
</div>
)
}
export default EventFilters
+4
View File
@@ -135,6 +135,10 @@ const footerData = [
text: 'Company',
url: '/company',
},
{
text: 'Events',
url: '/events',
},
{
text: 'General Availability',
url: '/ga',
+21
View File
@@ -87,6 +87,27 @@ async function generate() {
route = `/${blogUrl}/` + substring
}
/**
* Event based urls
* handle removal of dates in filename
*/
if (route.includes(`/${eventsUrl}/`)) {
// remove finelnames with __
if (route.includes(`__`)) return null
/**
* remove directory from route
*/
const _route = route.replace(`/${eventsUrl}/`, '')
/**
* remove the date from the file name
*/
const substring = _route.substring(11)
/**
* reconsruct the route
*/
route = `/${eventsUrl}/` + substring
}
return `
<url>
<loc>${`https://supabase.com${route}`}</loc>
+3 -2
View File
@@ -2,6 +2,7 @@ import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { generateReadingTime } from './helpers'
import { BlogPost } from '../.contentlayer/generated'
type Directories = '_blog' | '_case-studies' | '_customers' | '_alternatives' | '_events'
@@ -34,7 +35,7 @@ export const getSortedPosts = ({
const allPosts = fileNames
.map((filename) => {
const slug =
directory === '_blog'
directory === '_blog' || directory === '_events'
? filename.replace('.mdx', '').substring(FILENAME_SUBSTRING)
: filename.replace('.mdx', '')
@@ -118,7 +119,7 @@ export const getAllPostSlugs = (directory: Directories) => {
...dates,
slug: filename
.replace('.mdx', '')
.substring(directory === '_blog' ? FILENAME_SUBSTRING : 0),
.substring(directory === '_blog' || directory === '_events' ? FILENAME_SUBSTRING : 0),
},
}
})
+6 -4
View File
@@ -3,7 +3,7 @@ import 'config/code-hike.scss'
import '../styles/index.css'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
import { AuthProvider, ThemeProvider, useTelemetryProps, useThemeSandbox } from 'common'
import { AuthProvider, IS_PROD, ThemeProvider, useTelemetryProps, useThemeSandbox } from 'common'
import { DefaultSeo } from 'next-seo'
import { AppProps } from 'next/app'
import Head from 'next/head'
@@ -18,7 +18,7 @@ import MetaFaviconsPagesRouter, {
DEFAULT_FAVICON_THEME_COLOR,
} from 'common/MetaFavicons/pages-router'
import { WwwCommandMenu } from '~/components/CommandMenu'
import { API_URL, APP_NAME, DEFAULT_META_DESCRIPTION } from '~/lib/constants'
import { API_URL, APP_NAME, DEFAULT_META_DESCRIPTION, IS_PREVIEW } from '~/lib/constants'
import { post } from '~/lib/fetchWrapper'
import supabase from '~/lib/supabase'
import useDarkLaunchWeeks from '../hooks/useDarkLaunchWeeks'
@@ -27,6 +27,8 @@ export default function App({ Component, pageProps }: AppProps) {
const router = useRouter()
const telemetryProps = useTelemetryProps()
const { consentValue, hasAcceptedConsent } = useConsent()
const IS_DEV = !IS_PROD && !IS_PREVIEW
const blockEvents = IS_DEV || !hasAcceptedConsent
useThemeSandbox()
@@ -43,7 +45,7 @@ export default function App({ Component, pageProps }: AppProps) {
}
useEffect(() => {
if (!hasAcceptedConsent) return
if (blockEvents) return
function handleRouteChange(url: string) {
handlePageTelemetry(url)
@@ -57,7 +59,7 @@ export default function App({ Component, pageProps }: AppProps) {
}, [router.events, consentValue])
useEffect(() => {
if (!hasAcceptedConsent) return
if (blockEvents) return
/**
* Send page telemetry on first page load
*/
+85 -49
View File
@@ -7,7 +7,12 @@ import { MDXRemote } from 'next-mdx-remote'
import { NextSeo } from 'next-seo'
import dayjs from 'dayjs'
import matter from 'gray-matter'
import { VideoCameraIcon } from '@heroicons/react/solid'
import {
DesktopComputerIcon,
VideoCameraIcon,
MicrophoneIcon,
HandIcon,
} from '@heroicons/react/solid'
import authors from 'lib/authors.json'
import { isNotNullOrUndefined } from '~/lib/helpers'
@@ -30,16 +35,18 @@ import ShareArticleActions from '~/components/Blog/ShareArticleActions'
import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png'
import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png'
import { ChevronLeft } from 'lucide-react'
dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(advancedFormat)
type EventType = 'webinar' | 'launch_week' | 'conference'
type EventType = 'webinar' | 'meetup' | 'conference' | 'talk' | 'hackathon' | 'launch_week'
type CTA = {
url: string
label?: string
disabled_label?: string
target?: '_blank' | '_self'
}
@@ -57,6 +64,8 @@ interface EventData {
description: string
type: EventType
company?: CompanyType
onDemand?: boolean
disable_page_build?: boolean
duration?: string
timezone?: string
tags?: string[]
@@ -109,6 +118,12 @@ export const getStaticProps: GetStaticProps<EventPageProps, Params> = async ({ p
const postContent = await getPostdata(filePath, '_events')
const { data, content } = matter(postContent) as unknown as MatterReturn
if (data.disable_page_build) {
return {
notFound: true,
}
}
const mdxSource: any = await mdxSerialize(content)
return {
@@ -132,7 +147,7 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
})
.filter(isNotNullOrUndefined)
const IS_REGISTRATION_OPEN = Date.parse(event.date) > Date.now()
const IS_REGISTRATION_OPEN = event.onDemand || Date.parse(event.date) > Date.now()
const meta = {
title: event.meta_title ?? event.title,
@@ -141,9 +156,12 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
}
const eventIcons = {
webinar: (props: any) => <VideoCameraIcon {...props} />,
conference: (props: any) => <VideoCameraIcon {...props} />,
hackathon: (props: any) => <DesktopComputerIcon {...props} />,
launch_week: (props: any) => <VideoCameraIcon {...props} />,
meetup: (props: any) => <HandIcon {...props} />,
talk: (props: any) => <MicrophoneIcon {...props} />,
webinar: (props: any) => <VideoCameraIcon {...props} />,
}
const Icon = eventIcons[event.type]
@@ -198,6 +216,18 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
}}
/>
<DefaultLayout>
<div className="flex flex-col w-full bg-alternative border-b border-muted">
<SectionContainer className="!py-2 flex items-start">
<Link
href="/events"
className="text-foreground-lighter hover:text-foreground flex !m-0 !p-0 !leading-3 gap-1 cursor-pointer items-center text-sm transition"
>
<ChevronLeft className="w-4 h-4" />
All Events
</Link>
</SectionContainer>
</div>
<div className="flex flex-col w-full">
<header className="relative bg-alternative w-full overflow-hidden">
<NextImage
@@ -253,7 +283,9 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
? event.main_cta?.label
? event.main_cta?.label
: 'Register to this event'
: 'Registrations are closed'}
: event.main_cta?.disabled_label
? event.main_cta?.disabled_label
: 'Registrations are closed'}
</Link>
</Button>
</div>
@@ -286,49 +318,51 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
</SectionContainer>
</header>
<SectionContainer className="grid lg:grid-cols-3 gap-12 !py-10 md:!py-16">
<div className="order-first lg:col-span-full flex items-center gap-4 md:gap-6 lg:mb-4">
<figure className="h-6">
<NextImage
src={supabaseLogoWordmarkLight}
width={160}
height={30}
alt="Supabase Logo"
className="object-contain dark:hidden"
priority
/>
<NextImage
src={supabaseLogoWordmarkDark}
width={160}
height={30}
alt="Supabase Logo"
className="object-contain hidden dark:block"
priority
/>
</figure>
<XIcon className="w-4 h-4 text-foreground-lighter" />
<Link
href={event.company?.website_url ?? '#'}
target="_blank"
className="h-5 aspect-[9/1] transition-opacity opacity-100 hover:opacity-90"
>
<NextImage
src={`/images/events/` + event.company?.logo ?? ''}
alt={`${event.company?.name} Logo`}
fill
sizes="100%"
className="!relative object-contain object-left hidden dark:block"
priority
/>
<NextImage
src={`/images/events/` + event.company?.logo_light ?? ''}
alt={`${event.company?.name} Logo`}
fill
sizes="100%"
className="!relative object-contain object-left dark:hidden"
priority
/>
</Link>
</div>
{event.company && (
<div className="order-first lg:col-span-full flex items-center gap-4 md:gap-6 lg:mb-4">
<figure className="h-6">
<NextImage
src={supabaseLogoWordmarkLight}
width={160}
height={30}
alt="Supabase Logo"
className="object-contain dark:hidden"
priority
/>
<NextImage
src={supabaseLogoWordmarkDark}
width={160}
height={30}
alt="Supabase Logo"
className="object-contain hidden dark:block"
priority
/>
</figure>
<XIcon className="w-4 h-4 text-foreground-lighter" />
<Link
href={event.company?.website_url ?? '#'}
target="_blank"
className="h-5 aspect-[9/1] transition-opacity opacity-100 hover:opacity-90"
>
<NextImage
src={`/images/events/` + event.company?.logo ?? ''}
alt={`${event.company?.name} Logo`}
fill
sizes="100%"
className="!relative object-contain object-left hidden dark:block"
priority
/>
<NextImage
src={`/images/events/` + event.company?.logo_light ?? ''}
alt={`${event.company?.name} Logo`}
fill
sizes="100%"
className="!relative object-contain object-left dark:hidden"
priority
/>
</Link>
</div>
)}
<main className="lg:col-span-2">
<div className="prose prose-docs">
<h2 className="text-foreground-light text-sm font-mono uppercase">
@@ -353,7 +387,9 @@ const EventPage = ({ event }: InferGetStaticPropsType<typeof getStaticProps>) =>
? event.main_cta?.label
? event.main_cta?.label
: 'Register now'
: 'Registrations are closed'}
: event.main_cta?.disabled_label
? event.main_cta?.disabled_label
: 'Registrations are closed'}
</Link>
</Button>
</aside>
+157
View File
@@ -0,0 +1,157 @@
import { useState } from 'react'
import { useRouter } from 'next/router'
import { NextSeo } from 'next-seo'
import { getSortedPosts } from '~/lib/posts'
import { cn } from 'ui'
import DefaultLayout from '~/components/Layouts/Default'
import EventListItem from '~/components/Events/EventListItem'
import EventsFilters from '~/components/Events/EventsFilters'
import SectionContainer from '~/components/Layouts/SectionContainer'
import type BlogPost from '~/types/post'
interface Props {
events: BlogPost[]
onDemandEvents: BlogPost[]
categories: { [key: string]: number }
}
function Events({ events: allEvents, onDemandEvents, categories }: Props) {
const [events, setEvents] = useState(allEvents)
const router = useRouter()
const meta_title = 'Supabase Events: webinars, talks, hackathons, and meetups'
const meta_description = 'Join Supabase and the open-source community at the upcoming events.'
return (
<>
<NextSeo
title={meta_title}
description={meta_description}
openGraph={{
title: meta_title,
description: meta_description,
url: `https://supabase.com/${router.pathname}`,
images: [
{
url: `https://supabase.com/images/og/supabase-og.png`,
},
],
}}
additionalLinkTags={[
{
rel: 'alternate',
type: 'application/rss+xml',
href: `https://supabase.com/rss.xml`,
},
]}
/>
<DefaultLayout className="min-h-[80dvh]">
<SectionContainer className="!py-8 lg:!py-16">
<h1 className="h1">
<span className="sr-only">Supabase</span> Events
</h1>
<p className="text-foreground-light">Join us at the following upcoming events</p>
</SectionContainer>
<SectionContainer className="!py-0">
<EventsFilters
allEvents={allEvents}
events={events}
setEvents={setEvents}
categories={categories}
/>
<ol
className={cn(
'grid -mx-2 sm:-mx-4 py-6 lg:py-6 grid-cols-1',
!events?.length && 'mx-0 sm:mx-0'
)}
>
{events?.length ? (
events
?.sort((a, b) => new Date(a.date!).getTime() - new Date(b.date!).getTime())
.map((event: BlogPost, idx: number) => (
<div
className="col-span-12 px-2 sm:px-4 [&_a]:last:border-none opacity-0 !scale-100 animate-fade-in"
key={`${event.title}-upcoming-${idx}`}
>
<EventListItem event={event} />
</div>
))
) : (
<p className="text-sm py-2 sm:py-4 text-lighter col-span-full italic opacity-0 !scale-100 animate-fade-in">
No results found
</p>
)}
</ol>
</SectionContainer>
<SectionContainer>
<div className="pt-8 border-t">
<h2 className="h3">On Demand</h2>
<p className="text-foreground-light">Replay selected events on your schedule</p>
</div>
<ol
className={cn(
'grid -mx-2 sm:-mx-4 py-6 lg:py-6',
'grid-cols-12 lg:gap-4',
!onDemandEvents?.length && 'mx-0 sm:mx-0'
)}
>
{onDemandEvents?.length ? (
onDemandEvents?.map((event: BlogPost, idx: number) => (
<div
className="col-span-12 px-2 sm:px-4 [&_a]:last:border-none opacity-0 !scale-100 animate-fade-in"
key={`${event.title}-upcoming-${idx}`}
>
<EventListItem event={event} />
</div>
))
) : (
<p className="text-sm py-2 sm:py-4 text-lighter col-span-full italic opacity-0 !scale-100 animate-fade-in">
No results found
</p>
)}
</ol>
</SectionContainer>
</DefaultLayout>
</>
)
}
export async function getStaticProps() {
const allEvents = getSortedPosts({
directory: '_events',
runner: '** EVENTS PAGE **',
}) as BlogPost[]
const upcomingEvents = allEvents.filter((event: BlogPost) => new Date(event.date!) >= new Date())
const onDemandEvents = allEvents.filter(
(event: BlogPost) => new Date(event.date!) < new Date() && event.onDemand === true
)
const categories = upcomingEvents.reduce(
(acc: { [key: string]: number }, event: BlogPost) => {
// Increment the 'all' counter
acc.all = (acc.all || 0) + 1
// Increment the counter for each category
event.categories?.forEach((category) => {
acc[category] = (acc[category] || 0) + 1
})
return acc
},
{ all: 0 }
)
return {
props: {
events: upcomingEvents,
onDemandEvents,
categories,
},
}
}
export default Events
+24 -6
View File
@@ -168,6 +168,12 @@
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/events</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/launch-week</loc>
<changefreq>weekly</changefreq>
@@ -210,6 +216,12 @@
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/partners/integrations</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/launch-week/x</loc>
<changefreq>weekly</changefreq>
@@ -222,12 +234,6 @@
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/partners/integrations</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/blog/supabase-alpha-april-2020</loc>
<changefreq>weekly</changefreq>
@@ -1982,6 +1988,12 @@
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/blog/supabase-vercel-partnership</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/customers/berriai</loc>
<changefreq>weekly</changefreq>
@@ -2090,6 +2102,12 @@
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/events/launch-week-12-hackathon</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://supabase.com/alternatives/supabase-vs-auth0</loc>
<changefreq>weekly</changefreq>
+9
View File
@@ -21,6 +21,15 @@ type PostTypes = {
logo?: string
logo_inverse?: string
hideAuthor?: boolean
end_date?: string // for events that span multiple days (not implemented yet)
timezone?: string // utc timezone, e.g. 'America/New_York'. Reference: /apps/studio/components/interfaces/Database/Backups/PITR/PITR.constants.ts
onDemand?: boolean // events that are on-demand following a registration process
disable_page_build?: boolean // when true, we don't build the page and require a custom link
link?: {
href: string
target?: '_blank' | '_self'
label?: string
} // used on event previews to link to a custom event page
}
export default PostTypes