Merge branch 'feat/pricing-updates' of github.com:supabase/supabase into feat/pricing-updates

This commit is contained in:
Alaister Young committed 2022-03-26 15:46:51 +01:00
commit fc4f4ce3b7
13 files changed
+281 -234

No files matched your search

+23 -26
View File
@@ -13,15 +13,14 @@ date: '2022-03-08'
toc_depth: 3
---
Data auditing is a system that tracks changes to tables' contents over time.
Data auditing is a system that tracks changes to tables' contents over time.
PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL.
Auditing is particularly useful for historical analysis. To demonstrate, imagine you have a `users` table that tracks when a user is online.
You might add a `status` column which can have one of two values: `online` and `offline`. How would you track how long a user is online for throughout an entire month?
Auditing is particularly useful for historical analysis. To demonstrate, imagine you have a `users` table that tracks when a user is online.
You might add a `status` column which can have one of two values: `online` and `offline`. How would you track how long a user is online for throughout an entire month?
An auditing system would track every change with timestamps, and so you can measure the difference between each timestamp and sum them up for the entire month.
The goals of our auditing solution are:
The goals of our auditing solution are:
- low maintenance
- easy to use
@@ -60,11 +59,11 @@ select * from audit.record_history
```
```markdown
id | record_id | old_record_id | op | ts | table_oid | table_schema | table_name | record | old_record
id | record_id | old_record_id | op | ts | table_oid | table_schema | table_name | record | old_record
----+--------------------------------------+--------------------------------------+--------+-------------------------------------+-----------+--------------+------------+--------------------------+--------------------------
2 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | | INSERT | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "foo"} |
3 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | UPDATE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "bar"} | {"id": 1, "name": "foo"}
4 | | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | DELETE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | | {"id": 1, "name": "bar"}
2 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | | INSERT | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "foo"} |
3 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | UPDATE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | {"id": 1, "name": "bar"} | {"id": 1, "name": "foo"}
4 | | 1ecd5ff0-1b6b-5bc2-ad80-1cb19769c081 | DELETE | Mon Feb 28 18:13:52.698511 2022 PST | 16452 | public | members | | {"id": 1, "name": "bar"}
(3 rows)
```
@@ -74,10 +73,9 @@ Notice that our `record_id` and `old_record_id` stayed constant as we updated th
### Namespace
To quote a tenet from [the zen of python](https://www.python.org/dev/peps/pep-0020/):
To quote a tenet from [the zen of python](https://www.python.org/dev/peps/pep-0020/):
> Namespaces are one honking great idea -- let's do more of those!
>
So first things first, we'll create a separate schema named `audit` to house our auditing entities.
@@ -89,12 +87,12 @@ create schema if not exists audit;
Next, we need a table to track inserts, updates and deletes.
Classically, an audit table's schema mirrors the table being audited and appends some metadata columns like the commit's timestamp. That solution has a few maintenance challenges:
Classically, an audit table's schema mirrors the table being audited and appends some metadata columns like the commit's timestamp. That solution has a few maintenance challenges:
- enabling auditing on a table requires a database migration
- when the source table's schema changes, the audit table's schema must also change
So instead, we'll lean on PostgreSQL's schema-less [`JSONB` data type](https://www.postgresql.org/docs/current/datatype-json.html) to store each record's data in a single column.
So instead, we'll lean on PostgreSQL's schema-less [`JSONB` data type](https://www.postgresql.org/docs/current/datatype-json.html) to store each record's data in a single column.
That approach has the added benefit of allowing us to store multiple tables' audit history in a single audit table.
```sql
@@ -110,23 +108,22 @@ create table audit.record_version(
table_schema name not null, -- audited table's schema name e.g. 'public'
table_name name not null, -- audited table's table name e.g. 'account'
-- record data
record jsonb, -- contents of the new record
record jsonb, -- contents of the new record
old_record jsonb -- previous record contents (for UPDATE/DELETE)
);
```
<details>
<summary>Postgres version compatibility</summary>
<p>
The table above uses PostgreSQL's built-in <a href="https://www.postgresql.org/docs/14/functions-uuid.html">uuid functionality</a>, which is available from version 14.
For backwards compatibility you can use the uuid-ossp extension.
</p>
<p>
<code>create extension if not exists "uuid-ossp";</code>
</p>
<summary>Postgres version compatibility</summary>
<p>
The table above uses PostgreSQL's built-in{' '}
<a href="https://www.postgresql.org/docs/14/functions-uuid.html">uuid functionality</a>, which
is available from version 14. For backwards compatibility you can use the uuid-ossp extension.
</p>
<p>
<code>create extension if not exists "uuid-ossp";</code>
</p>
</details>
### Query Patterns
@@ -134,7 +131,7 @@ An audit log doesn't do us much good if its too slow to query! There are 2 query
**Changes to a Table in a Time Range**
For time slices, we need an index on the `ts` column. Since the table is append-only and the `ts` column is populated by insertion date, our values for `ts` are naturally in ascending order.
For time slices, we need an index on the `ts` column. Since the table is append-only and the `ts` column is populated by insertion date, our values for `ts` are naturally in ascending order.
PostgreSQL's builtin [BRIN index](https://www.postgresql.org/docs/current/brin-intro.html) can leverage that correlation between value and physical location to produce an index that, at scale, is many hundreds of times smaller than the default (BTREE index) with faster lookup times.
@@ -213,7 +210,7 @@ as $$
select
uuid_generate_v5(
'fd62bc3d-8d6e-43c2-919c-802ba3762271',
(
(
jsonb_build_array(to_jsonb($1))
|| jsonb_agg($3 ->> key_)
)::text
+26 -12
View File
@@ -1,4 +1,3 @@
import { Space } from '@supabase/ui'
import authors from 'lib/authors.json'
import React from 'react'
import Image from 'next/image'
@@ -10,7 +9,20 @@ interface Props {
const BlogListItem = ({ post }: Props) => {
// @ts-ignore
const author = post.author ? authors[post.author] : authors['supabase']
const authorArray = post.author && post.author.split(',')
const author = []
if (post.author) {
for (let i = 0; i < authorArray.length; i++) {
author.push(
// @ts-ignore
authors.find((authors: string) => {
// @ts-ignore
return authors.author_id === authorArray[i]
})
)
}
}
return (
<div key={post.slug}>
@@ -39,17 +51,19 @@ const BlogListItem = ({ post }: Props) => {
{post.date && <p className="text-xs text-scale-1100">{post.date}</p>}
<p className="text-base text-scale-1100 max-w-sm">{post.description}</p>
</div>
{author && !post.hideAuthor ? (
<div className="flex items-center">
{author.author_image_url && (
<img src={author.author_image_url} className="rounded-full w-10 mr-4" />
)}
<div className="flex flex-col">
<span className="text-sm m-0 text-scale-1200">{author.author}</span>
<span className="text-xs m-0 text-scale-900">{author.position}</span>
{author.map((author: any) => {
return (
<div className="flex items-center">
{author.author_image_url && (
<img src={author.author_image_url} className="rounded-full w-10 mr-4" />
)}
<div className="flex flex-col">
<span className="text-sm m-0 text-scale-1200">{author.author}</span>
<span className="text-xs m-0 text-scale-900">{author.position}</span>
</div>
</div>
</div>
) : null}
)
})}
</div>
</div>
</a>
+1 -1
View File
@@ -21,7 +21,7 @@ function ExampleCard(props: any) {
<p className="p text-sm">{props.description}</p>
<div>
<img src={props.author_img} className="inline w-6 rounded-full border-scale-500" />
<span className="text-sm ml-2">{props.author}</span>
<span className="text-sm ml-2 text-scale-1200">{props.author}</span>
</div>
</div>
</div>
+1 -1
View File
@@ -2,7 +2,7 @@ function FeatureColumn({ icon, title, text }: any) {
return (
<>
{icon && <div className="mb-2">{icon}</div>}
<h4 className="text-base mb-4">{title}</h4>
<h4 className="text-base text-scale-1200 mb-4">{title}</h4>
<p className="p">{text}</p>
</>
)
+1 -1
View File
@@ -125,7 +125,7 @@ const Footer = () => {
</div>
</div>
<div className="mt-32 border-t border-scale-500 dark:border-scale-600 pt-8 flex justify-between">
<p className="mb-0 self-center text-base">&copy; Supabase Inc</p>
<small className="small">&copy; Supabase Inc</small>
<DarkModeToggle />
</div>
</SectionContainer>
+6 -4
View File
@@ -23,18 +23,20 @@ const Hero = () => {
<h1
className="
sm:leading-none
text-xl lg:text-4xl xl:text-5xl"
text-xl lg:text-4xl xl:text-5xl
text-scale-1200
"
>
<span className="block">The Open Source</span>
<span className="text-brand-900 md:ml-0 block">Firebase Alternative</span>
</h1>
<div>
<p className="text-base sm:mt-5 lg:text-lg ">
<p className="p text-base sm:mt-5 lg:text-lg ">
Create a backend in less than 2 minutes. Start your project with a
Postgres Database, Authentication, instant APIs, Realtime subscriptions
and Storage.
</p>
<p className="text-base">Serverless functions coming soon</p>
<p className="p text-base">Serverless functions coming soon</p>
</div>
</div>
<div className="flex gap-2 items-center">
@@ -50,7 +52,7 @@ const Hero = () => {
</Link>
</div>
<div className="flex flex-col gap-4">
<small className="text-xs">backed by</small>
<small className="small text-xs">backed by</small>
<div className="w-full sm:max-w-lg lg:ml-0">
<div className="flex flex-wrap items-center justify-start">
<img
+16 -18
View File
@@ -12,32 +12,28 @@ type Props = {
const Developers = () => {
const { basePath } = useRouter()
const iconSections = Object.values(DevelopersData).map((company: Props) => {
const { text, description, url, icon } = company
const content = (
<a
href={url}
className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 dark:hover:bg-scale-500 transition ease-in-out duration-150"
>
<div className="-m-3 p-3 flex items-start rounded-lg hover:bg-gray-50 dark:hover:bg-scale-500 transition ease-in-out duration-150">
{/* <!-- Heroicon name: support --> */}
<svg
className="flex-shrink-0 h-6 w-6 stroke-gray dark:stroke-white"
className="flex-shrink-0 h-5 w-5 stroke-scale-900"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d={icon} />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d={icon} />
</svg>
<div className="ml-4">
<h5>{text}</h5>
<p>
<p>{description}</p>
</p>
<h5 className="text-base text-scale-1200">{text}</h5>
<p className="text-sm text-scale-900">{description}</p>
</div>
</a>
</div>
)
return url ? (
<a
@@ -59,18 +55,18 @@ const Developers = () => {
return (
<div className="grid grid-cols-12">
<nav className="col-span-6" aria-labelledby="solutionsHeading">
<div className="grid grid-cols-12 m-3 gap-3">{iconSections}</div>
<nav className="col-span-6 py-8" aria-labelledby="solutionsHeading">
<div className="grid grid-cols-12 m-3 gap-x-8 gap-y-4 py-4 pr-3">{iconSections}</div>
</nav>
<div className="col-span-6">
<div className="m-3 mx-6">
<p>Latest announcements</p>
<p className="p">Latest announcements</p>
<ul className="mt-6 space-y-3 pb-6">
{AnnouncementsData.map((caseStudy: any, idx: number) => (
<li className="flow-root" key={`flyout_case_${idx}`}>
<a
href={caseStudy.url}
className="p-3 flex rounded-lg hover:bg-gray-100 dark:hover:bg-dark-700 transition ease-in-out duration-150 border dark:border-gray-600 items-center"
className="p-3 flex rounded-lg hover:bg-gray-100 dark:hover:bg-dark-700 transition ease-in-out duration-150 border items-center"
>
<div className="hidden sm:block flex-shrink-0">
<img
@@ -79,9 +75,11 @@ const Developers = () => {
alt="caseStudyThumb"
/>
</div>
<div className="min-w-0 flex-1 sm:ml-8">
<h1 className="mb-0 text-normal">{caseStudy.title}</h1>
<p className="text-sm">{caseStudy.description}</p>
<div className="min-w-0 flex-1 sm:ml-4">
<h4 className="text-base text-scale-1200 mb-0 text-normal">
{caseStudy.title}
</h4>
<p className="p text-sm">{caseStudy.description}</p>
</div>
</a>
</li>
+8 -16
View File
@@ -20,7 +20,7 @@ const Product = () => {
<div className="ml-4 md:flex-1 md:flex md:flex-col md:justify-between lg:ml-0 mt-3">
<p className="text-sm text-scale-1000">{description}</p>
{label && (
<div>
<div className="mt-2">
<Badge>{label}</Badge>
</div>
)}
@@ -47,7 +47,7 @@ const Product = () => {
</nav>
<div className="col-span-6">
<div className="m-3 mx-6">
<p>Latest case studies</p>
<p className="p">Latest case studies</p>
<ul className="mt-6 space-y-3">
{CaseStudiesData.map((caseStudy: any, idx: number) => {
if (idx > 1) {
@@ -57,17 +57,7 @@ const Product = () => {
<li className="flow-root" key={`flyout_case_${idx}`}>
<a
href={caseStudy.url}
className="p-3 flex rounded-lg
transition ease-in-out duration-150
hover:bg-scale-400
dark:hover:bg-scale-500
border
dark:border-scale-400
items-center"
className="p-3 flex rounded-lg hover:bg-gray-100 dark:hover:bg-dark-700 transition ease-in-out duration-150 border items-center"
>
<div className="hidden sm:block flex-shrink-0">
<img
@@ -76,9 +66,11 @@ const Product = () => {
alt="caseStudyThumb"
/>
</div>
<div className="min-w-0 flex-1 sm:ml-8">
<h1 className="mb-0 text-normal">{caseStudy.title}</h1>
<p className="text-sm">{caseStudy.description}</p>
<div className="min-w-0 flex-1 sm:ml-4">
<h4 className="text-base text-scale-1200 mb-0 text-normal">
{caseStudy.title}
</h4>
<p className="p text-sm">{caseStudy.description}</p>
</div>
</a>
</li>
+1 -1
View File
@@ -19,7 +19,7 @@ const ProductHeader = (props: Types) => (
<div className="col-span-12 lg:col-span-5 space-y-8">
<div>
{props.icon || props.title ? (
<div className="flex mb-4 items-center gap-2">
<div className="flex mb-4 items-center gap-3">
{props.icon && <ProductIcon icon={props.icon} />}
{props.title && (
<span className="text-scale-1200" key={`product-name-${props.title}`}>
+1 -1
View File
@@ -7,7 +7,7 @@ const SectionHeader = (props: any) => {
</span>
<h3 className="h2">
<span>{props.title}</span>
{props.title_alt && <span className="text-scale-900 inline">{props.title_alt}</span>}
{props.title_alt && <span className="text-scale-1100 inline">{props.title_alt}</span>}
</h3>
</div>
{props.paragraph && <p className="p text-lg sm:mt-4 max-w-3xl">{props.paragraph}</p>}
+94 -79
View File
@@ -1,101 +1,116 @@
{
"rory_wilding": {
"author": "Rory Wilding",
"author_url": "https://github.com/roryw10",
"author_image_url": "https://github.com/roryw10.png",
"position": "Head of Growth"
},
"ant_wilson": {
"author": "Ant Wilson",
"author_url": "https://github.com/awalias",
"author_image_url": "https://github.com/awalias.png",
"position": "CTO and Co-Founder"
},
"paul_copplestone": {
"author": "Paul Copplestone",
"author_url": "https://github.com/kiwicopple",
"author_image_url": "https://github.com/kiwicopple.png",
"position": "CEO and Co-Founder"
},
"angelico_de_los_reyes": {
[
{
"author_id": "angelico_de_los_reyes",
"author": "Angelico de los Reyes",
"position": "",
"author_url": "https://github.com/dragarcia",
"author_image_url": "https://avatars0.githubusercontent.com/u/26374889?s=400&u=f5e35e9b47a50fa2b4d8c4bb96babd921071bcf1&v=4",
"position": ""
"author_image_url": "https://github.com/dragarcia.png"
},
"thor_schaeff": {
"author": "Thor Schaeff",
"author_url": "https://github.com/thorwebdev",
"author_image_url": "https://github.com/thorwebdev.png",
"position": "DevRel & DX"
{
"author_id": "ant_wilson",
"author": "Ant Wilson",
"position": "CTO and Co-Founder",
"author_url": "https://github.com/awalias",
"author_image_url": "https://github.com/awalias.png"
},
"supabase": {
"author": "Supabase",
"author_url": "https://github.com/supabase",
"author_image_url": "https://github.com/supabase.png",
"position": ""
},
"inian": {
"author": "Inian Parameshwaran",
"author_url": "https://twitter.com/everConfusedGuy",
"position": "Engineering",
"username": "inian",
"author_image_url": "https://avatars.githubusercontent.com/u/2155545?v=4"
},
"soedirgo": {
{
"author_id": "soedirgo",
"author": "Bobbie Soedirgo",
"author_url": "https://github.com/soedirgo",
"position": "Engineering",
"username": "soedirgo",
"position": "Engineering",
"author_url": "https://github.com/soedirgo",
"author_image_url": "https://github.com/soedirgo.png"
},
"fracek": {
"author": "Francesco Ceccon",
"author_url": "https://github.com/fracek",
"position": "Engineering",
"username": "fracek",
"author_image_url": "https://github.com/fracek.png"
},
"steve_chavez": {
"author": "Steve Chavez",
"author_url": "https://github.com/steve-chavez",
"position": "Engineering & PostgREST maintainer",
"username": "steve-chavez",
"author_image_url": "https://github.com/steve-chavez.png"
},
"kangmingtay": {
"author": "Kang Ming Tay",
"author_url": "https://github.com/kangmingtay",
"position": "Engineering",
"username": "kangmingtay",
"author_image_url": "https://github.com/kangmingtay.png"
},
"div_arora": {
{
"author_id": "div_arora",
"author": "Div Arora",
"author_url": "https://github.com/darora",
"position": "Engineering",
"username": "darora",
"position": "Engineering",
"author_url": "https://github.com/darora",
"author_image_url": "https://github.com/darora.png"
},
"gurjeet": {
"author": "Gurjeet Singh",
"author_url": "https://github.com/gurjeet",
{
"author_id": "fracek",
"author": "Francesco Ceccon",
"username": "fracek",
"position": "Engineering",
"author_url": "https://github.com/fracek",
"author_image_url": "https://github.com/fracek.png"
},
{
"author_id": "gurjeet",
"author": "Gurjeet Singh",
"username": "gurjeet",
"position": "Engineering",
"author_url": "https://github.com/gurjeet",
"author_image_url": "https://github.com/gurjeet.png"
},
"wenbo_xie": {
"author": "Wen Bo Xie",
"author_url": "https://github.com/w3b6x9",
{
"author_id": "inian",
"author": "Inian Parameshwaran",
"username": "inian",
"position": "Engineering",
"username": "w3b6x9",
"author_image_url": "https://github.com/w3b6x9.png"
"author_url": "https://twitter.com/everConfusedGuy",
"author_image_url": "https://avatars.githubusercontent.com/u/2155545?v=4"
},
"oli_rice": {
"author": "Oliver Rice",
"author_url": "https://github.com/olirice",
{
"author_id": "kangmingtay",
"author": "Kang Ming Tay",
"username": "kangmingtay",
"position": "Engineering",
"author_url": "https://github.com/kangmingtay",
"author_image_url": "https://github.com/kangmingtay.png"
},
{
"author_id": "oli_rice",
"author": "Oliver Rice",
"username": "olirice",
"position": "Engineering",
"author_url": "https://github.com/olirice",
"author_image_url": "https://github.com/olirice.png"
},
{
"author_id": "paul_copplestone",
"author": "Paul Copplestone",
"position": "CEO and Co-Founder",
"author_url": "https://github.com/kiwicopple",
"author_image_url": "https://github.com/kiwicopple.png"
},
{
"author_id": "rory_wilding",
"author": "Rory Wilding",
"position": "Head of Growth",
"author_url": "https://github.com/roryw10",
"author_image_url": "https://github.com/roryw10.png"
},
{
"author_id": "steve_chavez",
"author": "Steve Chavez",
"username": "steve-chavez",
"position": "Engineering & PostgREST maintainer",
"author_url": "https://github.com/steve-chavez",
"author_image_url": "https://github.com/steve-chavez.png"
},
{
"author_id": "supabase",
"author": "Supabase",
"position": "",
"author_url": "https://github.com/supabase",
"author_image_url": "https://github.com/supabase.png"
},
{
"author_id": "thor_schaeff",
"author": "Thor Schaeff",
"position": "DevRel & DX",
"author_url": "https://github.com/thorwebdev",
"author_image_url": "https://github.com/thorwebdev.png"
},
{
"author_id": "wenbo_xie",
"author": "Wen Bo Xie",
"username": "w3b6x9",
"position": "Engineering",
"author_url": "https://github.com/w3b6x9",
"author_image_url": "https://github.com/w3b6x9.png"
}
}
]
+34 -23
View File
@@ -132,13 +132,24 @@ function Blog(props: any) {
function FeaturedThumb(blog: PostTypes) {
// @ts-ignore
const author = blog.author ? authors[blog.author] : authors['supabase']
const authorArray = blog.author.split(',')
const author = []
for (let i = 0; i < authorArray.length; i++) {
// @ts-ignore
author.push(
authors.find((authors: string) => {
// @ts-ignore
return authors.author_id === authorArray[i]
})
)
}
return (
<div key={blog.slug} className="cursor-pointer w-full">
<a href={`/blog/${blog.url}`}>
<a className="grid lg:grid-cols-2 gap-8 lg:gap-16">
<div className="relative overflow-auto w-full h-96 border dark:border-dark rounded-lg">
<div className="relative overflow-auto w-full h-96 border rounded-lg">
<Image
src={`/images/blog/` + (blog.thumb ? blog.thumb : blog.image)}
layout="fill"
@@ -146,37 +157,37 @@ function FeaturedThumb(blog: PostTypes) {
/>
</div>
<div className="flex flex-col space-y-2">
<div className="flex space-x-2 text-sm">
<div className="text-scale-900 flex space-x-2 text-sm">
<p>{blog.date}</p>
<p>•</p>
<p>{blog.readingTime}</p>
</div>
<div>
<h2 className="text-4xl">{blog.title}</h2>
<p className="m-0">
<span className="text-xl">{blog.description}</span>
</p>
<h2 className="h2">{blog.title}</h2>
<p className="p text-xl">{blog.description}</p>
</div>
{author && (
<div className="flex space-x-3 items-center">
{author.author_image_url && (
<div className="relative overflow-auto w-10 h-10">
<Image
src={author.author_image_url}
className="rounded-full"
layout="fill"
objectFit="cover"
/>
{author.map((author: any) => {
return (
<div className="flex space-x-3 items-center">
{author.author_image_url && (
<div className="relative overflow-auto w-10 h-10">
<Image
src={author.author_image_url}
className="rounded-full"
layout="fill"
objectFit="cover"
/>
</div>
)}
<div className="flex flex-col">
<span className="m-0 text-sm text-scale-1200">{author.author}</span>
<span className="m-0 text-xs text-scale-900">{author.position}</span>
</div>
)}
<div className="flex flex-col">
<span className="m-0 text-sm text-scale-1200">{author.author}</span>
<span className="m-0 text-xs text-scale-900">{author.position}</span>
</div>
</div>
)}
)
})}
</div>
</a>
</a>
+69 -51
View File
@@ -88,8 +88,19 @@ export async function getStaticProps({ params }: any) {
function BlogPostPage(props: any) {
// @ts-ignore
const author = props.blog.author ? authors[props.blog.author] : authors['supabase']
const content = hydrate(props.blog.content, { components })
const authorArray = props.blog.author.split(',')
const author = []
for (let i = 0; i < authorArray.length; i++) {
author.push(
// @ts-ignore
authors.find((authors: string) => {
// @ts-ignore
return authors.author_id === authorArray[i]
})
)
}
const { basePath } = useRouter()
@@ -102,22 +113,17 @@ function BlogPostPage(props: any) {
return (
<Link href={`/blog/${post.url}`} as={`/blog/${post.url}`}>
<div className={className}>
<Card className="cursor-pointer" hoverable>
<Space direction="vertical">
<div className="cursor-pointer border border-scale-500 p-6 transition rounded hover:bg-scale-100 dark:hover:bg-scale-300">
<div className="space-y-4">
<div>
<p>{label}</p>
<p className="text-sm text-scale-900">{label}</p>
</div>
<div>
<h4>{post.title}</h4>
<p>{post.date}</p>
<h4 className="text-lg text-scale-1200">{post.title}</h4>
<p className="small">{post.date}</p>
</div>
<div>
{post.tags.map((tag: string) => {
return <Badge key={`category-badge-${tag}`}>{tag}</Badge>
})}
</div>
</Space>
</Card>
</div>
</div>
</div>
</Link>
)
@@ -191,7 +197,7 @@ function BlogPostPage(props: any) {
<p>
<a
href={'/blog'}
className="hover:text-gray-900 dark:hover:text-white cursor-pointer flex items-center"
className="transition text-sm text-scale-900 hover:text-scale-1200 cursor-pointer flex items-center"
>
<IconChevronLeft style={{ padding: 0 }} />
Back
@@ -203,44 +209,52 @@ function BlogPostPage(props: any) {
<div className="mb-16 space-y-8 max-w-5xl">
<div className="space-y-4">
<p className="text-brand-900">Blog post</p>
<h1 className="text-5xl">{props.blog.title}</h1>
<div className="flex space-x-3 text-sm">
<h1 className="h1">{props.blog.title}</h1>
<div className="text-scale-900 flex space-x-3 text-sm">
<p>{props.blog.date}</p>
<p>•</p>
<p>{generateReadingTime(props.blog.content.renderedOutput)}</p>
</div>
{author && (
<div className="mt-6 mb-8 lg:mb-0 w-max">
<Link href={author.author_url}>
<a className="cursor-pointer">
<div className="flex items-center">
{author.author_image_url && (
<div className="w-10">
<Image
src={author.author_image_url}
className="rounded-full border dark:border-dark"
width="100%"
height="100%"
layout="responsive"
/>
<div className="flex">
{author.map((author: any) => {
return (
<div className="mt-6 mb-8 mr-4 lg:mb-0 w-max">
<Link href={author.author_url}>
<a className="cursor-pointer">
<div className="flex gap-3 items-center">
{author.author_image_url && (
<div className="w-10">
<Image
src={author.author_image_url}
className="rounded-full border dark:border-dark"
width="100%"
height="100%"
layout="responsive"
/>
</div>
)}
<div className="flex flex-col">
<span className="text-sm text-scale-1200 mb-0">
{author.author}
</span>
<span className="text-xs mb-0 text-scale-900">
{author.position}
</span>
</div>
</div>
)}
<div className="flex flex-col">
<span className="text-xs mb-0">{author.author}</span>
<span className="text-xs mb-0 text-scale-900">{author.position}</span>
</div>
</div>
</a>
</Link>
</div>
)}
</a>
</Link>
</div>
)
})}
</div>
</div>
</div>
<div className="grid grid-cols-12 lg:gap-16 xl:gap-8">
{/* Content */}
<div className="col-span-12 lg:col-span-7 xl:col-span-7">
{props.blog.thumb && (
<div className="relative overflow-auto w-full h-96 mb-8 border dark:border-gray-600">
<div className="relative overflow-auto w-full h-96 mb-8 border rounded">
<Image
src={'/images/blog/' + props.blog.thumb}
layout="fill"
@@ -270,7 +284,7 @@ function BlogPostPage(props: any) {
>
<path
d="m154.729 400c185.669 0 287.205-153.876 287.205-287.312 0-4.37-.089-8.72-.286-13.052a205.304 205.304 0 0 0 50.352-52.29c-18.087 8.044-37.55 13.458-57.968 15.899 20.841-12.501 36.84-32.278 44.389-55.852a202.42 202.42 0 0 1 -64.098 24.511c-18.42-19.628-44.644-31.904-73.682-31.904-55.744 0-100.948 45.222-100.948 100.965 0 7.925.887 15.631 2.619 23.025-83.895-4.223-158.287-44.405-208.074-105.504a100.739 100.739 0 0 0 -13.668 50.754c0 35.034 17.82 65.961 44.92 84.055a100.172 100.172 0 0 1 -45.716-12.63c-.015.424-.015.837-.015 1.29 0 48.903 34.794 89.734 80.982 98.986a101.036 101.036 0 0 1 -26.617 3.553c-6.493 0-12.821-.639-18.971-1.82 12.851 40.122 50.115 69.319 94.296 70.135-34.549 27.089-78.07 43.224-125.371 43.224a204.9 204.9 0 0 1 -24.078-1.399c44.674 28.645 97.72 45.359 154.734 45.359"
fill-rule="nonzero"
fillRule="nonzero"
/>
</svg>
</a>
@@ -313,14 +327,16 @@ function BlogPostPage(props: any) {
<div className="mb-4">
<p className="text-sm text-scale-1200">Related articles</p>
</div>
<div className="space-y-0">
<div className="space-y-3">
{props.relatedPosts.map((post: any) => (
<Link href={`/blog/${post.url}`} as={`/blog/${post.url}`}>
<div>
<p className="cursor-pointer">
<div className="flex items-center gap-2">
<IconFile size={'small'} style={{ minWidth: '1.2rem' }} />
<span className="text-sm hover:text-gray-900 dark:hover:text-white">
<div className="flex gap-2">
<div className="text-scale-900">
<IconFile size={'small'} style={{ minWidth: '1.2rem' }} />
</div>
<span className="text-sm text-scale-1100 hover:text-gray-1200">
{post.title}
</span>
</div>
@@ -329,11 +345,13 @@ function BlogPostPage(props: any) {
</div>
</Link>
))}
<Link href={`/blog`} as={`/blog`}>
<a className="text-sm text-scale-1100 hover:text-scale-1200 cursor-pointer">
View all posts
</a>
</Link>
<div className="mt-2">
<Link href={`/blog`} passHref>
<a className="text-sm text-scale-1100 hover:text-scale-1200 cursor-pointer">
View all posts
</a>
</Link>
</div>
</div>
</div>
</div>