mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
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:
1 parent
6f3f8ac494
commit
36938fb06a
18 files changed
+827
-66
No files matched your search
+2
-2
@@ -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
|
||||
---
|
||||
+13
@@ -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)
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
@@ -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
|
||||
@@ -135,6 +135,10 @@ const footerData = [
|
||||
text: 'Company',
|
||||
url: '/company',
|
||||
},
|
||||
{
|
||||
text: 'Events',
|
||||
url: '/events',
|
||||
},
|
||||
{
|
||||
text: 'General Availability',
|
||||
url: '/ga',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user