Merge master resolve conflicts
No files matched your search
@@ -5,32 +5,46 @@ const Favicons = () => {
|
||||
const { basePath } = useRouter()
|
||||
return (
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href={`${basePath}/favicon/apple-touch-icon.png`}
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href={`${basePath}/favicon/favicon-32x32.png`}
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href={`${basePath}/favicon/favicon-16x16.png`}
|
||||
/>
|
||||
<link rel="manifest" href={`${basePath}/favicon/site.webmanifest`} />
|
||||
{/* <link rel="mask-icon" href={`${basePath}/favicon/safari-pinned-tab.svg`} color="#333333" /> */}
|
||||
<link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} />
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="57x57" href={`${basePath}/favicon/apple-touch-icon-57x57.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href={`${basePath}/favicon/apple-touch-icon-114x114.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href={`${basePath}/favicon/apple-touch-icon-72x72.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href={`${basePath}/favicon/apple-touch-icon-144x144.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="60x60" href={`${basePath}/favicon/apple-touch-icon-60x60.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href={`${basePath}/favicon/apple-touch-icon-120x120.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76" href={`${basePath}/favicon/apple-touch-icon-76x76.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href={`${basePath}/favicon/apple-touch-icon-152x152.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-196x196.png" sizes="196x196`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-96x96.png" sizes="96x96`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-32x32.png" sizes="32x32`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-16x16.png" sizes="16x16`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-128.png" sizes="128x128`}/>
|
||||
<meta name="application-name" content=" " />
|
||||
<meta name="msapplication-TileColor" content="#1E1E1E" />
|
||||
<meta name="msapplication-config" content={`${basePath}/favicon/browserconfig.xml`} />
|
||||
<meta name="msapplication-TileImage" content="mstile-144x144.png" />
|
||||
<meta name="msapplication-square70x70logo" content="mstile-70x70.png" />
|
||||
<meta name="msapplication-square150x150logo" content="mstile-150x150.png" />
|
||||
<meta name="msapplication-wide310x150logo" content="mstile-310x150.png" />
|
||||
<meta name="msapplication-square310x310logo" content="mstile-310x310.png" />
|
||||
<meta name="theme-color" content="#1E1E1E" />
|
||||
<link rel="alternate" type="application/rss+xml" href={`${basePath}/feed.xml`} />
|
||||
<link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} />
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon.ico`} />
|
||||
<link rel="apple-touch-icon" href={`${basePath}/favicon/favicon.ico`} />
|
||||
{/* misc */}
|
||||
<link rel="manifest" href={`${basePath}/favicon/site.webmanifest`} />
|
||||
<meta name="msapplication-config" content={`${basePath}/favicon/browserconfig.xml`} />
|
||||
<link rel="alternate" type="application/rss+xml" href={`${basePath}/feed.xml`} />
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -367,7 +367,6 @@ protect multiple routes at once.
|
||||
```ts
|
||||
// src/hooks.server.ts
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getSupabase } from '@supabase/auth-helpers-sveltekit';
|
||||
import { redirect, error } from '@sveltejs/kit';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
|
||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 869 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 452 B |
|
After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 859 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M1040 5110 c-25 -4 -63 -11 -84 -14 -60 -10 -207 -61 -287 -101 -165
|
||||
-81 -317 -208 -430 -358 -106 -141 -203 -370 -226 -534 -4 -32 -8 -735 -8
|
||||
-1563 l0 -1505 22 -93 c116 -485 498 -847 979 -926 135 -23 2988 -22 3114 0
|
||||
303 54 576 222 761 466 99 132 190 340 225 513 11 55 15 3029 5 3085 -5 25
|
||||
-12 63 -15 84 -8 49 -57 197 -87 262 -163 351 -504 610 -894 678 -66 12 -334
|
||||
14 -1555 14 -811 0 -1495 -4 -1520 -8z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 932 B |
@@ -3,12 +3,12 @@
|
||||
"short_name": "Supabase",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicons/android-chrome-192x192.png",
|
||||
"src": "/favicon/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicons/android-chrome-512x512.png",
|
||||
"src": "/favicon/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 2500 2500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M174.672,1866.26L173.916,1866.7C191.548,1898.57 218.67,1925.69 254.607,1946.71C428.869,2047.74 1012.68,2382.03 1129.3,2451.19C1135.77,2454.98 1142.22,2458.47 1148.65,2461.66C1221.03,2498 1291.94,2496.48 1364.59,2453.9C1657.51,2284.39 1951.11,2116.23 2244.04,1947.39C2280.65,1926.37 2307.1,1899.92 2324.72,1867.38C2341.57,1836.39 2350.38,1800.72 2350.49,1760.36C2350.49,1759.87 2350.49,1759.38 2350.49,1758.89L2350.49,739.086C2350.49,697.725 2341.68,660.432 2323.37,629.239L2323.37,629.241L2323.37,629.239C2305.74,598.05 2279.29,572.284 2244.04,551.942C1951.11,383.104 1657.51,214.269 1364.59,45.431C1285.26,0 1209.32,1.357 1130.66,47.464C1014.03,115.95 429.546,450.91 255.963,551.942C184.087,593.303 149.507,657.042 149.507,739.765L149.507,1759.57C149.507,1799.77 158.113,1835.33 174.672,1866.26Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
<path d="M1566.65,1427.32C1504.27,1537.84 1385.61,1613.11 1249.32,1613.11C1113.71,1613.11 995.729,1539.2 933.347,1429.35C902.834,1375.79 885.203,1314.76 885.203,1248.99C885.203,1047.6 1047.94,884.869 1249.32,884.869C1383.58,884.869 1500.88,958.099 1563.94,1066.59L1881.95,883.512C1755.16,665.179 1519.19,518.039 1248.64,518.039C844.519,518.039 517.696,845.542 517.696,1248.99C517.696,1381.21 552.953,1505.97 614.658,1613.11C740.776,1832.12 978.098,1979.94 1249.32,1979.94C1521.23,1979.94 1758.55,1831.44 1884.66,1611.07L1566.65,1427.32ZM1968.74,1076.08L1896.19,1076.08L1896.19,1426.64L1968.74,1426.64L1968.74,1076.08ZM2129.44,1076.08L2056.89,1076.08L2056.89,1426.64L2129.44,1426.64L2129.44,1076.08Z" style="fill-rule:nonzero;"/>
|
||||
<g>
|
||||
<path d="M2188.43,1135.07L1837.88,1135.07L1837.88,1207.63L2188.43,1207.63L2188.43,1135.07ZM2188.43,1295.1L1837.88,1295.1L1837.88,1367.65L2188.43,1367.65L2188.43,1295.1Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.24265 11.2995L2.2467 11.2971C2.1581 11.1317 2.11206 10.9414 2.11206 10.7263V5.27012C2.11206 4.82754 2.29707 4.48652 2.68163 4.26523C3.12626 4.00643 4.07482 3.46075 5.01133 2.922C6.03091 2.33546 7.03621 1.75714 7.36146 1.56615C7.78231 1.31947 8.18861 1.31221 8.61304 1.55527C9.40246 2.01029 10.1928 2.4653 10.9831 2.92032C11.7618 3.36862 12.5405 3.81692 13.3183 4.26523C13.5069 4.37406 13.6484 4.51192 13.7427 4.67878C13.8407 4.84567 13.8878 5.0452 13.8878 5.26649V10.7227V10.7306C13.8873 10.9465 13.8401 11.1373 13.75 11.3031C13.6557 11.4772 13.5142 11.6187 13.3183 11.7312C12.7234 12.0741 12.128 12.4164 11.5325 12.7589C10.5591 13.3186 9.58538 13.8785 8.61304 14.4412C8.22435 14.669 7.84496 14.6771 7.45771 14.4827C7.42331 14.4656 7.3888 14.4469 7.35419 14.4267C6.97211 14.2001 5.65273 13.4416 4.47316 12.7634C3.72646 12.3342 3.03579 11.9371 2.67437 11.7276C2.4821 11.6151 2.33699 11.47 2.24265 11.2995ZM9.69419 8.94871C9.36044 9.54002 8.72558 9.94273 7.99639 9.94273C7.27085 9.94273 6.63962 9.54729 6.30586 8.95957C6.14261 8.67301 6.04828 8.34649 6.04828 7.9946C6.04828 6.91712 6.91896 6.04647 7.99639 6.04647C8.71472 6.04647 9.3423 6.43826 9.67969 7.01872L11.3811 6.03921C10.7028 4.87107 9.44026 4.08384 7.99276 4.08384C5.83061 4.08384 4.08203 5.83606 4.08203 7.9946C4.08203 8.70201 4.27066 9.36951 4.6008 9.94273C5.27556 11.1145 6.54529 11.9054 7.99639 11.9054C9.45118 11.9054 10.7209 11.1108 11.3956 9.93182L9.69419 8.94871ZM11.8455 7.06949H11.4573V7.3851H11.1453V7.77331H11.4573V8.2413H11.1453V8.62946H11.4573V8.94507H11.8455V8.62946H12.3171V8.94507H12.7052V8.62946H13.0208V8.2413H12.7052V7.77331H13.0208V7.3851H12.7052V7.06949H12.3171V7.3851H11.8455V7.06949ZM11.8455 8.2413V7.77331H12.3171V8.2413H11.8455Z" fill="url(#paint0_linear_1002_71738)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1002_71738" x1="-4.11388" y1="12.9341" x2="13.1956" y2="12.9341" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D8FFEC" stop-opacity="0.6"/>
|
||||
<stop offset="1" stop-color="#F4FFFA"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -23,7 +23,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-scale-100 dark:bg-[#1c1c1c] #{!important};
|
||||
@apply bg-scale-200 dark:bg-[#1c1c1c] #{!important};
|
||||
scroll-behavior: smooth;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@@ -14,7 +14,7 @@ toc_depth: 2
|
||||
|
||||
With Easter just around the corner, [Launch Week](https://supabase.com/launch-week) is about to start and, as usual, we're running an async hackathon alongside it. This time it’s AI-themed!
|
||||
|
||||
The hackathon starts **now** and ends Monday 16th April at 11:59 pm. You can win extremily limited edition Launch Week themed mechanical keyboards and limited edition Supabase swag.
|
||||
The hackathon starts **now** and ends Monday 16th April at 11:59 pm. You can win extremely limited edition Launch Week themed mechanical keyboards and limited edition Supabase swag.
|
||||
|
||||
For some inspiration check out the [Next.js OpenAI Doc Search template](https://github.com/supabase-community/nextjs-openai-doc-search?og=v2) released yesterday and [storing embeddings on Supabase article](https://supabase.com/blog/openai-embeddings-postgres-vector).
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
---
|
||||
title: 'Supabase Logs: open source logging server'
|
||||
launchweek: 7
|
||||
tags:
|
||||
- launch-week
|
||||
- o11y
|
||||
date: '2023-04-10'
|
||||
to_depth: 3
|
||||
author: chasers,ziinc
|
||||
image: launch-week-7/day-1-supabase-logs-self-hosted/day-1-supabase-logs-self-hosted-og.jpg
|
||||
thumb: launch-week-7/day-1-supabase-logs-self-hosted/day-1-supabase-logs-self-hosted-thumb.jpg
|
||||
---
|
||||
|
||||
Today, we’re releasing Supabase Logs for both self-hosted users and CLI development.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
className="w-full"
|
||||
width="560"
|
||||
height="315"
|
||||
src="https://www.youtube.com/embed/Ai2BjHV36Ng"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
_Whatch the video announcement_
|
||||
</div>
|
||||
|
||||
## Logflare Introduction
|
||||
|
||||
Since [Logflare joined Supabase](https://supabase.com/blog/supabase-acquires-logflare) over a year ago it’s been quietly handling over 1 billion log events every day. These events come from various tools in the Supabase infrastructure - the API gateway, Postgres databases, Storage, Edge Functions, Auth, and Realtime.
|
||||
|
||||
Logflare is a multi-node, highly available Elixir cluster, ingesting the log events and storing them into BigQuery for Supabase and Logflare’s customers. On average, the cluster has 6 nodes handling every spike our customers throw at it.
|
||||
|
||||
To expose log data to customers, we leverage Logflare Endpoints. This provides an HTTP integration into Supabase Studio, powering the log query UIs and most time-series charts. These charts live across the studio, such as the project home page and the new API reports.
|
||||
|
||||
{/* <!-- add chart screenshot --> */}
|
||||
|
||||
## Self-hosting Logflare
|
||||
|
||||
Logflare was available under a BSL license prior to joining Supabase. We’ve since changed the license to [Apache 2.0](https://github.com/Logflare/logflare/blob/staging/LICENSE.md), aligning it with our open source philosophy.
|
||||
|
||||
In the past few months we’ve made Logflare more developer-friendly for local development and self-hosting. While you’re building a project, you can view and query your logs from any Supabase service, just as you would in our cloud platform.
|
||||
|
||||

|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 italic">
|
||||
|
||||
📢 Check out the [new self-hosting docs](https://supabase.com/docs/reference/self-hosting-analytics/introduction) to get Logflare up and running as your analytics server.
|
||||
|
||||
</div>
|
||||
|
||||
It currently supports a BigQuery backend, and we are actively working on supporting more.
|
||||
|
||||
## The Ingestion Pipeline
|
||||
|
||||
Logflare receives Supabase log events via multiple methods. Services like Postgres use [Vector](https://vector.dev/) to clean and forward log events to the Logflare ingest API. Other services such as Realtime and Storage utilize [native Logflare integrations](https://github.com/Logflare/logflare#integrations) to send the log events directly. These then get processed and streamed into BigQuery.
|
||||
|
||||
## The Querying Pipeline
|
||||
|
||||
The hard part comes after ingesting the logs: searching, aggregating ,and analyzing them at scale. Crunching many terabytes of data on each query is expensive, and exposing the ingested data to Supabase customers in a naive manner would cause our costs to skyrocket.
|
||||
|
||||
To solve these issues, we built and refined Logflare Endpoints, the query engine that powers many of Supabase’s features, such as the logs views, Logs Explorer, and usage charts.
|
||||
|
||||
With Endpoints, you can create HTTP API endpoints from a SQL query, including parameterized queries. Endpoints are like PostgREST views but with some benefits:
|
||||
|
||||
- **Query parameters**
|
||||
- You can provide string parameters to the SQL query via the HTTP endpoint.
|
||||
- **Read-through caching**
|
||||
- Results from the query are cached in memory for fast response times.
|
||||
- A read-through cache provides results if cached results do not exist.
|
||||
- **Active cache warming**
|
||||
- Query results are proactively warmed at a configurable interval for a combination of fast response times and as-realtime-as-needed data.
|
||||
- **Query sandboxing**
|
||||
- If an Endpoint query contains a CTE and the sandbox option is selected, the Endpoint will inject the query string of the `sql` query parameter into the Endpoint SQL replacing the default query (the part of the SQL query after the CTE).
|
||||
- Endpoints parse SQL to allow `select` queries only. No DML or DDL statements are permitted to run through Logflare Endpoints.
|
||||
|
||||
With this feature set, Supabase has been able to build any view we’ve needed on top of billions of daily log events.
|
||||
|
||||
### Logflare Endpoint Example
|
||||
|
||||
Using webhooks, we can send all GitHub events in the Supabase organization to Logflare. The webhook sends structured events, and Logflare transforms the payload into metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"event_message": "supabase/supabase | JohannesBauer97 | created",
|
||||
"id": "0d48b71d-91c5-4356-82c7-fdb299b625d0",
|
||||
"metadata": {
|
||||
"sender": {
|
||||
"id": 15695124,
|
||||
"login": "JohannesBauer97",
|
||||
"node_id": "MDQ6VXNlcjE1Njk1MTI0",
|
||||
"site_admin": false,
|
||||
"type": "User",
|
||||
"url": "https://api.github.com/users/JohannesBauer97"
|
||||
},
|
||||
"starred_at": "2023-03-30T20:33:55Z"
|
||||
//...
|
||||
},
|
||||
"timestamp": 1680208436849642
|
||||
}
|
||||
```
|
||||
|
||||
We’re interested in the top contributors, which can be extracted with SQL (in BigQuery dialect):
|
||||
|
||||
```sql
|
||||
select
|
||||
count(t.timestamp) as count,
|
||||
s.login as gh_user
|
||||
from
|
||||
`github.supabase.webhooks` as t
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.sender) as s
|
||||
where
|
||||
timestamp::date > current_date() - @day::int
|
||||
group by
|
||||
gh_user
|
||||
order by
|
||||
count desc
|
||||
limit
|
||||
25
|
||||
```
|
||||
|
||||
With this view in place, we can use Endpoints to provide an API that we can hit from our application:
|
||||
|
||||
```bash
|
||||
curl "https://logflare.app/endpoints/query/69425db0-1cfb-48b4-84c7-2a872b6f0a61" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-G -d "day=30"
|
||||
```
|
||||
|
||||
This returns a JSON response with the top org wide contributors for the last 30 days!
|
||||
|
||||
```json
|
||||
{
|
||||
"result": [
|
||||
{ "count": 23404, "gh_user": "vercel[bot]" },
|
||||
{ "count": 10005, "gh_user": "joshenlim" },
|
||||
{ "count": 7026, "gh_user": "MildTomato" },
|
||||
{ "count": 6405, "gh_user": "fsansalvadore" },
|
||||
{ "count": 5195, "gh_user": "saltcod" },
|
||||
{ "count": 3454, "gh_user": "alaister" },
|
||||
{ "count": 2691, "gh_user": "kevcodez" },
|
||||
{ "count": 2117, "gh_user": "gregnr" },
|
||||
{ "count": 1769, "gh_user": "Ziinc" },
|
||||
{ "count": 1749, "gh_user": "chasers" },
|
||||
{ "count": 1430, "gh_user": "Isaiah-Hamilton" }
|
||||
//...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
We can configure this Endpoint to cache results for an interval of 10 minutes after the first API request, and proactively update those cached results every 2 minutes - 5 queries across the 10 minute interval. Even if we hit the Endpoint thousands of times, we only sustain the cost of 5 queries.
|
||||
|
||||
The initial request is fast because Logflare also performs setup (such as partitioning) on our BigQuery tables appropriately. Subsequent requests are _extremely fast_ as they are cached in-memory.
|
||||
|
||||
The best part is that all these knobs can be tweaked for your use case. If we have a real-time requirement, we can completely disable caching or reduce the proactive caching to update on a per-second interval.
|
||||
|
||||
## The Self-hosted Challenge
|
||||
|
||||
To change the license, we needed to remove all closed-source dependencies. Previously, Logflare relied on the closed source [General SQL Parser](https://www.sqlparser.com/) under a business licenses. This is incompatible with the Apache License.
|
||||
|
||||
We switched to an open source alternative, the rust-based [sqlparser-rs](https://github.com/sqlparser-rs/sqlparser-rs) library, contributing a [few updates](https://github.com/sqlparser-rs/sqlparser-rs/pulls?q=is%3Apr+is%3Amerged+author%3AZiinc) for the BigQuery dialect.
|
||||
|
||||
Along with the parser, we invested a lot of effort into transforming the multi-tenant architecture into something that was self-hosting friendly and easily configurable. We moved towards environment variable based configuration instead of compile-time configurations, exposing the Endpoints configurations necessary for Supabase Logs.
|
||||
|
||||
## What’s Next?
|
||||
|
||||
To further integrate Logflare into the Supabase platform, we are building out 2 main areas: Management API, Multiple Backends.
|
||||
|
||||
### Management API
|
||||
|
||||
The Management API allows users to interact programmatically with Logflare to manage their account and resources. This feature will be available for both Logflare customers and self-hosted users.
|
||||
|
||||
You can check out the preview of our OpenAPI spec here: [https://logflare.app/swaggerui](https://logflare.app/swaggerui)
|
||||
|
||||

|
||||
|
||||
Not only that, we intend to expose user account provisioning to select partners. Soon, you’ll be able to become a Logflare Partner to provision Logflare accounts through the Partner API. Perfect if you want to resell a log analytics service from your own platform.
|
||||
|
||||
Contact us at growth@supabase.com to get in early on that waitlist.
|
||||
|
||||
## Multiple Backends
|
||||
|
||||
Logflare currently supports a BigQuery backend. We plan to add support for other analytics-optimized databases, like Clickhouse. We will also support pushing data to other web services, making Logflare a good fit for any data pipeline.
|
||||
|
||||
This will benefit the Supabase CLI: once Postgres support is available, Logflare will be able to integrate seamlessly, without the BigQuery requirement.
|
||||
|
||||
## Wrapping Up
|
||||
|
||||
Logflare has given Supabase the flexibility to quickly deploy features powered by an underlying structured event stream. Materializing metrics from an event stream is a powerful framework for delivering real-time views on analytics streams.
|
||||
|
||||
Logflare is the hub of analytics streams for Supabase. We look forward to giving Supabase customers the same superpower.
|
||||
@@ -0,0 +1,64 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
const ShareArticleActions = ({ title, slug }: { title: string; slug: string }) => (
|
||||
<div className="mt-4 flex items-center space-x-4">
|
||||
<Link
|
||||
passHref
|
||||
href={`https://twitter.com/share?text=${title}&url=https://supabase.com/blog/${slug}`}
|
||||
>
|
||||
<a target="_blank" className="text-scale-900 hover:text-scale-1200">
|
||||
<svg
|
||||
height="26"
|
||||
width="26"
|
||||
viewBox="-89 -46.8 644 446.8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<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"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
passHref
|
||||
href={`https://www.linkedin.com/shareArticle?url=https://supabase.com/blog/${slug}&title=${title}`}
|
||||
>
|
||||
<a target="_blank" className="text-scale-900 hover:text-scale-1200">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 5 1036 990"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M0 120c0-33.334 11.667-60.834 35-82.5C58.333 15.833 88.667 5 126 5c36.667 0 66.333 10.666 89 32 23.333 22 35 50.666 35 86 0 32-11.333 58.666-34 80-23.333 22-54 33-92 33h-1c-36.667 0-66.333-11-89-33S0 153.333 0 120zm13 875V327h222v668H13zm345 0h222V622c0-23.334 2.667-41.334 8-54 9.333-22.667 23.5-41.834 42.5-57.5 19-15.667 42.833-23.5 71.5-23.5 74.667 0 112 50.333 112 151v357h222V612c0-98.667-23.333-173.5-70-224.5S857.667 311 781 311c-86 0-153 37-201 111v2h-1l1-2v-95H358c1.333 21.333 2 87.666 2 199 0 111.333-.667 267.666-2 469z" />
|
||||
</svg>
|
||||
</a>
|
||||
</Link>
|
||||
<Link
|
||||
passHref
|
||||
href={`https://news.ycombinator.com/submitlink?u=https://supabase.com/blog/${slug}&t=${title}`}
|
||||
>
|
||||
<a target="_blank" className="text-scale-900 hover:text-scale-1200">
|
||||
<svg
|
||||
width="23"
|
||||
height="23"
|
||||
viewBox="0 0 23 23"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M22.7969 0.878906H0.703125V22.9727H22.7969V0.878906Z" fill="currentColor" />
|
||||
<path
|
||||
d="M11.0093 13.3976L7.17969 6.25391H8.94719L11.1566 10.7463C11.1566 10.8199 11.2302 10.8936 11.3039 10.9672C11.3775 11.0409 11.3775 11.1145 11.4511 11.2618L11.5248 11.3355V11.4091C11.5984 11.5564 11.5984 11.6301 11.6721 11.7773C11.7457 11.851 11.7457 11.9983 11.8194 12.0719C11.893 11.851 12.0403 11.7037 12.114 11.4091C12.1876 11.1882 12.3349 10.9672 12.4822 10.7463L14.6916 6.25391H16.3118L12.4822 13.4712V18.0372H11.0093V13.3976Z"
|
||||
fill="#1C1C1C"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default ShareArticleActions
|
||||
@@ -5,32 +5,47 @@ const Favicons = () => {
|
||||
const { basePath } = useRouter()
|
||||
return (
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href={`${basePath}/favicon/apple-touch-icon.png`}
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href={`${basePath}/favicon/favicon-32x32.png`}
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href={`${basePath}/favicon/favicon-16x16.png`}
|
||||
/>
|
||||
<link rel="manifest" href={`${basePath}/favicon/site.webmanifest`} />
|
||||
{/* <link rel="mask-icon" href={`${basePath}/favicon/safari-pinned-tab.svg`} color="#333333" /> */}
|
||||
<link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} />
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="57x57" href={`${basePath}/favicon/apple-touch-icon-57x57.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href={`${basePath}/favicon/apple-touch-icon-114x114.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href={`${basePath}/favicon/apple-touch-icon-72x72.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href={`${basePath}/favicon/apple-touch-icon-144x144.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="60x60" href={`${basePath}/favicon/apple-touch-icon-60x60.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href={`${basePath}/favicon/apple-touch-icon-120x120.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76" href={`${basePath}/favicon/apple-touch-icon-76x76.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href={`${basePath}/favicon/apple-touch-icon-152x152.png`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-196x196.png" sizes="196x196`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-96x96.png" sizes="96x96`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-32x32.png" sizes="32x32`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-16x16.png" sizes="16x16`}/>
|
||||
{/* prettier-ignore */}
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon-128.png" sizes="128x128`}/>
|
||||
<meta name="application-name" content=" " />
|
||||
<meta name="msapplication-TileColor" content="#1E1E1E" />
|
||||
<meta name="msapplication-config" content={`${basePath}/favicon/browserconfig.xml`} />
|
||||
<meta name="msapplication-TileImage" content="mstile-144x144.png" />
|
||||
<meta name="msapplication-square70x70logo" content="mstile-70x70.png" />
|
||||
<meta name="msapplication-square150x150logo" content="mstile-150x150.png" />
|
||||
<meta name="msapplication-wide310x150logo" content="mstile-310x150.png" />
|
||||
<meta name="msapplication-square310x310logo" content="mstile-310x310.png" />
|
||||
<meta name="theme-color" content="#1E1E1E" />
|
||||
<link rel="alternate" type="application/rss+xml" href={`${basePath}/feed.xml`} />
|
||||
<link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} />
|
||||
<link rel="icon" type="image/png" href={`${basePath}/favicon/favicon.ico`} />
|
||||
{/* misc */}
|
||||
<link rel="manifest" href={`${basePath}/favicon/site.webmanifest`} />
|
||||
<link rel="alternate" type="application/rss+xml" href={`${basePath}/feed.xml`} />
|
||||
<link rel="apple-touch-icon" href={`${basePath}/favicon/favicon.ico`} />
|
||||
<meta name="msapplication-config" content={`${basePath}/favicon/browserconfig.xml`} />
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
import days from '~/components/LaunchWeek/lw7_days'
|
||||
|
||||
const HackernewsShare = ({ url, title }: { url: string; title: string }) => {
|
||||
const hackerNewsLink = `https://news.ycombinator.com/submitlink?u=${encodeURIComponent(
|
||||
url
|
||||
)}&t=${encodeURIComponent(title)}`
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 lg:gap-6">
|
||||
<p className="text-lg">Start a conversation about this topic</p>
|
||||
<a href={hackerNewsLink} target="_blank" rel="noopener noreferrer">
|
||||
<svg
|
||||
width="23"
|
||||
height="23"
|
||||
viewBox="0 0 23 23"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M22.7969 0.878906H0.703125V22.9727H22.7969V0.878906Z" fill="#A0A0A0" />
|
||||
<path
|
||||
d="M11.0093 13.3976L7.17969 6.25391H8.94719L11.1566 10.7463C11.1566 10.8199 11.2302 10.8936 11.3039 10.9672C11.3775 11.0409 11.3775 11.1145 11.4511 11.2618L11.5248 11.3355V11.4091C11.5984 11.5564 11.5984 11.6301 11.6721 11.7773C11.7457 11.851 11.7457 11.9983 11.8194 12.0719C11.893 11.851 12.0403 11.7037 12.114 11.4091C12.1876 11.1882 12.3349 10.9672 12.4822 10.7463L14.6916 6.25391H16.3118L12.4822 13.4712V18.0372H11.0093V13.3976Z"
|
||||
fill="#1C1C1C"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HackernewsShare
|
||||
@@ -3,15 +3,22 @@ import LabelBadge from '../LabelBadge'
|
||||
import LaunchWeekPrizeCard from './LaunchWeekPrizeCard'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
export default function LaunchWeekPrizeSection({ className }: { className?: string }) {
|
||||
export default function LaunchWeekPrizeSection({
|
||||
className,
|
||||
ticket,
|
||||
}: {
|
||||
className?: string
|
||||
ticket?: any
|
||||
}) {
|
||||
const finalAnimationState = { y: 0, opacity: 1 }
|
||||
const Ticket = ticket
|
||||
|
||||
return (
|
||||
<div id="lw-7-prizes" className={[className].join(' ')}>
|
||||
<div className="">
|
||||
<div id="lw-7-prizes" className={['scroll-mt-[75px]', className].join(' ')}>
|
||||
<div className="!max-w-[100vw]">
|
||||
<div className="text-center relative z-10 text-white">
|
||||
<motion.div
|
||||
className="max-w-[38rem] mx-auto px-4 flex flex-col items-center gap-4"
|
||||
className="max-w-[38rem] mx-auto flex flex-col items-center gap-4 px-4"
|
||||
initial={{ y: -20, opacity: 0 }}
|
||||
whileInView={finalAnimationState}
|
||||
viewport={{ once: true, margin: '-150px' }}
|
||||
@@ -56,7 +63,8 @@ export default function LaunchWeekPrizeSection({ className }: { className?: stri
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
<div className="px-4 pt-24">
|
||||
{ticket && <Ticket />}
|
||||
<div className="pt-4 px-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 col-span-2 lg:grid-cols-5 gap-4 max-w-7xl mx-auto text-white">
|
||||
<LaunchWeekPrizeCard
|
||||
imageUrl="/images/launchweek/seven/keyboard.jpg"
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function TicketBrickWall({ users }: Props) {
|
||||
<div className="flex justify-center w-full mx-auto mt-2 lg:mt-4">
|
||||
<Link href="/launch-week/tickets">
|
||||
<a>
|
||||
<Button type="outline" size="medium">
|
||||
<Button type="outline" size="medium" onClick={() => window.scrollTo(0, 0)}>
|
||||
View all tickets
|
||||
</Button>
|
||||
</a>
|
||||
|
||||
@@ -30,6 +30,16 @@ const opacityVariant = {
|
||||
},
|
||||
},
|
||||
}
|
||||
const opacityVariant2 = {
|
||||
default: { opacity: 0, ease: defaultEase, duration: 0.2 },
|
||||
hover: {
|
||||
opacity: 0.2,
|
||||
transition: {
|
||||
duration: 0.4,
|
||||
ease: defaultEase,
|
||||
},
|
||||
},
|
||||
}
|
||||
const scaleOpacityVariant = {
|
||||
default: { scale: 1, opacity: 0.9, ease: defaultEase, duration: 0.2 },
|
||||
hover: {
|
||||
@@ -47,6 +57,28 @@ const aiImageMobileVariant = {
|
||||
scale: 0.9,
|
||||
},
|
||||
}
|
||||
const moveX10 = {
|
||||
default: { x: 0, ease: defaultEase, duration: 0.2 },
|
||||
hover: {
|
||||
x: 10,
|
||||
transition: {
|
||||
duration: 0.4,
|
||||
ease: defaultEase,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const getDay1Motion = (index: number) => {
|
||||
switch (index) {
|
||||
case 1:
|
||||
case 2:
|
||||
return moveX10
|
||||
case 3:
|
||||
return opacityVariant2
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export default function LW7Releases() {
|
||||
const [preRelease, day1, day2, day3, day4, day5] = days
|
||||
@@ -72,7 +104,7 @@ export default function LW7Releases() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SectionContainer className="!py-0">
|
||||
<SectionContainer className="!py-0 w-full !px-0 !max-w-none">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 scroll-mt-[105px]" id="hackathon">
|
||||
<SmallCard
|
||||
bgGradient
|
||||
@@ -102,11 +134,11 @@ export default function LW7Releases() {
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row ml-2 sm:ml-4">
|
||||
<span className="text-black dark:text-white mr-2">
|
||||
Supabase AI Hackathon is on. Join now!
|
||||
Supabase AI Hackathon has begun. Join now!
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 z-10">
|
||||
<div className="flex w-full sm:w-auto justify-center gap-2 z-10">
|
||||
<ChipLink href={'/blog/launch-week-7-hackathon'}>
|
||||
Blog post
|
||||
<div className="bg-[#eeeeee] dark:bg-[#313131] rounded-full inline-block p-1 ml-2">
|
||||
@@ -146,18 +178,15 @@ export default function LW7Releases() {
|
||||
<span className="text-black dark:text-white mr-2">Join the prize draw</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 z-10">
|
||||
<ChipLink href="#lw-7-prizes">
|
||||
Learn more
|
||||
<div className="bg-[#eeeeee] dark:bg-[#313131] rounded-full inline-block p-1 ml-2">
|
||||
<PencilSvg />
|
||||
</div>
|
||||
<div className="flex w-full sm:w-auto justify-center gap-2 z-10">
|
||||
<ChipLink href="#lw-7-prizes" className="pr-3 !justify-center">
|
||||
More info
|
||||
</ChipLink>
|
||||
</div>
|
||||
</SmallCard>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
<SectionContainer className="!pt-0">
|
||||
<SectionContainer className="!pt-0 !w-full !px-0 !max-w-none">
|
||||
<Accordion
|
||||
type="default"
|
||||
openBehaviour="multiple"
|
||||
@@ -188,7 +217,7 @@ export default function LW7Releases() {
|
||||
<motion.div
|
||||
className={`
|
||||
relative overflow-hidden group/2 flex-1 flex flex-col items-center gap-5 lg:items-start justify-between
|
||||
basis-1/2 lg:basis-2/3 border rounded-xl h-full p-14 xs:text-2xl text-xl text-center shadow-lg
|
||||
basis-1/2 lg:flex-shrink xl:basis-2/3 border rounded-xl h-full py-10 sm:py-14 px-4 sm:px-8 lg:px-10 xs:text-2xl text-xl text-center shadow-lg
|
||||
`}
|
||||
initial="default"
|
||||
animate="default"
|
||||
@@ -202,7 +231,7 @@ export default function LW7Releases() {
|
||||
/>
|
||||
<div className="flex items-center text-center lg:text-left lg: justify-between flex-col-reverse lg:flex-row lg:justify-start gap-2 text-black dark:text-white">
|
||||
<CartTitle>{preRelease.steps[0].title}</CartTitle>
|
||||
<StyledArticleBadge className="lg:ml-4">Guide</StyledArticleBadge>
|
||||
<StyledArticleBadge className="lg:ml-2">Guide</StyledArticleBadge>
|
||||
</div>
|
||||
<SectionButtons
|
||||
docs={preRelease.steps[0].docs}
|
||||
@@ -253,7 +282,7 @@ export default function LW7Releases() {
|
||||
<motion.div
|
||||
className={`
|
||||
relative overflow-hidden group/3 flex-1 flex flex-col items-center justify-between
|
||||
basis-1/2 lg:basis-1/3 border rounded-xl h-full bg-no-repeat py-14 lg:px-10 text-2xl bg-contain shadow-lg
|
||||
basis-1/2 lg:basis-1/3 border rounded-xl h-full bg-no-repeat py-10 sm:py-14 px-4 sm:px-8 lg:px-10 text-2xl bg-contain shadow-lg
|
||||
`}
|
||||
initial="default"
|
||||
animate="default"
|
||||
@@ -319,7 +348,73 @@ export default function LW7Releases() {
|
||||
className="h-[79px]"
|
||||
id={day1.d.toString()}
|
||||
>
|
||||
<div></div>
|
||||
{day1.steps.length > 0 && (
|
||||
<div className="h-[400px] flex flex-col gap-5 lg:flex-row">
|
||||
<motion.div
|
||||
className={`
|
||||
relative overflow-hidden group/2 flex-1 flex flex-col items-center gap-5 lg:items-start justify-between
|
||||
w-full border rounded-xl h-full p-14 xs:text-2xl text-xl text-center shadow-lg
|
||||
`}
|
||||
initial="default"
|
||||
animate="default"
|
||||
whileHover="hover"
|
||||
>
|
||||
<div className="flex items-center text-center lg:text-left lg: justify-between flex-col-reverse lg:flex-row lg:justify-start gap-2 text-black dark:text-white">
|
||||
<CartTitle>{day1.steps[0].title}</CartTitle>
|
||||
<StyledArticleBadge className="lg:ml-2">New</StyledArticleBadge>
|
||||
</div>
|
||||
<SectionButtons docs={day1.steps[0].docs} blog={day1.steps[0].blog} />
|
||||
{day1.steps[0].bg_layers &&
|
||||
day1.steps[0].bg_layers?.map((layer, i) =>
|
||||
!!layer.lottie ? (
|
||||
<div className="absolute inset-0 opacity-90 w-full h-full -z-10 transition-all duration-300">
|
||||
<Lottie
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
autoplay={true}
|
||||
animationData={layer.lottie}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
!!layer.img && (
|
||||
<motion.div
|
||||
className={[
|
||||
'absolute inset-0 w-full h-full -z-10',
|
||||
// i === 6 && '!mix-blend-difference',
|
||||
i === 3 && '!mix-blend-overlay blur-2xl',
|
||||
].join(' ')}
|
||||
variants={getDay1Motion(i)}
|
||||
>
|
||||
<Image
|
||||
src={
|
||||
!!layer.mobileImg && isTablet
|
||||
? (layer.mobileImg as any)
|
||||
: layer.img
|
||||
}
|
||||
className={[
|
||||
`
|
||||
absolute opacity-90
|
||||
w-full h-full -z-10 transition-all duration-300
|
||||
`,
|
||||
i === 5 && '',
|
||||
].join(' ')}
|
||||
layout="fill"
|
||||
objectPosition={
|
||||
!!layer.mobileImg && isTablet ? '25% 80%' : '80% 50%'
|
||||
}
|
||||
objectFit="cover"
|
||||
/>
|
||||
</motion.div>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</Accordion.Item>
|
||||
</div>
|
||||
<div className="border-b pb-3">
|
||||
|
||||
@@ -83,7 +83,7 @@ export const SmallCard = ({
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'rounded-2xl text-sm px-6 py-4 flex flex-col sm:flex-row justify-between items-center backdrop-blur-md',
|
||||
'rounded-2xl text-sm px-4 sm:px-8 lg:px-10 py-4 flex flex-col sm:flex-row justify-between items-center backdrop-blur-md',
|
||||
bgGradient ? styles['lw7-article-card-gradient'] : 'bg-[#1c1c1c99]',
|
||||
].join(' ')}
|
||||
>
|
||||
@@ -143,9 +143,26 @@ export const AccordionHeader = ({ date, day, title, shipped }: any) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const ChipLink = ({ href, children }: { href: string; children: any }) => (
|
||||
<Link href={href} target="_blank" rel="noopener">
|
||||
<a className="flex items-center border border-slate-400 bg-gradient-to-r text-black dark:text-white from-[#46444480] to-[#19191980] hover:from-[#4e4e4e80] hover:to-[#19191980] backdrop-blur-sm rounded-full text-sm py-2 pl-3 pr-2">
|
||||
export const ChipLink = ({
|
||||
href,
|
||||
className,
|
||||
target,
|
||||
children,
|
||||
}: {
|
||||
href: string
|
||||
className?: string
|
||||
target?: '_blank' | '_self' | '_parent' | '_top' | 'framename'
|
||||
children: any
|
||||
}) => (
|
||||
<Link href={href}>
|
||||
<a
|
||||
target={target ?? '_self'}
|
||||
rel="noopener"
|
||||
className={[
|
||||
'flex justify-between w-full min-h-[43px] sm:w-auto items-center border border-slate-400 bg-gradient-to-r text-black dark:text-white from-[#46444480] to-[#19191980] hover:from-[#4e4e4e80] hover:to-[#19191980] backdrop-blur-sm rounded-full text-sm py-2 pl-3 pr-2',
|
||||
className,
|
||||
].join(' ')}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
</Link>
|
||||
@@ -167,7 +184,7 @@ export const SectionButtons = ({
|
||||
hackernews?: string
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex gap-2 z-10">
|
||||
<div className="flex w-full md:w-auto justify-center gap-2 z-10">
|
||||
{!!blog && (
|
||||
<ChipLink href={blog}>
|
||||
Blog post
|
||||
@@ -193,7 +210,7 @@ export const SectionButtons = ({
|
||||
</ChipLink>
|
||||
)}
|
||||
{!!github && (
|
||||
<ChipLink href={github}>
|
||||
<ChipLink href={github} target="_blank">
|
||||
View on Github
|
||||
<div className="bg-[#eeeeee] dark:bg-[#313131] rounded-full inline-block p-1 ml-2">
|
||||
<GithubSvg />
|
||||
@@ -209,7 +226,7 @@ export const SectionButtons = ({
|
||||
</ChipLink>
|
||||
)}
|
||||
{hackernews && (
|
||||
<ChipLink href={hackernews}>
|
||||
<ChipLink href={hackernews} target="_blank">
|
||||
Read more
|
||||
<div className="bg-[#eeeeee] dark:bg-[#313131] rounded-full inline-block p-1 ml-2">
|
||||
<HackernewsSvg />
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function Ticket({
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
`relative w-full max-w-[700px] lg:max-w-[1100px] flex flex-col items-center lg:grid lg:grid-cols-12 gap-4 lg:gap-8 lg:p-2 rounded-3xl backdrop-blur lg:items-stretch h-auto"`,
|
||||
`relative w-full max-w-screen md:max-w-[700px] lg:max-w-[1100px] flex flex-col items-center lg:grid lg:grid-cols-12 gap-4 lg:gap-8 lg:p-2 rounded-3xl backdrop-blur lg:items-stretch h-auto"`,
|
||||
!isMobile && styles['ticket-hero'],
|
||||
].join(' ')}
|
||||
id="wayfinding--ticket-visual-wrapper-container"
|
||||
|
||||
@@ -35,7 +35,11 @@ export default function TicketsGrid({ loadedUsers, isLoading, pageCount, offset
|
||||
const recalculatedDelay = i >= pageCount * 2 ? (i - pageCount * (offset - 1)) / 15 : i / 15
|
||||
|
||||
return (
|
||||
<Link href={`/launch-week/tickets/${user.username}`} key={`${user.username}-000${i}`}>
|
||||
<Link
|
||||
href={`/launch-week/tickets/${user.username}`}
|
||||
key={`${user.username}-000${i}`}
|
||||
onClick={() => window.scrollTo(0, 0)}
|
||||
>
|
||||
<motion.a
|
||||
className="relative w-full p-[1px] rounded-lg sm:rounded-xl overflow-hidden hover:cursor-pointer bg-gradient-to-b from-[#ffffff60] to-[#ffffff10]"
|
||||
initial={{ opacity: 0, y: 20, x: isOddRow ? 30 : -30 }}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
const images = {
|
||||
'0-ogImages-01': '/images/launchweek/seven/day0/ai-images/ai-images-01.png',
|
||||
'0-ogImages-02': '/images/launchweek/seven/day0/ai-images/ai-image-03.png',
|
||||
'0-ogImages-02': '/images/launchweek/seven/day0/ai-images/ai-image-03.svg',
|
||||
'0-ogImages-03': '/images/launchweek/seven/day0/ai-images/ai-image-04.svg',
|
||||
'0-ogImages-03-mobile': '/images/launchweek/seven/day0/ai-images/ai-image-text-input.png',
|
||||
'0-ogImages-04': '/images/launchweek/seven/day0/ai-images/images/img_3.png',
|
||||
'0-ogImages-05': '/images/launchweek/seven/day0/ai-images/ai-images-overlay.png',
|
||||
'0-supavisor-01': '/images/launchweek/seven/day0/supavisor/supavisor_01.png',
|
||||
'0-supavisor-02': '/images/launchweek/seven/day0/supavisor/supavisor_02.png',
|
||||
'01-self-hosted-logs-01': '/images/launchweek/seven/day1/self-hosted-logs-base.jpg',
|
||||
'01-self-hosted-logs-01-mobile':
|
||||
'/images/launchweek/seven/day1/self-hosted-logs-007-mobile-base.jpg',
|
||||
'01-self-hosted-logs-03': '/images/launchweek/seven/day1/self-hosted-logs-001-5.svg',
|
||||
'01-self-hosted-logs-03-mobile':
|
||||
'/images/launchweek/seven/day1/self-hosted-logs-001-5-mobile.svg',
|
||||
'01-self-hosted-logs-04': '/images/launchweek/seven/day1/self-hosted-logs-001-7.svg',
|
||||
'01-self-hosted-logs-04-mobile':
|
||||
'/images/launchweek/seven/day1/self-hosted-logs-001-7-mobile.svg',
|
||||
'01-self-hosted-logs-06': '/images/launchweek/seven/day1/self-hosted-logs-004.png',
|
||||
}
|
||||
|
||||
export interface WeekDayProps {
|
||||
@@ -69,7 +79,7 @@ const days: WeekDayProps[] = [
|
||||
{
|
||||
title: 'Supavisor',
|
||||
github: 'https://github.com/supabase/supavisor',
|
||||
// hackernews: 'hackernews_link',
|
||||
hackernews: 'https://news.ycombinator.com/item?id=35501718',
|
||||
thumb: '/images/launchweek/seven/day0/supavisor/supavisor-thumb.png',
|
||||
bg_layers: [{ img: images['0-supavisor-01'] }, { img: images['0-supavisor-02'] }],
|
||||
},
|
||||
@@ -77,16 +87,37 @@ const days: WeekDayProps[] = [
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
shipped: false,
|
||||
shipped: true,
|
||||
date: '10 Apr',
|
||||
publishedAt: '2023-04-10T07:00:00.000-07:00',
|
||||
publishedAt: '2023-04-09T07:00:00.000-07:00',
|
||||
description: '',
|
||||
d: 1,
|
||||
dd: 'Mon',
|
||||
youtube_id: '',
|
||||
blogpost: '',
|
||||
docs: '',
|
||||
steps: [],
|
||||
steps: [
|
||||
{
|
||||
title: 'Open Source Logging',
|
||||
blog: '/blog/supabase-logs-self-hosted',
|
||||
thumb: '/images/launchweek/seven/day1/self-hosted-logs-thumb.jpg',
|
||||
bg_layers: [
|
||||
{
|
||||
img: images['01-self-hosted-logs-01'],
|
||||
mobileImg: images['01-self-hosted-logs-01-mobile'],
|
||||
},
|
||||
{
|
||||
img: images['01-self-hosted-logs-03'],
|
||||
mobileImg: images['01-self-hosted-logs-03-mobile'],
|
||||
},
|
||||
{
|
||||
img: images['01-self-hosted-logs-04'],
|
||||
mobileImg: images['01-self-hosted-logs-04-mobile'],
|
||||
},
|
||||
{ img: images['01-self-hosted-logs-06'] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
|
||||
@@ -11,13 +11,14 @@ import SolutionsData from 'data/Solutions.json'
|
||||
import Solutions from '~/components/Nav/Product'
|
||||
import Developers from '~/components/Nav/Developers'
|
||||
import Announcement from '~/components/Nav/Announcement'
|
||||
import CountdownBanner from '~/components/LaunchWeek/Banners/CountdownBanner'
|
||||
import ScrollProgress from '~/components/ScrollProgress'
|
||||
|
||||
import { useIsLoggedIn, useTheme } from 'common'
|
||||
import TextLink from '../TextLink'
|
||||
import Image from 'next/image'
|
||||
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 CountdownBanner from '../LaunchWeek/Banners/CountdownBanner'
|
||||
|
||||
const Nav = () => {
|
||||
const { isDarkMode } = useTheme()
|
||||
@@ -112,7 +113,7 @@ const Nav = () => {
|
||||
|
||||
const HamburgerButton = (props: HamburgerButtonProps) => (
|
||||
<div
|
||||
className="absolute inset-y-0 left-0 flex items-center px-2 lg:hidden"
|
||||
className="absolute inset-y-0 left-0 flex items-center px-4 lg:hidden"
|
||||
onClick={() => props.toggleFlyOut()}
|
||||
>
|
||||
<button
|
||||
@@ -436,6 +437,7 @@ const Nav = () => {
|
||||
<FlyOut open={openDevelopers} handleCancel={handleCancel}>
|
||||
<Developers />
|
||||
</FlyOut>
|
||||
<ScrollProgress />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
const ScrollProgress = () => {
|
||||
const [progressPercentage, setProgressPercentage] = useState(0)
|
||||
const { pathname } = useRouter()
|
||||
|
||||
const isBlogPost = pathname.includes('/blog/')
|
||||
if (!isBlogPost) return null
|
||||
|
||||
const handleScroll = () => {
|
||||
if (typeof document === 'undefined') return null
|
||||
const article = document?.querySelector('article')
|
||||
const { top, height } = (article as any)?.getBoundingClientRect()
|
||||
let scrollDistance = -top
|
||||
let progressPercentage =
|
||||
(scrollDistance / (height - document.documentElement.clientHeight)) * 100
|
||||
setProgressPercentage(progressPercentage)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
let isActive = progressPercentage <= 100
|
||||
|
||||
return (
|
||||
<div className="h-[2px] w-full flex justify-start relative">
|
||||
<div
|
||||
className="h-full top-0 bottom-0 right-0 absolute w-screen bg-brand-900 will-change-transform transition-opacity"
|
||||
style={{
|
||||
display: isActive ? 'absolute' : 'relative',
|
||||
transform: `translate3d(${isActive ? progressPercentage - 100 + '%' : '0'},0,0)`,
|
||||
opacity: isActive ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScrollProgress
|
||||
@@ -154,6 +154,14 @@
|
||||
"enterprise": "Contact Us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Single Sign-On (SAML 2.0)",
|
||||
"tiers": {
|
||||
"free": false,
|
||||
"pro": "50 free, then $0.015 per MAU",
|
||||
"enterprise": "Contact Us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Advanced security features",
|
||||
"tiers": {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
const useActiveAnchors = (
|
||||
anchorsQuerySelector: string = 'h2',
|
||||
tocQuerySelector: string = '.prose-toc a',
|
||||
offset: number = 200
|
||||
) => {
|
||||
const anchors = useRef<NodeListOf<HTMLHeadingElement> | null>(null)
|
||||
const toc = useRef<NodeListOf<HTMLHeadingElement> | null>(null)
|
||||
|
||||
const handleScroll = () => {
|
||||
const pageYOffset = window.pageYOffset
|
||||
let newActiveAnchor: string = ''
|
||||
|
||||
anchors.current?.forEach((anchor) => {
|
||||
if (pageYOffset >= anchor.offsetTop - offset) {
|
||||
newActiveAnchor = anchor.id
|
||||
}
|
||||
})
|
||||
|
||||
toc.current?.forEach((link) => {
|
||||
link.classList.remove('toc-animate')
|
||||
// TODO: escape emojis
|
||||
// The problem is that MDXRemote strips out emojis on slugs
|
||||
// but ReactMarkdown doesn't,instead it encodes them
|
||||
const sanitizedHref = (link.getAttribute('href') ?? '').replace('#', '')
|
||||
const isMatch = sanitizedHref === newActiveAnchor
|
||||
|
||||
if (isMatch) {
|
||||
link.classList.add('toc-animate')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
anchors.current = document.querySelectorAll(anchorsQuerySelector)
|
||||
toc.current = document.querySelectorAll(tocQuerySelector)
|
||||
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default useActiveAnchors
|
||||
@@ -285,5 +285,19 @@
|
||||
"position": "Design",
|
||||
"author_url": "https://dribbble.com/marijanasimag",
|
||||
"author_image_url": "https://github.com/marijanasimag.png"
|
||||
},
|
||||
{
|
||||
"author_id": "ziinc",
|
||||
"author": "Lee TzeYiing",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/Ziinc",
|
||||
"author_image_url": "https://github.com/Ziinc.png"
|
||||
},
|
||||
{
|
||||
"author_id": "chasers",
|
||||
"author": "Chase Granberry",
|
||||
"position": "Engineering",
|
||||
"author_url": "https://github.com/chasers",
|
||||
"author_image_url": "https://github.com/chasers.png"
|
||||
}
|
||||
]
|
||||
@@ -3,11 +3,11 @@ import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
export default class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en" className="dark">
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<script async src="https://platform.twitter.com/widgets.js" />
|
||||
</Head>
|
||||
<body>
|
||||
<body className="dark">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { Badge, Divider, IconChevronLeft, IconFile } from 'ui'
|
||||
import matter from 'gray-matter'
|
||||
import authors from 'lib/authors.json'
|
||||
import { MDXRemote } from 'next-mdx-remote'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import matter from 'gray-matter'
|
||||
import authors from 'lib/authors.json'
|
||||
import { MDXRemote } from 'next-mdx-remote'
|
||||
import { Badge, Divider, IconChevronLeft } from 'ui'
|
||||
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import BlogLinks from '~/components/LaunchWeek/7/BlogLinks'
|
||||
import { generateReadingTime } from '~/lib/helpers'
|
||||
import ShareArticleActions from '~/components/Blog/ShareArticleActions'
|
||||
import useActiveAnchors from '~/hooks/useActiveAnchors'
|
||||
import mdxComponents from '~/lib/mdx/mdxComponents'
|
||||
import { mdxSerialize } from '~/lib/mdx/mdxSerialize'
|
||||
import { getAllPostSlugs, getPostdata, getSortedPosts } from '~/lib/posts'
|
||||
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
|
||||
|
||||
// table of contents extractor
|
||||
const toc = require('markdown-toc')
|
||||
@@ -65,6 +68,7 @@ export async function getStaticProps({ params }: any) {
|
||||
function BlogPostPage(props: any) {
|
||||
const content = props.blog.content
|
||||
const authorArray = props.blog.author.split(',')
|
||||
// useActiveAnchors('h2, h3, h4')
|
||||
const isLaunchWeek7 = props.blog.launchweek === 7
|
||||
|
||||
const author = []
|
||||
@@ -105,7 +109,7 @@ function BlogPostPage(props: any) {
|
||||
const toc = props.blog.toc && (
|
||||
<div className="space-y-8 py-8 lg:py-0">
|
||||
<div>
|
||||
<div className="space-x-2">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{props.blog.tags.map((tag: string) => {
|
||||
return (
|
||||
<a href={`/blog/tags/${tag}`} key={`category-badge-${tag}`}>
|
||||
@@ -199,39 +203,41 @@ function BlogPostPage(props: any) {
|
||||
<p>•</p>
|
||||
<p>{generateReadingTime(props.blog.source)}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 pt-6 md:flex-row md:gap-0 lg:gap-3">
|
||||
{author.map((author: any, i: number) => {
|
||||
return (
|
||||
<div className="mr-4 w-max" key={i}>
|
||||
<Link href={author.author_url}>
|
||||
<a className="cursor-pointer">
|
||||
<div className="flex items-center gap-3">
|
||||
{author.author_image_url && (
|
||||
<div className="w-10">
|
||||
<Image
|
||||
src={author.author_image_url}
|
||||
className="dark:border-dark rounded-full border"
|
||||
alt={`${author.author} avatar`}
|
||||
width="100%"
|
||||
height="100%"
|
||||
layout="responsive"
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-1 flex flex-col gap-3 pt-6 md:flex-row md:gap-0 lg:gap-3">
|
||||
{author.map((author: any, i: number) => {
|
||||
return (
|
||||
<div className="mr-4 w-max" key={i}>
|
||||
<Link href={author.author_url}>
|
||||
<a className="cursor-pointer">
|
||||
<div className="flex items-center gap-3">
|
||||
{author.author_image_url && (
|
||||
<div className="w-10">
|
||||
<Image
|
||||
src={author.author_image_url}
|
||||
className="dark:border-dark rounded-full border"
|
||||
alt={`${author.author} avatar`}
|
||||
width="100%"
|
||||
height="100%"
|
||||
layout="responsive"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-scale-1200 mb-0 text-sm">
|
||||
{author.author}
|
||||
</span>
|
||||
<span className="text-scale-900 mb-0 text-xs">
|
||||
{author.position}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<span className="text-scale-1200 mb-0 text-sm">
|
||||
{author.author}
|
||||
</span>
|
||||
<span className="text-scale-900 mb-0 text-xs">
|
||||
{author.position}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -267,48 +273,11 @@ function BlogPostPage(props: any) {
|
||||
</div>
|
||||
</article>
|
||||
{isLaunchWeek7 && <BlogLinks />}
|
||||
<div className="py-16">
|
||||
<div className="block lg:hidden py-8">
|
||||
<div className="text-scale-900 dark:text-scale-1000 text-sm">
|
||||
Share this article
|
||||
</div>
|
||||
<div className="mt-4 flex items-center space-x-4">
|
||||
<Link
|
||||
passHref
|
||||
href={`https://twitter.com/share?text=${props.blog.title}&url=https://supabase.com/blog/${props.blog.slug}`}
|
||||
>
|
||||
<a target="_blank" className="text-scale-900 hover:text-scale-1200">
|
||||
<svg
|
||||
height="26"
|
||||
width="26"
|
||||
viewBox="-89 -46.8 644 446.8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<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"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
passHref
|
||||
href={`https://www.linkedin.com/shareArticle?url=https://supabase.com/blog/${props.blog.slug}&title=${props.blog.title}`}
|
||||
>
|
||||
<a target="_blank" className="text-scale-900 hover:text-scale-1200">
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 5 1036 990"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path d="M0 120c0-33.334 11.667-60.834 35-82.5C58.333 15.833 88.667 5 126 5c36.667 0 66.333 10.666 89 32 23.333 22 35 50.666 35 86 0 32-11.333 58.666-34 80-23.333 22-54 33-92 33h-1c-36.667 0-66.333-11-89-33S0 153.333 0 120zm13 875V327h222v668H13zm345 0h222V622c0-23.334 2.667-41.334 8-54 9.333-22.667 23.5-41.834 42.5-57.5 19-15.667 42.833-23.5 71.5-23.5 74.667 0 112 50.333 112 151v357h222V612c0-98.667-23.333-173.5-70-224.5S857.667 311 781 311c-86 0-153 37-201 111v2h-1l1-2v-95H358c1.333 21.333 2 87.666 2 199 0 111.333-.667 267.666-2 469z" />
|
||||
</svg>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<ShareArticleActions title={props.blog.title} slug={props.blog.slug} />
|
||||
</div>
|
||||
<div className="grid gap-8 py-8 lg:grid-cols-1">
|
||||
<div>
|
||||
@@ -323,21 +292,21 @@ function BlogPostPage(props: any) {
|
||||
</div>
|
||||
{/* Sidebar */}
|
||||
<div className="col-span-12 space-y-8 lg:col-span-5 xl:col-span-3 xl:col-start-9">
|
||||
<div className="space-y-8 lg:sticky lg:top-24 lg:mb-24">
|
||||
<div className="space-y-6 lg:sticky lg:top-24 lg:mb-48">
|
||||
<div className="hidden lg:block">{toc}</div>
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<p className="text-scale-1200 text-sm">Related articles</p>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2">
|
||||
{props.relatedPosts.map((post: any, i: number) => (
|
||||
<Link href={`${post.path}`} as={`${post.path}`} key={i}>
|
||||
<div>
|
||||
<p className="cursor-pointer">
|
||||
<div className="flex gap-2">
|
||||
<div className="text-scale-900">
|
||||
{/* <div className="text-scale-900">
|
||||
<IconFile size={'small'} style={{ minWidth: '1.2rem' }} />
|
||||
</div>
|
||||
</div> */}
|
||||
<span className="text-scale-1100 hover:text-gray-1200 text-sm">
|
||||
{post.title}
|
||||
</span>
|
||||
@@ -349,11 +318,15 @@ function BlogPostPage(props: any) {
|
||||
))}
|
||||
<div className="mt-2">
|
||||
<Link href={`/blog`} passHref>
|
||||
<a className="text-scale-1100 hover:text-scale-1200 cursor-pointer text-sm">
|
||||
<a className="text-scale-1100 hover:text-scale-1200 cursor-pointer text-xs">
|
||||
View all posts
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="py-4 hidden lg:block">
|
||||
<div className="text-scale-1200 text-sm">Share this article</div>
|
||||
<ShareArticleActions title={props.blog.title} slug={props.blog.slug} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,6 +86,18 @@ export default function TicketHome({ users }: Props) {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const Ticket = () =>
|
||||
supabase && (
|
||||
<div className="w-full max-w-[100vw] px-4 flex justify-center py-8 md:py-20">
|
||||
<TicketContainer
|
||||
supabase={supabase}
|
||||
session={session}
|
||||
defaultUserData={defaultUserData}
|
||||
defaultPageState="ticket"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
@@ -116,20 +128,10 @@ export default function TicketHome({ users }: Props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SectionContainer className="relative w-full -mt-48 md:mt-[-460px] z-20 flex flex-col justify-around items-center !py-4 md:!py-8 gap-2 md:gap-4 !mx-auto">
|
||||
<div className="relative !w-full max-w-[100vw] !px-4 sm:max-w-xl md:max-w-2xl lg:max-w-7xl -mt-48 md:mt-[-460px] z-20 flex flex-col justify-around items-center !py-4 md:!py-8 gap-2 md:gap-4 !mx-auto">
|
||||
<LW7Releases />
|
||||
<div className="w-full flex justify-center py-8 md:py-14 !px-2">
|
||||
{supabase && (
|
||||
<TicketContainer
|
||||
supabase={supabase}
|
||||
session={session}
|
||||
defaultUserData={defaultUserData}
|
||||
defaultPageState="ticket"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<LaunchWeekPrizeSection className="pt-10 md:pt-20" />
|
||||
</SectionContainer>
|
||||
<LaunchWeekPrizeSection className="pt-10" ticket={Ticket} />
|
||||
</div>
|
||||
{users && <TicketBrickWall users={users} />}
|
||||
</div>
|
||||
<CTABanner />
|
||||
|
||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 869 B |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 46 KiB |