diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml index 11fda1e766c..766fddf9226 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-tests.yml @@ -5,12 +5,12 @@ name: Studio Unit Tests on: push: - branches: [master] + branches: [master, studio] paths: - 'studio/**' - 'package-lock.json' pull_request: - branches: [master] + branches: [master, studio] paths: - 'studio/**' - 'package-lock.json' diff --git a/apps/docs/DEVELOPERS.md b/apps/docs/DEVELOPERS.md index 3935d5e0ce0..5c5b74e39de 100644 --- a/apps/docs/DEVELOPERS.md +++ b/apps/docs/DEVELOPERS.md @@ -45,7 +45,7 @@ You can use any standard React components in these `.mdx` files without having t ### Reference docs for client libraries -We maintain client libraries for [Javascript](https://supabase.com/docs/reference/javascript) and [Flutter/Dart](https://supabase.com/docs/reference/dart) (with more to come). These reference docs document every object and method available for developers to use. The are assembled from different sources and work much differently than the `.mdx` Guides we just looked at. +We maintain client libraries for [JavaScript](https://supabase.com/docs/reference/javascript) and [Flutter/Dart](https://supabase.com/docs/reference/dart) (with more to come). These reference docs document every object and method available for developers to use. They are assembled from different sources and work much differently than the `.mdx` Guides we just looked at. The client libraries are essentially wrappers around the clients for the various tools we use — GoTrue, PostgREST, Storage, Functions, and Realtime. The easiest way to describe how the things fit together is to look at an example and trace where the various pieces of information are coming from. diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index fa70a94cd1a..a5d057ab713 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -578,6 +578,7 @@ export const functions = { name: 'Connecting directly to Postgres', url: '/guides/functions/connect-to-postgres', }, + { name: 'Dart Edge on Supabase', url: '/guides/functions/dart-edge' }, ], }, { diff --git a/apps/docs/pages/guides/api/quickstart.mdx b/apps/docs/pages/guides/api/quickstart.mdx index 7d645a8b5f7..f71eb970247 100644 --- a/apps/docs/pages/guides/api/quickstart.mdx +++ b/apps/docs/pages/guides/api/quickstart.mdx @@ -78,8 +78,8 @@ We'll create a database table called `todos` for storing tasks. This creates a c -- Allow anonymous access create policy "Allow anonymous access" on todos - to anon for select + to anon using (true); ``` diff --git a/apps/docs/pages/guides/functions/dart-edge.mdx b/apps/docs/pages/guides/functions/dart-edge.mdx new file mode 100644 index 00000000000..49ac9142d37 --- /dev/null +++ b/apps/docs/pages/guides/functions/dart-edge.mdx @@ -0,0 +1,192 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'functions-dart-edge', + title: 'Dart Edge', + description: 'Write your functions using Dart.', +} + +[Dart Edge](https://docs.dartedge.dev/), a project built and maintained by [Invertase](https://invertase.io/), enables Dart code to be run on different edge environments. This guide will walk you through how you can start using Dart Edge to write your backend code using Dart and run it on Supabase. + +> ⚠️ WARNING: Dart Edge is an experimental project - it's probably not ready for production usage unless you're okay with living on the 'edge'. Open issues on [Dart Edge repo](https://github.com/invertase/dart_edge) if you encounter any issues. + +## **Pre requirements** + +Make sure you have [the edge CLI](https://docs.dartedge.dev/#getting-started) installed in addition to [the Supabase CLI](https://supabase.com/docs/guides/cli#installation). + +```bash +# install the edge CLI +dart pub global activate edge +``` + +## **Setting up your first Dart Edge project** + +Create a new Dart Edge project using the `new` command on the edge CLI. This will create a familiar boilerplate for your Dart Edge project such as `main.dart` and `pubspec.yaml` file. + +```bash +edge new supabase_functions new_project +``` + +`cd` into the project and initialize a new Supabase project inside the project you just created. + +```bash +cd new_project +supabase init +``` + +Run `pub get` to install the dependencies. + +```bash +dart pub get +``` + +Open the `lib/main.dart` and look at the initial template. + +```dart +void main() { + SupabaseFunctions(fetch: (request) { + return Response("Hello from Supabase Edge Functions!"); + }); +} +``` + +`SupabaseFunctions` class has a `fetch` handler, which is expected to return a `Response` object, and has a `request` parameter, which contains information about the request such as body and headers. + +## Using Supabase Client + +Now let’s try to interact with our Supabase database. We will start by installing the dependencies. + +### Add dependencies + +Add [supabase](https://pub.dev/packages/supabase) to interact with Supabase service. + +```bash +dart pub add supabase +``` + +We also need to use a special HTTP client to HTTP requests in the edge environment. + +```bash +dart pub add edge_http_client +``` + +We also need to add the `yet_another_json_isolate` package, which is the JSON encoder/decoder that utilizes isolate. + +```bash +dart pub add yet_another_json_isolate +``` + +### Override the JSON isolate + +Since isolates are not supported in the edge, we have to override its behavior to handle JSON serialization using the main thread. + +```dart +import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; + +class EdgeIsolate implements YAJsonIsolate { + @override + Future decode(String json) { + return Future.value(jsonDecode(json)); + } + + @override + Future dispose() async {} + + @override + Future encode(Object? json) { + return Future.value(jsonEncode(json)); + } + + @override + Future initialize() async {} +} +``` + +### Initialize SupabaseClient + +At this point, you can initialize a `SupabaseClient` by passing the [EdgeHttpClient](https://pub.dev/packages/edge_http_client) and the `EdgeIsolate`. We can access the Supabase credentials through the [environment variables](https://supabase.com/docs/guides/functions/secrets) using `Deno.env.get()` method. Note that [Supabase credentials are available by default](https://supabase.com/docs/guides/functions/secrets), so no configurations are necessary. + +Combining the isolates we overrode earlier, we have the following sample functions code to query Supabase database using the Supabase client. + +```dart +import 'dart:convert'; + +import 'package:supabase_functions/supabase_functions.dart'; +import 'package:edge_http_client/edge_http_client.dart'; +import 'package:supabase/supabase.dart'; +import 'package:yet_another_json_isolate/yet_another_json_isolate.dart'; + +void main() { + final supabase = SupabaseClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, // Use service role key to bypass RLS + httpClient: EdgeHttpClient(), + isolate: EdgeIsolate(), + ); + + SupabaseFunctions(fetch: (request) async { + // You can query `public.users` table for example. + final List users = await supabase.from('users').select().limit(10); + return Response.json(users); + }); +} + +class EdgeIsolate implements YAJsonIsolate { + @override + Future decode(String json) { + return Future.value(jsonDecode(json)); + } + + @override + Future dispose() async {} + + @override + Future encode(Object? json) { + return Future.value(jsonEncode(json)); + } + + @override + Future initialize() async {} +} +``` + +## Run the Functions + +### Run locally + +Run the following command to compile your Dart code. This command will start a watcher that will recompile anytime you make changes to your code. + +```bash +edge build supabase_functions --dev +``` + +With docker running, start your local Supabase with the following command + +```bash +supabase start +``` + +While you have the above code running, open another terminal and run the following to start the Supabase local development environment. + +```bash +supabase functions serve dart_edge --no-verify-jwt +``` + +You should be able to access your local function here: + +[http://localhost:54321/functions/v1/dart_edge](http://localhost:54321/functions/v1/dart_edge) + +### Deploy to the Edge + +Run the following commands to compile and deploy your function on Supabase Edge functions. + +```bash +edge build supabase_functions +supabase functions deploy dart_edge +``` + +You will be asked to provide the project reference of your Supabase instance to which you want to deploy the function. You can [link your local project to your Supabase instance](https://supabase.com/docs/guides/cli/local-development#link-your-project) to avoid having to provide the Supabase reference every time you deploy. + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/support.mdx b/apps/docs/pages/support.mdx deleted file mode 100644 index 175fb0d0c9b..00000000000 --- a/apps/docs/pages/support.mdx +++ /dev/null @@ -1,23 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'support', - title: 'Support', - description: 'Supabase Support', -} - -How can we help? - -## Community Support - -For help and questions about best practices, we have a discussion forum set up on GitHub: https://github.com/supabase/supabase/discussions - -## Business Support - -We offer email based support for business users. You can email us at support@supabase.com. - -If you need SLAs, guaranteed response times, or other enterprise level services, please contact us at this email address. - -## Slack Support - -If you are building with Supabase and you would like to set up a shared Slack channel (via [Slack Connect](https://slack.com/connect)), please reach out to us at support@supabase.com. diff --git a/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx b/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx index 2a82e547dce..d3952629f74 100644 --- a/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx +++ b/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx @@ -33,7 +33,11 @@ After exploring a variety of text prompts, blends, and styles, we decided that m After what-seemed-like hours (okay, just a few), we were able to generate some interesting artwork that could be useful. We chose purple hues and simpler swirls - minimalist visuals that could serve as backgrounds for our tickets and landing page. -![midjourney variations](/images/blog/launch-week-7/designing-with-ai-midjourney/midjourney-variations.png) + ### Chasing Gold Behind the Scenes diff --git a/apps/www/_blog/2023-04-07-launch-week-7-hackathon.mdx b/apps/www/_blog/2023-04-07-launch-week-7-hackathon.mdx index de89693f3be..8ea19a22a51 100644 --- a/apps/www/_blog/2023-04-07-launch-week-7-hackathon.mdx +++ b/apps/www/_blog/2023-04-07-launch-week-7-hackathon.mdx @@ -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 extremely limited edition Launch Week themed mechanical keyboards and limited edition Supabase swag. +The hackathon starts **now** and ends Sunday 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). diff --git a/apps/www/_blog/2023-04-13-supabase-auth-sso-pkce.mdx b/apps/www/_blog/2023-04-13-supabase-auth-sso-pkce.mdx new file mode 100644 index 00000000000..c1a4dfb36ea --- /dev/null +++ b/apps/www/_blog/2023-04-13-supabase-auth-sso-pkce.mdx @@ -0,0 +1,252 @@ +--- +title: 'Supabase Auth: SSO, Mobile, and Server-side support' +launchweek: 7 +tags: + - launch-week + - auth +date: '2023-04-13' +toc_depth: 3 +author: stojan,joel,kangmingtay +image: launch-week-7/day-4-supabase-auth-sso-pkce/supabase-auth-sso-pkce-thumb.png +thumb: launch-week-7/day-4-supabase-auth-sso-pkce/supabase-auth-sso-pkce-og.jpg +--- + +Today we're excited to announce a few new features for Supabase Auth: + +1. [Easily add Single Sign-On support to your projects using SAML 2.0](/blog/supabase-auth-sso-pkce#single-sign-on-support-using-saml-20) +2. [Better support for server-side rendering and mobile apps using PKCE](/blog/supabase-auth-sso-pkce#server-side-and-mobile-auth) +3. [Native Apple login on iOS](/blog/supabase-auth-sso-pkce#native-apple-login-on-ios) + +## Single Sign-On Support using SAML 2.0 + +With Single Sign-On (SSO), your users can login with their company's identity provider (IDP), a critical feature when you're building applications for Enterprises. + +Every developer building a B2B application eventually needs the SSO authentication flow to onboard enterprise customers. SSO is a requirement for larger Enterprise customers because it's a standard request in Enterprise Security Policies. Over the past few months, we've been [dogfooding SSO for our own Enterprise customers](https://supabase.com/docs/guides/platform/sso), and today we're releasing it for you to do the same. + +Building SSO into your application isn't necessarily hard, but does come with some complexity. A lot of time can be spent understanding the nuances and details of the protocol - from dissecting the jargon to testing the implementation heavily. It took us months to build it for ourselves. With this release, you will have SSO set up and running in less than an hour so that you can focus on shipping the core features of your product. This feature is available for the [Pro-tier and above](https://supabase.com/pricing), starting today. This will also be available on the self-hosted version. + +
+ +
+ +### Getting Started with SAML 2.0 + +To get started, enable the [“SAML 2.0“](https://app.supabase.com/project/phcnitosaawbzytgyznx/auth/providers) [authentication method in the dashboard](https://app.supabase.com/project/_/auth/providers). We've added new commands to the [Supabase CLI](https://supabase.com/docs/guides/cli) to help with the configuration process: + +```bash +$ supabase sso --help +Manage Single Sign-On (SSO) authentication for projects + +Usage: + supabase sso [command] + +Available Commands: + add Add a new SSO identity provider + info Returns the SAML SSO settings required for the identity provider + list List all SSO identity providers for a project + remove Remove an existing SSO identity provider + show Show information about an SSO identity provider + update Update information about an SSO identity provider +``` + +Once you've added a new SSO identity provider to your project, it's as simple as calling the `signInWithSSO()` from the `supabase-js` library: + +```tsx +const { data } = await supabase.auth.signInWithSSO({ domain: 'acme.corp' }) + +if (data.url) window.location.href = data.url +``` + +### SSO with Row Level Security and multi-tenancy + +As usual, we've engineered this feature around the excellent capabilities of PostgreSQL. + +For example, you can use Row Level Security (RLS) to build multi-tenant applications, simply by using the provider's unique identifier in the user's JWT: + +```sql +create policy "Only allow read-write access to tenants" on tablename as restrictive to authenticated using ( + tenant_id = (auth.jwt () -> 'app_metadata' ->> 'provider') +); +``` + +The journey to enterprise readiness isn't an end goal, it is a continuous process that demands constant attention and maintenance. With Supabase Auth, your team can offload this engineering burden to us and prioritize the features that matter. + +## Server-Side and Mobile Auth + +Many developers today are using Supabase to build mobile apps, and server-side rendering is becoming popular (again!). This release will add support for these use cases by introducing the _Proof Key for Code Exchange flow (PKCE)_ authentication flow. This improves security for mobile apps and makes building server-first apps simple. Since this is a major update that touches many of the authentication routes, we will be rolling it out gradually over the next few weeks. + +### A brief history of Supabase Auth + +When we [launched](https://news.ycombinator.com/item?id=24072051) Supabase Auth, our target was JAMstack developers. In these cases, the protocol used between the user's application and Supabase Auth is known as the [Implicit Grant Flow](https://www.rfc-editor.org/rfc/rfc6749#section-4.2): + +diagram reference + +diagram reference + +As developers built more complex apps, they encountered two problems with this authentication flow: + +- **Server-Side Email Verification Links** + Data provided in a URL fragment is only accessible in a browser environment, not on the server. This is problematic for email verification links that redirect users to a server-side route. +- **Challenges with Mobile App Authentication** + The implicit grant flow raised security concerns for mobile use cases since [malicious apps could potentially obtain the user session](https://www.rfc-editor.org/rfc/rfc7636#section-1). + +Server-side auth unlocks a number of benefits. Developers can: + +- Set cookies on the same domain as the application. +- Enable server-side rendering for protected pages. +- Perform downstream actions after user authentication, such as adding the user to a CRM or sending analytics. + +### Introducing PKCE + +To solve these problems, we're introducing support for the _Proof Key for Code Exchange_ flow (PKCE, pronounced “pixy”). + +The PKCE flow introduces a _code verifier_ (a randomly generated secret) and a _code challenge_ (the hash of the _code verifier_). The authorization code is returned as a query parameter so it's accessible on the server. During the PKCE flow: + +1. The _code challenge_ is sent to Supabase Auth, which returns an _authorization code._ +2. The client sends the _authorization code_ together with the _code verifier_ to obtain the user's session. +3. Supabase Auth checks if the _code verifier_ matches the _code challenge_ sent earlier by computing the hash. This renders a malicious attacker's attempt to intercept the authorization code useless, since they need to know the value of the _code verifier_ as well. + +diagram reference + +diagram reference + +### Migrating to PKCE on the client + +Over the next few weeks, you'll be able to use it with the Supabase libraries. We've already added PKCE to the [JavaScript](https://supabase.com/docs/reference/javascript/installing) client library and our [auth-helpers](https://supabase.com/docs/guides/auth/auth-helpers) library. If you're using `supabase-js` , you can switch to PKCE by initializing your client with the following option: + +```tsx +import { createClient } from '@supabase/supabase-js' + +const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, { + auth: { + flowType: 'pkce', + }, +}) +``` + +For client-side auth, that's all you need to do to switch over. `supabase-js` will handle the generation and storage for the code verifier, as well as exchanging the authorization code for the user's session. + +### Migrating to PKCE on the server + +Server-side authentication is now a lot easier. Let's look at an example using NextJS. + +Install the `next` version of auth-helpers (lets use the `nextjs` version for this example) + +```bash +npm install @supabase/auth-helpers-nextjs@next +``` + +Then prepare an endpoint for the sign in process. The redirect URL is set to `/api/auth/callback`, which will be implemented next. + +```tsx +// api/auth/login +import { NextApiRequest, NextApiResponse } from 'next' +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // Create the Supabase Client + const supabase = createServerSupabaseClient( + { req, res }, + { + supabaseUrl: process.env.SUPABASE_URL, + supabaseKey: process.env.SUPABASE_ANON_KEY, + } + ) + + // Start sign in with one-time password + const { error } = await supabase.auth.signInWithOtp({ + email: 'foo@example.com', + options: { + emailRedirectTo: 'http://localhost:3000/api/auth/callback', + }, + }) + + if (error) { + res.json(JSON.stringify(error)) + } + + res.redirect('/') +} +``` + +Now we can set up the callback API endpoint: + +```tsx +// api/auth/callback +import { NextApiRequest, NextApiResponse } from 'next' +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient( + { req, res }, + { + supabaseUrl: SUPABASE_URL, + supabaseKey: SUPABASE_ANON_KEY, + } + ) + // check for code in url querystring + const code = req.query.code + + if (typeof code === 'string') { + // exchange the auth code for user session + await supabase.auth.exchangeCodeForSession(code) + } + + // redirect the user to a server-side protected area in your app + res.redirect('/') +} +``` + +### Roll out + +Since this is a major update that touches many of the authentication routes, we will roll it out gradually over the next few weeks. You will receive a notification in your dashboard when the feature is available for your project. Reach out to us if you want early access to this feature. + +## Native Apple login on iOS + +While PKCE support is great, that is not the only news for you mobile app developers out there. + +Building apps for iOS requires [^1] support for native _Sign in with Apple_. We heard the community's requests for native sign-in. We hope you join our excitement to officially announce support for native _Sign in with Apple_. + +Your app's iOS Bundle ID can now be configured in the Apple provider section of your project's dashboard. + +![native-apple-login-on-iOS.png](/images/blog/launch-week-7/day-4-supabase-auth-sso-pkce/native-apple-login-on-iOS.png) + +This is the only prerequisite for triggering a native _Sign in with Apple._ With [supabase-flutter](https://pub.dev/packages/supabase_flutter#native-sign-in-with-apple-example), this is as easy as: + +```dart +final AuthResponse response = await supabase.auth.signInWithApple(); +``` + +It's that easy! No need to set up deep links, no need to pass any parameters. + +We're just starting with Apple login, and soon add support for Google login. + +## Wrapping Up + +Supabase Auth aims to continue developing auth features that are secure by default yet simple to implement. We use Supabase Auth for our hosted platform and continuously dogfood the latest version on it. If you are interested to migrate to Supabase Auth, you can check out this blog post on how [Parqet migrated 125,000 users from Auth0 to Supabase Auth](https://kevcodez.medium.com/migrating-125-000-users-from-auth0-to-supabase-81c0568de307). + +[^1]: [App store review guidelines](https://developer.apple.com/app-store/review/guidelines/#sign-in-with-apple) diff --git a/apps/www/components/Blog/BlogListItem.tsx b/apps/www/components/Blog/BlogListItem.tsx index c08b5006db7..423c6b077be 100644 --- a/apps/www/components/Blog/BlogListItem.tsx +++ b/apps/www/components/Blog/BlogListItem.tsx @@ -1,5 +1,6 @@ import authors from 'lib/authors.json' import Image from 'next/image' +import Link from 'next/link' import React from 'react' import Author from '~/types/author' import PostTypes from '~/types/post' @@ -24,8 +25,8 @@ const BlogListItem = ({ post }: Props) => { return (
- - ) } diff --git a/apps/www/components/Blog/ShareArticleActions.tsx b/apps/www/components/Blog/ShareArticleActions.tsx index f4d68458657..56b6ede83a6 100644 --- a/apps/www/components/Blog/ShareArticleActions.tsx +++ b/apps/www/components/Blog/ShareArticleActions.tsx @@ -51,8 +51,8 @@ const ShareArticleActions = ({ title, slug }: { title: string; slug: string }) = xmlns="http://www.w3.org/2000/svg" > diff --git a/apps/www/components/LaunchWeek/7/BlogLinks.tsx b/apps/www/components/LaunchWeek/7/BlogLinks.tsx index 9d7a3bf6b30..d7087d7e3b9 100644 --- a/apps/www/components/LaunchWeek/7/BlogLinks.tsx +++ b/apps/www/components/LaunchWeek/7/BlogLinks.tsx @@ -36,8 +36,8 @@ const BlogLinks = () => {
{step.title} diff --git a/apps/www/components/LaunchWeek/Releases/LW7/LW7Releases.tsx b/apps/www/components/LaunchWeek/Releases/LW7/LW7Releases.tsx index 122a0b64a15..ed7d4ccc23f 100644 --- a/apps/www/components/LaunchWeek/Releases/LW7/LW7Releases.tsx +++ b/apps/www/components/LaunchWeek/Releases/LW7/LW7Releases.tsx @@ -19,44 +19,56 @@ import { useMobileViewport } from '../../../../hooks/useMobileViewport' import { motion } from 'framer-motion' const defaultEase = [0.25, 0.25, 0, 1] +const defaultDuratonIn = 0.25 +const defaultDuratonOut = 0.1 const opacityVariant = { - default: { opacity: 0.9, ease: defaultEase, duration: 0.2 }, + default: { opacity: 0.9, ease: defaultEase, duration: defaultDuratonOut }, hover: { opacity: 1, transition: { - duration: 0.4, + duration: defaultDuratonIn, ease: defaultEase, }, }, } const opacityVariant2 = { - default: { opacity: 0, ease: defaultEase, duration: 0.2 }, + default: { opacity: 0, ease: defaultEase, duration: defaultDuratonOut }, hover: { opacity: 0.2, transition: { - duration: 0.4, + duration: defaultDuratonIn, ease: defaultEase, }, }, } const opacityVariant3 = { - default: { opacity: 0, ease: defaultEase, duration: 0.2 }, + default: { opacity: 0, ease: defaultEase, duration: defaultDuratonOut }, hover: { opacity: 1, transition: { - duration: 0.4, + duration: defaultDuratonIn, + ease: defaultEase, + }, + }, +} +const opacityVariant4 = { + default: { opacity: 0.3, ease: defaultEase, duration: defaultDuratonOut }, + hover: { + opacity: 1, + transition: { + duration: defaultDuratonIn, ease: defaultEase, }, }, } const scaleOpacityVariant = { - default: { scale: 1, opacity: 0.9, ease: defaultEase, duration: 0.2 }, + default: { scale: 1, opacity: 0.9, ease: defaultEase, duration: defaultDuratonOut }, hover: { scale: 1.05, opacity: 1, transition: { - duration: 0.4, + duration: defaultDuratonIn, ease: defaultEase, }, }, @@ -68,11 +80,11 @@ const aiImageMobileVariant = { }, } const moveX10 = { - default: { x: 0, ease: defaultEase, duration: 0.2 }, + default: { x: 0, ease: defaultEase, duration: defaultDuratonOut }, hover: { x: 10, transition: { - duration: 0.4, + duration: defaultDuratonIn, ease: defaultEase, }, }, @@ -107,6 +119,16 @@ const getDay3Motion = (index: number) => { return undefined } } +const getDay4Motion = (index: number) => { + switch (index) { + case 0: + return opacityVariant4 + // case 2: + // return opacityVariant3 + default: + return undefined + } +} export default function LW7Releases() { const [preRelease, day1, day2, day3, day4, day5] = days @@ -153,8 +175,8 @@ export default function LW7Releases() { xmlns="http://www.w3.org/2000/svg" > @@ -187,14 +209,14 @@ export default function LW7Releases() { xmlns="http://www.w3.org/2000/svg" > @@ -255,7 +277,7 @@ export default function LW7Releases() { background: `radial-gradient(90% 130px at 80% 0px, #4635A7, transparent)`, }} /> -
+
{preRelease.steps[0].title} Guide
@@ -379,13 +401,13 @@ export default function LW7Releases() { -
+
{day1.steps[0].title} New
@@ -427,7 +449,7 @@ export default function LW7Releases() { } className={[ ` - absolute opacity-90 + absolute w-full h-full -z-10 transition-all duration-300 `, i === 5 && '', @@ -467,13 +489,13 @@ export default function LW7Releases() { -
+
{day2.steps[0].title} New
@@ -515,7 +537,7 @@ export default function LW7Releases() { } className={[ ` - absolute opacity-90 + absolute w-full h-full -z-10 transition-all duration-300 `, i === 5 && '', @@ -555,13 +577,13 @@ export default function LW7Releases() { -
+
{day3.steps[0].title} Updated
@@ -602,7 +624,7 @@ export default function LW7Releases() { } className={[ ` - absolute opacity-90 + absolute w-full h-full -z-10 transition-all duration-300 `, i === 5 && '', @@ -637,7 +659,75 @@ export default function LW7Releases() { className="h-[79px]" id={day4.d.toString()} > -
+ {day4.steps.length > 0 && ( +
+ +
+ {day4.steps[0].title} + New +
+ + {day4.steps[0].bg_layers && + day4.steps[0].bg_layers?.map((layer, i) => + !!layer.lottie ? ( +
+ +
+ ) : ( + !!layer.img && ( + + + + ) + ) + )} +
+
+ )}
diff --git a/apps/www/components/LaunchWeek/Releases/LW7/components/index.tsx b/apps/www/components/LaunchWeek/Releases/LW7/components/index.tsx index 31527f8701c..632764ee29d 100644 --- a/apps/www/components/LaunchWeek/Releases/LW7/components/index.tsx +++ b/apps/www/components/LaunchWeek/Releases/LW7/components/index.tsx @@ -175,6 +175,7 @@ export const SectionButtons = ({ github, url, hackernews, + mobileGrid, }: { blog?: string docs?: string @@ -182,9 +183,15 @@ export const SectionButtons = ({ github?: string url?: string hackernews?: string + mobileGrid?: boolean }) => { return ( -
+
{!!blog && ( Blog post diff --git a/apps/www/components/LaunchWeek/lw7_days.ts b/apps/www/components/LaunchWeek/lw7_days.ts index 05fa19b1d53..10ec823035c 100644 --- a/apps/www/components/LaunchWeek/lw7_days.ts +++ b/apps/www/components/LaunchWeek/lw7_days.ts @@ -28,6 +28,11 @@ const images = { '03-storage-02': '/images/launchweek/seven/day3/storage-02.png', '03-storage-02-mobile': '/images/launchweek/seven/day3/storage-02-mobile.png', '03-storage-03': '/images/launchweek/seven/day3/storage-03.png', + '04-sso-thumb': '/images/launchweek/seven/day4/sso-support-thumb.jpg', + '04-sso-01': '/images/launchweek/seven/day4/sso-support-01.png', + '04-sso-01-mobile': '/images/launchweek/seven/day4/sso-support-01-mobile.jpg', + '04-sso-02': '/images/launchweek/seven/day4/sso-support-02.png', + '04-sso-02-mobile': '/images/launchweek/seven/day4/sso-support-02-mobile.png', } export interface WeekDayProps { @@ -150,7 +155,7 @@ const days: WeekDayProps[] = [ blog: '/blog/edge-runtime-self-hosted-deno-functions', thumb: images['02-self-hosted-edge-functions-thumb'], video: 'https://www.youtube.com/watch?v=cPGxPl1lx4Y', - hackernews: 'https://news.ycombinator.com/item?id=30868849', + hackernews: 'https://news.ycombinator.com/item?id=35525222', bg_layers: [ { img: images['02-self-hosted-edge-functions-01'], @@ -166,7 +171,7 @@ const days: WeekDayProps[] = [ title: '', shipped: true, date: '12 Apr', - publishedAt: '2023-04-12T00:00:00.000-07:00', + publishedAt: '2023-04-12T07:00:00.000-07:00', description: '', d: 3, dd: 'Wed', @@ -199,16 +204,37 @@ const days: WeekDayProps[] = [ }, { title: '', - shipped: false, + shipped: true, date: '13 Apr', - publishedAt: '2023-04-13T07:00:00.000-07:00', + publishedAt: '2023-04-13T00:00:00.000-07:00', description: '', d: 4, dd: 'Thu', youtube_id: '', blogpost: '', docs: '', - steps: [], + steps: [ + { + title: 'Supabase Auth: SSO, Mobile, and Server-side support', + break_thumb_title: true, + description: '', + blog: '/blog/supabase-auth-sso-pkce', + thumb: images['04-sso-thumb'], + video: 'https://www.youtube.com/watch?v=hAwJeR6mhB0', + docs: '/docs/guides/auth/sso/auth-sso-saml', + hackernews: 'https://news.ycombinator.com/item?id=35555263', + bg_layers: [ + { + img: images['04-sso-01'], + mobileImg: images['04-sso-01-mobile'], + }, + { + img: images['04-sso-02'], + mobileImg: images['04-sso-02-mobile'], + }, + ], + }, + ], }, { title: '', diff --git a/apps/www/components/Quote.tsx b/apps/www/components/Quote.tsx index f55e82ac291..2c1b1bcc48d 100644 --- a/apps/www/components/Quote.tsx +++ b/apps/www/components/Quote.tsx @@ -11,12 +11,17 @@ export default function Quote(props: Props) { const { children, caption, img } = props return ( -
-
-
+
+
+

{children} -

- +

+ {caption && img && ( +
+ +
+ )} +
) diff --git a/apps/www/data/Community.tsx b/apps/www/data/Community.tsx index 95c35119b19..dfe7a708131 100644 --- a/apps/www/data/Community.tsx +++ b/apps/www/data/Community.tsx @@ -30,7 +30,7 @@ const data: CommunityItem[] = [ }, { title: 'GitHub', - stat: '46,000+', + stat: '48,000+', statLabel: 'GitHub stars', img: 'github.png', invertImgDarkMode: true, diff --git a/apps/www/hooks/useActiveAnchors.tsx b/apps/www/hooks/useActiveAnchors.tsx index 00a48824387..8dd569c6f5f 100644 --- a/apps/www/hooks/useActiveAnchors.tsx +++ b/apps/www/hooks/useActiveAnchors.tsx @@ -1,10 +1,13 @@ +import { useRouter } from 'next/router' import { useEffect, useRef } from 'react' +import { isBrowser, stripEmojis } from '~/lib/helpers' const useActiveAnchors = ( anchorsQuerySelector: string = 'h2', tocQuerySelector: string = '.prose-toc a', offset: number = 200 ) => { + const router = useRouter() const anchors = useRef | null>(null) const toc = useRef | null>(null) @@ -20,11 +23,13 @@ const useActiveAnchors = ( 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 + + // Need to decodeURI the href to get emojis, then strip them off and remove "--"" + // to make toc hrefs and content headings ids match + const sanitizedHref = stripEmojis( + decodeURI(link.getAttribute('href') ?? '').replace('#', '') + ).replaceAll('-', '') + const isMatch = sanitizedHref === newActiveAnchor.replaceAll('-', '') if (isMatch) { link.classList.add('toc-animate') @@ -33,6 +38,7 @@ const useActiveAnchors = ( } useEffect(() => { + if (!isBrowser || !router.isReady) return anchors.current = document.querySelectorAll(anchorsQuerySelector) toc.current = document.querySelectorAll(tocQuerySelector) @@ -41,7 +47,7 @@ const useActiveAnchors = ( return () => { window.removeEventListener('scroll', handleScroll) } - }, []) + }, [router]) return null } diff --git a/apps/www/lib/helpers.tsx b/apps/www/lib/helpers.tsx index fbf8fe599dd..eb8495ca624 100644 --- a/apps/www/lib/helpers.tsx +++ b/apps/www/lib/helpers.tsx @@ -31,3 +31,12 @@ export const toPixels = (value: string | number) => { } export const isBrowser = typeof window !== 'undefined' + +export const stripEmojis = (str: string) => + str + .replace( + /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g, + '' + ) + .replace(/\s+/g, ' ') + .trim() diff --git a/apps/www/lib/mdx/mdxComponents.tsx b/apps/www/lib/mdx/mdxComponents.tsx index a54bee5c0ad..e08739ca7bb 100644 --- a/apps/www/lib/mdx/mdxComponents.tsx +++ b/apps/www/lib/mdx/mdxComponents.tsx @@ -12,6 +12,18 @@ import { Badge } from 'ui' const ignoreClass = 'ignore-on-export' +const getCaptionAlign = (align?: 'left' | 'center' | 'right') => { + switch (align) { + case 'left': + return 'text-left' + case 'right': + return 'text-right' + case 'center': + default: + return 'text-center' + } +} + export default function mdxComponents(type?: 'blog' | 'lp' | undefined) { const components = { CodeBlock, @@ -43,6 +55,22 @@ export default function mdxComponents(type?: 'blog' | 'lp' | undefined) { } return }, + Img: (props: any) => ( +
+ + + + {props.caption && ( +
+ {props.caption} +
+ )} +
+ ), code: (props: any) => {props.children}, } diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index f285f916040..06bcb44ca6e 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1903,4 +1903,9 @@ module.exports = [ source: '/docs/guides/integrations/dashibase', destination: '/docs', }, + { + permanent: true, + source: '/docs/support', + destination: '/support', + }, ] diff --git a/apps/www/pages/_document.tsx b/apps/www/pages/_document.tsx index 1aec4af5553..3abbdca1b66 100644 --- a/apps/www/pages/_document.tsx +++ b/apps/www/pages/_document.tsx @@ -4,9 +4,7 @@ export default class MyDocument extends Document { render() { return ( - -