diff --git a/apps/docs/components/MDX/kotlin_project_setup.mdx b/apps/docs/components/MDX/kotlin_project_setup.mdx index c052565ede4..fbf2e6ebd09 100644 --- a/apps/docs/components/MDX/kotlin_project_setup.mdx +++ b/apps/docs/components/MDX/kotlin_project_setup.mdx @@ -20,15 +20,15 @@ Now we are going to set up the database schema. You can just copy/paste the SQL scrollable size="small" type="underlined" - defaultActiveId="dashboard" + defaultActiveId="sql" > - +{/* 1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard. 2. Click **Product Management**. 3. Click **Run**. - + */} @@ -46,25 +46,8 @@ We just need to get the Project URL and `anon` key from the API settings. ### Set up Google Authentication -From [Google Console](https://console.developers.google.com/apis/library), create a Project -In the project, create new credential +From the [Google Console](https://console.developers.google.com/apis/library), create a new project and add OAuth2 credentials. -Screenshot 2023-07-15 at 20 47 31 +![Create Google OAuth credentials](/docs/img/guides/kotlin/google-cloud-oauth-credentials-create.png) -Then go to Supabase dashboard, in Authentication tab, open Providers to enable Google. Turn on toggle then copy and paste Client ID and Secret from Google Cloud - -Screenshot 2023-07-15 at 20 48 26 - -Screenshot 2023-07-15 at 20 49 34 +In your [Supabase Auth settings](https://app.supabase.com/project/_/auth/providers) enable Google as a provider and set the required credentials as outlined in the [auth docs](/docs/guides/auth/social-login/auth-google). diff --git a/apps/docs/docs/ref/kotlin/introduction.mdx b/apps/docs/docs/ref/kotlin/introduction.mdx index 7f1f3784175..914310ba347 100644 --- a/apps/docs/docs/ref/kotlin/introduction.mdx +++ b/apps/docs/docs/ref/kotlin/introduction.mdx @@ -18,15 +18,17 @@ This reference documents every object and method available in Supabase's Kotlin Supported Kotlin targets: -| **Module** | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** | -| -------------------------------------- | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ | -| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **JS** _(Browser)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ | -| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ | +| | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** | +| ------------------------------------------------------------------ | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ | +| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **JS** _(Browser, NodeJS)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **tvOS** _(tvosArm64, tvosX64, tvosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **watchOS** _(watchosArm64, watchosX64, watchosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ | +| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ = full support diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index b24019de139..59bead2a64d 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -8,6 +8,7 @@ import Head from 'next/head' import { PropsWithChildren, memo } from 'react' import Footer from '~/components/Navigation/Footer' import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' +import { Announcement, LW8CountdownBanner } from 'ui' const levelsData = { home: { @@ -326,6 +327,9 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => { Supabase Docs
+ + +
diff --git a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx index 8256063a9b8..ed2aea88f97 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx @@ -92,7 +92,7 @@ Options are available via `queryParams`: prompt: 'consent', hd: 'domain.com', }} - onlyThirdPartyProviders={true} + onlyThirdPartyProviders /> ``` diff --git a/apps/docs/pages/guides/cli/local-development.mdx b/apps/docs/pages/guides/cli/local-development.mdx index 4faeb3f900f..1682de48d8c 100644 --- a/apps/docs/pages/guides/cli/local-development.mdx +++ b/apps/docs/pages/guides/cli/local-development.mdx @@ -403,8 +403,8 @@ To use Auth locally, update your project's `supabase/config.toml` file that gets ```bash supabase/config.toml [auth.external.github] enabled = true -client_id = "env($SUPABASE_AUTH_GITHUB_CLIENT_ID)" -secret = "env($SUPABASE_AUTH_GITHUB_SECRET)" +client_id = "env(SUPABASE_AUTH_GITHUB_CLIENT_ID)" +secret = "env(SUPABASE_AUTH_GITHUB_SECRET)" redirect_uri = "http://localhost:54321/auth/v1/callback" ``` diff --git a/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx index 55592b402aa..91f5a535fa5 100644 --- a/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx +++ b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx @@ -4,7 +4,7 @@ import StepHikeCompact from '~/components/StepHikeCompact' export const meta = { title: 'Use Supabase with Android Kotlin', subtitle: - 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Android Kotlin app.', + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.', breadcrumb: 'Framework Quickstarts', } @@ -45,8 +45,7 @@ export const meta = { - Setup environment for Android Development following official document. - Open Android Studio > New > New Android Project + Open Android Studio > New > New Android Project. @@ -55,16 +54,7 @@ export const meta = { - Setup Supabase libraries requires some others libraries from ktor, so make sure all dependencies are added. - Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with the latest versions as shown below. - + Import Supabase and all required dependencies. Replace the version placeholders `$supabase_version` and `$ktor_version` with the respective latest versions. @@ -83,10 +73,7 @@ export const meta = { - Open `build.gradle` (app), add serialization plugin to use annotation for data parsing. Please note that the plugin version should be the same with Kotlin version in your app. - - latest stable supabase-kt version - + Open the `build.gradle` (app), add the serialization plugin to use annotation for data parsing. Please note that the plugin version should be the same as the Kotlin version in your app. @@ -105,14 +92,12 @@ export const meta = { - You can create a whenever you need to perform some API call. - However, it is recommended that we should provide this kind of instance by using dependency injection library like Hilt. So see the code below to provide them properly. This is better for testability and maintainability + You can create a Supabase client whenever you need to perform an API call. That being said, it is recommended to use a dependency injection library like [Hilt](https://developer.android.com/training/dependency-injection/hilt-android#kts). ```kotlin - // Handy create instance when need val client = createSupabaseClient( supabaseUrl = "https://xyzcompany.supabase.co", supabaseKey = "public-anon-key" @@ -129,7 +114,7 @@ export const meta = { - + @@ -161,8 +146,8 @@ export const meta = { - - This kind of object will be consumed by the view + + This kind of object will be consumed by the view. @@ -186,7 +171,7 @@ export const meta = { - Usually, we will put the code of interacting with data source in repository. Create a repository: + Create a repository to interact with the data source. @@ -212,7 +197,7 @@ export const meta = { - For dependency injection, we will use Hilt. Learn more about [Dependency injection with Hilt](https://developer.android.com/training/dependency-injection/hilt-android). + Use [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) for dependency injection. ```kotlin @@ -229,7 +214,7 @@ export const meta = { - To use the repository in a ViewModel, use the `@Inject` annotation. + Add the `@Inject` annotation to use the repository in a ViewModel. ```kotlin @@ -269,7 +254,7 @@ export const meta = { - + ```kotlin diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx index ecb16a0a42d..8f295c4233e 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx @@ -1,17 +1,17 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - title: 'Build a Product Management App with Jetpack Compose in Android', + title: 'Build a Product Management Android App with Jetpack Compose', description: 'Learn how to use Supabase in your Android Kotlin App.', } -This tutorial demonstrates how to build a basic product management app. The app demonstrates management operations, photo upload, account creation and authentication +This tutorial demonstrates how to build a basic product management app. The app demonstrates management operations, photo upload, account creation and authentication using: - [Supabase Database](/docs/guides/database) - a Postgres database for storing your user data and [Row Level Security](/docs/guides/auth#row-level-security) so data is protected and users can only access their own information. -- [Supabase Auth](/docs/guides/auth) - users log in through magic links sent to their email (without having to set up passwords). +- [Supabase Auth](/docs/guides/auth) - users log in through magic links sent to their email (without having to set up a password). - [Supabase Storage](/docs/guides/storage) - users can upload a profile photo. -![manage-product-cover](https://github.com/hieuwu/product-sample-supabase-kt/assets/43868345/1bed0c84-208a-4266-a2ec-2bac50ddf80c) +![manage-product-cover](/docs/img/guides/kotlin/manage-product-cover.png) If you get stuck while working through this guide, refer to the [full example on @@ -24,34 +24,26 @@ This tutorial demonstrates how to build a basic product management app. The app ### Create new Android project -Open Android Studio > New Project > Base Activity (Jetpack Compose) +Open Android Studio > New Project > Base Activity (Jetpack Compose). -Screen Shot 2023-06-02 at 21 33 16 +![Android Studio new project](/docs/img/guides/kotlin/android-studio-new-project.png) ### Set up API key and secret securely -API key and secret from any services are credential information, we should not want to expose these to others because some strangers will use it for bad purpose - #### Create local environment secret -Create or edit `local.properties` file at top level (same level at build.gradle of the project). +Create or edit the `local.properties` file at the root (same level as `build.gradle`) of your project. -> **Note**: This file should not be pushed directly to the repository, meaning that you should include this in `.gitignore` file +> **Note**: Do not commit this file to your source control, e.g. by adding it to your `.gitignore` file! ```kotlin -API_KEY=YOUR_SUPABASE_API_KEY -SECRET=YOUR_SUPABASE_SECRET +SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY SUPABASE_URL=YOUR_SUPABASE_URL - ``` #### Read and set value to BuildConfig -In `build.gradle (app)`, create `Properties` object and read value from `local.properties` set value for `BuildConfig` by calling `buildConfigField` method +In your `build.gradle` (app) file, create a `Properties` object and read the values from your `local.properties` file by calling the `buildConfigField` method: ```kotlin defaultConfig { @@ -65,7 +57,7 @@ defaultConfig { // Set value part Properties properties = new Properties() properties.load(project.rootProject.file("local.properties").newDataInputStream()) - buildConfigField("String", "API_KEY", "\"${properties.getProperty("API_KEY")}\"") + buildConfigField("String", "SUPABASE_ANON_KEY", "\"${properties.getProperty("SUPABASE_ANON_KEY")}\"") buildConfigField("String", "SECRET", "\"${properties.getProperty("SECRET")}\"") buildConfigField("String", "SUPABASE_URL", "\"${properties.getProperty("SUPABASE_URL")}\"") } @@ -73,43 +65,20 @@ defaultConfig { #### Use value from `BuildConfig` -Read value from `BuildConfig` (Remember to import the right class, it is one from your project). For example: +Read the value from `BuildConfig`: ```kotlin val url = BuildConfig.SUPABASE_URL -val apiKey = BuildConfig.API_KEY +val apiKey = BuildConfig.SUPABASE_ANON_KEY ``` ### Set up Supabase dependencies -Screen Shot 2023-06-05 at 21 39 24 +![Gradle dependencies](/docs/img/guides/kotlin/gradle-dependencies.png) -In build.gradle (app) file, add these dependencies then press Sync now. Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with the latest versions as shown below. - - +In the `build.gradle` (app) file, add these dependencies then press "Sync now". Replace the dependency version placeholders `$supabase_version` and `$ktor_version` with their respective latest versions. ```kotlin -// Supabase setup implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version" implementation "io.github.jan-tennert.supabase:storage-kt:$supabase_version" implementation "io.github.jan-tennert.supabase:gotrue-kt:$supabase_version" @@ -118,11 +87,8 @@ implementation "io.ktor:ktor-client-core:$ktor_version" implementation "io.ktor:ktor-utils:$ktor_version" ``` -Also in build.gradle (app) file, add plugin for serialization. The version of this plugin should be the same with Kotlin version +Also in the `build.gradle` (app) file, add the plugin for serialization. The version of this plugin should be the same as your Kotlin version. - - latest kotlin version - ```kotlin plugins { ... @@ -133,7 +99,7 @@ plugins { ### Set up Hilt for dependency injection -In build.gradle (app) file, add these +In the `build.gradle` (app) file, add the following: ```kotlin implementation "com.google.dagger:hilt-android:$hilt_version" @@ -141,7 +107,7 @@ annotationProcessor "com.google.dagger:hilt-compiler:$hilt_version" implementation("androidx.hilt:hilt-navigation-compose:1.0.0") ``` -Create new `ManageProductApplication.kt` class extending Application with `@HiltAndroidApp` annotation +Create a new `ManageProductApplication.kt` class extending Application with `@HiltAndroidApp` annotation: ```kotlin // ManageProductApplication.kt @@ -149,7 +115,7 @@ Create new `ManageProductApplication.kt` class extending Application with `@Hilt class ManageProductApplication: Application() ``` -Open AndroidManifest.xml file, update name property of Application tag +Open the `AndroidManifest.xml` file, update name property of Application tag: ```xml - By the time this page is written, there might be some changes in the code to make it more simple. - If you face any problem when implement, feel free to refer the example repository above - - -Create ProductListViewModel +Create a `ProductListViewModel`: ```kotlin - @HiltViewModel class ProductListViewModel @Inject constructor( private val productRepository: ProductRepository, @@ -463,7 +421,7 @@ private val productRepository: ProductRepository, ``` -Create ProductListScreen.kt +Create the `ProductListScreen.kt`: ```kotlin @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @@ -581,7 +539,7 @@ private fun AddProductButton( } ``` -Create ProductDetailsViewModel.kt +Create the `ProductDetailsViewModel.kt`: ```kotlin @@ -654,7 +612,7 @@ class ProductDetailsViewModel @Inject constructor( } ``` -Create ProductDetailsScreen.kt +Create the `ProductDetailsScreen.kt`: ```kotlin @OptIn(ExperimentalCoilApi::class) @@ -826,7 +784,7 @@ private fun uriToByteArray(contentResolver: ContentResolver, uri: Uri): ByteArra } ``` -Create AddProductScreen +Create a AddProductScreen: ```kotlin @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @@ -885,7 +843,7 @@ fun AddProductScreen( } ``` -Create AddProductViewModel.kt +Create the `AddProductViewModel.kt`: ```kotlin @HiltViewModel @@ -917,7 +875,7 @@ class AddProductViewModel @Inject constructor( } ``` -Create SignUpViewModel +Create a `SignUpViewModel`: ```kotlin @HiltViewModel @@ -950,7 +908,7 @@ class SignUpViewModel @Inject constructor( } ``` -Create SignUpScreen.kt +Create the `SignUpScreen.kt`: ```kotlin @Composable @@ -1048,7 +1006,7 @@ fun SignUpScreen( } ``` -Create SignInViewModel +Create a `SignInViewModel`: ```kotlin @HiltViewModel @@ -1088,7 +1046,7 @@ class SignInViewModel @Inject constructor( } ``` -Create SignInScreen.kt +Create the `SignInScreen.kt`: ```kotlin @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @@ -1203,10 +1161,9 @@ fun SignInScreen( } ``` -Create successful screen -Implement Sign In Success screen +### Create the success screen -Create new Empty Activity and in AndroidManifest.xml, add deep link, make sure `scheme` and `host` are the same with ones when set up GoTrue instance +Implement a "Sign In" success screen. Create new "Empty Activity" and in `AndroidManifest.xml`, add a deep link, make sure `scheme` and `host` are the same as the ones set in the GoTrue instance. ```xml diff --git a/apps/docs/public/img/deeplink-setting.png b/apps/docs/public/img/deeplink-setting.png index a0111ae04f8..b34ff0a13b8 100644 Binary files a/apps/docs/public/img/deeplink-setting.png and b/apps/docs/public/img/deeplink-setting.png differ diff --git a/apps/docs/public/img/guides/kotlin/android-studio-new-project.png b/apps/docs/public/img/guides/kotlin/android-studio-new-project.png new file mode 100644 index 00000000000..70581407182 Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/android-studio-new-project.png differ diff --git a/apps/docs/public/img/guides/kotlin/google-cloud-oauth-credentials-create.png b/apps/docs/public/img/guides/kotlin/google-cloud-oauth-credentials-create.png new file mode 100644 index 00000000000..724d07fb63b Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/google-cloud-oauth-credentials-create.png differ diff --git a/apps/docs/public/img/guides/kotlin/gradle-dependencies.png b/apps/docs/public/img/guides/kotlin/gradle-dependencies.png new file mode 100644 index 00000000000..fdb4c035935 Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/gradle-dependencies.png differ diff --git a/apps/docs/public/img/guides/kotlin/manage-product-cover.png b/apps/docs/public/img/guides/kotlin/manage-product-cover.png new file mode 100644 index 00000000000..5986b8bc9fc Binary files /dev/null and b/apps/docs/public/img/guides/kotlin/manage-product-cover.png differ 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 07f29e5190f..d15b4143e63 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 @@ -57,7 +57,7 @@ And voila, we had a baseline for generating variations: The final visual that you see everywhere won us over because of its vibrant swirls. While each swirl looks slightly different, a consistent style is still maintained throughout. -Fortunately, we didn't require full-width images for each ticket. Upscaled MidJourney images sufficed. Nevertheless, we had to recreate the main visual on the landing page in vectors to use it effectively. You can view it in full glory at [https://supabase.com/launch-week](https://supabase.com/launch-week). +Fortunately, we didn't require full-width images for each ticket. Upscaled MidJourney images sufficed. Nevertheless, we had to recreate the main visual on the landing page in vectors to use it effectively. You can view it in full glory at [https://supabase.com/launch-week/7](https://supabase.com/launch-week/7). ![Screenshot 2023-04-06 at 09.47.47.png](/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-home.png) @@ -258,13 +258,13 @@ const channel = supabase .subscribe() ``` -Interested to know how this fits within your Next.js application? Find the code [here](https://github.com/supabase/supabase/blob/master/apps/www/components/LaunchWeek/Ticket/TicketForm.tsx#L70-L102). +Interested to know how this fits within your Next.js application? Find the code [here](https://github.com/supabase/supabase/blob/master/apps/www/components/LaunchWeek/7/Ticket/TicketForm.tsx#L70-L102). ## Displaying the images -We have some images, so how can we now display them somewhere? [@Francesco Sansalvadore](https://twitter.com/frnk_snslvdr) saw one of our team members trying to “swipe” on the tickets slider component on the [launch week page](https://supabase.com/launch-week/tickets) and he thought, why not feature all the fantastic people who generated tickets on a single page? +We have some images, so how can we now display them somewhere? [@Francesco Sansalvadore](https://twitter.com/frnk_snslvdr) saw one of our team members trying to “swipe” on the tickets slider component on the [launch week page](https://supabase.com/launch-week/7/tickets) and he thought, why not feature all the fantastic people who generated tickets on a single page? -We built a [ticket wall](https://supabase.com/launch-week/tickets) that you can scroll endlessly. We approached it with an infinite scroll technique, lazy-loading a few tickets at a time. +We built a [ticket wall](https://supabase.com/launch-week/7/tickets) that you can scroll endlessly. We approached it with an infinite scroll technique, lazy-loading a few tickets at a time. ![launch week 7 ticket wall](/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-wall.png) diff --git a/apps/www/components/CTABanner/index.tsx b/apps/www/components/CTABanner/index.tsx index fb08eb5630e..ff1f2ddec1a 100644 --- a/apps/www/components/CTABanner/index.tsx +++ b/apps/www/components/CTABanner/index.tsx @@ -1,18 +1,26 @@ import { Button } from 'ui' -const CTABanner = (props: any) => { - const { darkerBg } = props +interface Props { + className?: string + darkerBg?: boolean +} + +const CTABanner = ({ darkerBg, className }: Props) => { return (

Build in a weekend, - scale to millions + + {' '} + scale to millions +

diff --git a/apps/www/components/DarkModeToggle.tsx b/apps/www/components/DarkModeToggle.tsx index 4b3b03cc7aa..f8c9ad1abff 100644 --- a/apps/www/components/DarkModeToggle.tsx +++ b/apps/www/components/DarkModeToggle.tsx @@ -1,20 +1,21 @@ import { useTheme } from 'common/Providers' import { IconSun, IconMoon } from 'ui' -function DarkModeToggle() { +function DarkModeToggle({ disabled = false }: { disabled?: boolean }) { const { isDarkMode, toggleTheme } = useTheme() return ( -
+
© Supabase Inc - +
diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index a309e4087f1..1cc9750aafc 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -3,31 +3,59 @@ import { useRouter } from 'next/router' import Telemetry, { TelemetryEvent } from '~/lib/telemetry' import { useTelemetryProps } from 'common/hooks/useTelemetryProps' import gaEvents from '~/lib/gaEvents' -import { Button, IconBookOpen } from 'ui' +import { Badge, Button, IconArrowRight, IconBookOpen } from 'ui' import SectionContainer from '~/components/Layouts/SectionContainer' import HeroFrameworks from './HeroFrameworks' import styles from './hero.module.css' +import Image from 'next/image' +import { range } from 'lodash' +import ParticlesCanvas from '../LaunchWeek/8/AnimatedParticles/ParticlesCanvas' +import { useBreakpoint } from 'common' +import { useEffect, useState } from 'react' const Hero = () => { const router = useRouter() + const [showAnnouncement, setShowAnnouncement] = useState(false) const telemetryProps = useTelemetryProps() + const isMobile = useBreakpoint() const sendTelemetryEvent = async (event: TelemetryEvent) => { await Telemetry.sendEvent(event, telemetryProps, router) } + useEffect(() => { + if (typeof window !== undefined) { + const lw8AnnouncementKey = window.localStorage.getItem('announcement_SupabaseLaunchWeek8') + setShowAnnouncement(!!lw8AnnouncementKey) + } + }, [showAnnouncement]) + return ( -
- +
+
-
+
+ {showAnnouncement && ( + + +
+ Get you ticket +
+ Launch Week 8 - August 7th + +
+ + )}

Build in a weekend @@ -63,12 +91,35 @@ const Hero = () => {

+
+
+ + starts background +
+
+ +
+
) diff --git a/apps/www/components/Hero/HeroFrameworks.tsx b/apps/www/components/Hero/HeroFrameworks.tsx index b9b438c1021..2065c40a3d3 100644 --- a/apps/www/components/Hero/HeroFrameworks.tsx +++ b/apps/www/components/Hero/HeroFrameworks.tsx @@ -33,7 +33,7 @@ const frameworks = [ }, { name: 'Android Kotlin', - icon: '/images/logos/frameworks/kotlin.svg', + icon: 'M29.6,11.4L11.4,28.2V13.4c0-1.1,1-2,2.1-2H29.6z M45.1,14.7L25.4,32.9L11.6,45.5c-0.2-0.3-0.3-0.6-0.3-0.9V33.7l24.2-22.3h8C45.5,11.4,46.4,13.5,45.1,14.7z M44.5,46.6H16.5l16.5-15.2L46,43.3C47.3,44.5,46.4,46.6,44.5,46.6z', docs: '/docs/guides/getting-started/quickstarts/kotlin', gaEvent: 'www_hp_hero_frameworks_kotlin', }, diff --git a/apps/www/components/LaunchWeek/ScheduleInfo.tsx b/apps/www/components/LaunchWeek/5/ScheduleInfo.tsx similarity index 100% rename from apps/www/components/LaunchWeek/ScheduleInfo.tsx rename to apps/www/components/LaunchWeek/5/ScheduleInfo.tsx diff --git a/apps/www/components/LaunchWeek/days.json b/apps/www/components/LaunchWeek/5/days.json similarity index 100% rename from apps/www/components/LaunchWeek/days.json rename to apps/www/components/LaunchWeek/5/days.json diff --git a/apps/www/components/LaunchWeek/HackathonSection.tsx b/apps/www/components/LaunchWeek/6/HackathonSection.tsx similarity index 100% rename from apps/www/components/LaunchWeek/HackathonSection.tsx rename to apps/www/components/LaunchWeek/6/HackathonSection.tsx diff --git a/apps/www/components/LaunchWeek/LaunchHero.tsx b/apps/www/components/LaunchWeek/6/LaunchHero.tsx similarity index 100% rename from apps/www/components/LaunchWeek/LaunchHero.tsx rename to apps/www/components/LaunchWeek/6/LaunchHero.tsx diff --git a/apps/www/components/LaunchWeek/PreLaunchTeaser.tsx b/apps/www/components/LaunchWeek/6/PreLaunchTeaser.tsx similarity index 100% rename from apps/www/components/LaunchWeek/PreLaunchTeaser.tsx rename to apps/www/components/LaunchWeek/6/PreLaunchTeaser.tsx diff --git a/apps/www/components/LaunchWeek/launch-controller.json b/apps/www/components/LaunchWeek/6/launch-controller.json similarity index 100% rename from apps/www/components/LaunchWeek/launch-controller.json rename to apps/www/components/LaunchWeek/6/launch-controller.json diff --git a/apps/www/components/LaunchWeek/lw6_days.json b/apps/www/components/LaunchWeek/6/lw6_days.json similarity index 100% rename from apps/www/components/LaunchWeek/lw6_days.json rename to apps/www/components/LaunchWeek/6/lw6_days.json diff --git a/apps/www/components/LaunchWeek/7/BlogLinks.tsx b/apps/www/components/LaunchWeek/7/BlogLinks.tsx index 41168f03550..6ccf0de19ca 100644 --- a/apps/www/components/LaunchWeek/7/BlogLinks.tsx +++ b/apps/www/components/LaunchWeek/7/BlogLinks.tsx @@ -2,7 +2,7 @@ import { motion } from 'framer-motion' import Image from 'next/image' import Link from 'next/link' import React from 'react' -import days from '~/components/LaunchWeek/lw7_days' +import days from '~/components/LaunchWeek/7/lw7_days' const BlogLinks = () => { const activeDays = days.filter((day) => Date.parse(day.publishedAt) <= Date.now()) diff --git a/apps/www/components/LaunchWeek/LW7BgGraphic.tsx b/apps/www/components/LaunchWeek/7/LW7BgGraphic.tsx similarity index 100% rename from apps/www/components/LaunchWeek/LW7BgGraphic.tsx rename to apps/www/components/LaunchWeek/7/LW7BgGraphic.tsx diff --git a/apps/www/components/LaunchWeek/LabelBadge.tsx b/apps/www/components/LaunchWeek/7/LabelBadge.tsx similarity index 100% rename from apps/www/components/LaunchWeek/LabelBadge.tsx rename to apps/www/components/LaunchWeek/7/LabelBadge.tsx diff --git a/apps/www/components/LaunchWeek/LaunchSection/Announcement.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/Announcement.tsx similarity index 94% rename from apps/www/components/LaunchWeek/LaunchSection/Announcement.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/Announcement.tsx index 73805f6de09..5f8f4db18a9 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/Announcement.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/Announcement.tsx @@ -1,4 +1,4 @@ -import { Announcement as AnnouncementType } from '../types' +import { Announcement as AnnouncementType } from '../../types' export const Announcement = (props: AnnouncementType) => { const containerClasses = [] diff --git a/apps/www/components/LaunchWeek/LaunchSection/ArticleButton.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/ArticleButton.tsx similarity index 95% rename from apps/www/components/LaunchWeek/LaunchSection/ArticleButton.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/ArticleButton.tsx index 10826a772f0..364e9ccd5eb 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/ArticleButton.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/ArticleButton.tsx @@ -1,5 +1,5 @@ import { NewspaperIcon } from '@heroicons/react/outline' -import { Article } from '../types' +import { Article } from '../../types' const ArticleButton = (props: Article) => { return ( diff --git a/apps/www/components/LaunchWeek/LaunchSection/ArticleButtonListItem.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/ArticleButtonListItem.tsx similarity index 94% rename from apps/www/components/LaunchWeek/LaunchSection/ArticleButtonListItem.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/ArticleButtonListItem.tsx index be927a23cd2..e374bd023fc 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/ArticleButtonListItem.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/ArticleButtonListItem.tsx @@ -1,6 +1,6 @@ import { NewspaperIcon } from '@heroicons/react/outline' import Link from 'next/link' -import { Article } from '../types' +import { Article } from '../../types' const ArticleButtonListItem = (props: Article) => { return ( diff --git a/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/LaunchWeekLogoHeader.tsx similarity index 100% rename from apps/www/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/LaunchWeekLogoHeader.tsx diff --git a/apps/www/components/LaunchWeek/LaunchSection/ProductButton.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/ProductButton.tsx similarity index 95% rename from apps/www/components/LaunchWeek/LaunchSection/ProductButton.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/ProductButton.tsx index c1b89239015..550d0e28815 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/ProductButton.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/ProductButton.tsx @@ -1,5 +1,5 @@ import { SparklesIcon } from '@heroicons/react/outline' -import { Article } from '../types' +import { Article } from '../../types' const ProductButton = (props: Article) => { return ( diff --git a/apps/www/components/LaunchWeek/LaunchSection/ProductButtonListItem.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/ProductButtonListItem.tsx similarity index 95% rename from apps/www/components/LaunchWeek/LaunchSection/ProductButtonListItem.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/ProductButtonListItem.tsx index bb8edfb1af4..07ed656ae0c 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/ProductButtonListItem.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/ProductButtonListItem.tsx @@ -1,6 +1,6 @@ import { SparklesIcon } from '@heroicons/react/outline' import Link from 'next/link' -import { Article } from '../types' +import { Article } from '../../types' export const ProductButtonListItem = (props: Article) => { return ( diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrick.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/TicketBrick.tsx similarity index 96% rename from apps/www/components/LaunchWeek/LaunchSection/TicketBrick.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/TicketBrick.tsx index 50e0438868d..5f393f57650 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/TicketBrick.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/TicketBrick.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import TicketHeader from '../Ticket/TicketHeader' import TicketNumber from '../Ticket/TicketNumber' -import { UserData } from '../Ticket/hooks/use-conf-data' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' import Image from 'next/image' interface Props { diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWall.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/TicketBrickWall.tsx similarity index 96% rename from apps/www/components/LaunchWeek/LaunchSection/TicketBrickWall.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/TicketBrickWall.tsx index b757f4cdeaf..ea7922d61f4 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWall.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/TicketBrickWall.tsx @@ -2,7 +2,7 @@ import Link from 'next/link' import React from 'react' import { Button } from 'ui' import { useBreakpoint } from 'common/hooks/useBreakpoint' -import { UserData } from '../Ticket/hooks/use-conf-data' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' import { TicketBrickWallSlider } from './TicketBrickWallSlider' type user = UserData diff --git a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/TicketBrickWallSlider.tsx similarity index 92% rename from apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/TicketBrickWallSlider.tsx index 8ff947547f9..ebc7a12ffb9 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/TicketBrickWallSlider.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/TicketBrickWallSlider.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import Image from 'next/image' import Link from 'next/link' -import { UserData } from '../Ticket/hooks/use-conf-data' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' import { useWindowSize } from 'react-use' import { Swiper, SwiperSlide } from 'swiper/react' @@ -34,7 +34,7 @@ export function TicketBrickWallSlider({ users, reverse, speed = 10000 }: Props) return (
-
+
setSwiperInstance(swiper)} centeredSlides={true} @@ -65,7 +65,7 @@ export function TicketBrickWallSlider({ users, reverse, speed = 10000 }: Props) > {users.map((user, i) => ( - +
diff --git a/apps/www/components/LaunchWeek/LaunchSection/brick-wall.module.css b/apps/www/components/LaunchWeek/7/LaunchSection/brick-wall.module.css similarity index 100% rename from apps/www/components/LaunchWeek/LaunchSection/brick-wall.module.css rename to apps/www/components/LaunchWeek/7/LaunchSection/brick-wall.module.css diff --git a/apps/www/components/LaunchWeek/LaunchSection/index.tsx b/apps/www/components/LaunchWeek/7/LaunchSection/index.tsx similarity index 99% rename from apps/www/components/LaunchWeek/LaunchSection/index.tsx rename to apps/www/components/LaunchWeek/7/LaunchSection/index.tsx index f48efa21534..3faf36f20d0 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/index.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchSection/index.tsx @@ -3,7 +3,7 @@ import { useState } from 'react' import { PlayIcon, TruckIcon, XIcon } from '@heroicons/react/outline' import { Badge, Modal } from 'ui' -import { Article, Product, WeekDayProps } from '../types' +import { Article, Product, WeekDayProps } from '../../types' import _days from './../days.json' import ArticleButtonListItem from './ArticleButtonListItem' import ProductButtonListItem from './ProductButtonListItem' diff --git a/apps/www/components/LaunchWeek/LaunchSection/ticket-brick-wall-slider.module.css b/apps/www/components/LaunchWeek/7/LaunchSection/ticket-brick-wall-slider.module.css similarity index 100% rename from apps/www/components/LaunchWeek/LaunchSection/ticket-brick-wall-slider.module.css rename to apps/www/components/LaunchWeek/7/LaunchSection/ticket-brick-wall-slider.module.css diff --git a/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeCard.tsx b/apps/www/components/LaunchWeek/7/LaunchWeekPrizeCard.tsx similarity index 100% rename from apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeCard.tsx rename to apps/www/components/LaunchWeek/7/LaunchWeekPrizeCard.tsx diff --git a/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection.tsx b/apps/www/components/LaunchWeek/7/LaunchWeekPrizeSection.tsx similarity index 99% rename from apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection.tsx rename to apps/www/components/LaunchWeek/7/LaunchWeekPrizeSection.tsx index 052d6c44479..4f8d365192b 100644 --- a/apps/www/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection.tsx +++ b/apps/www/components/LaunchWeek/7/LaunchWeekPrizeSection.tsx @@ -1,5 +1,5 @@ import React from 'react' -import LabelBadge from '../LabelBadge' +import LabelBadge from './LabelBadge' import LaunchWeekPrizeCard from './LaunchWeekPrizeCard' import { motion } from 'framer-motion' diff --git a/apps/www/components/LaunchWeek/ReferralIndicator.tsx b/apps/www/components/LaunchWeek/7/ReferralIndicator.tsx similarity index 82% rename from apps/www/components/LaunchWeek/ReferralIndicator.tsx rename to apps/www/components/LaunchWeek/7/ReferralIndicator.tsx index fd3a7f4c3fd..c4ad96a0871 100644 --- a/apps/www/components/LaunchWeek/ReferralIndicator.tsx +++ b/apps/www/components/LaunchWeek/7/ReferralIndicator.tsx @@ -1,6 +1,6 @@ import React from 'react' -import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' -import useWinningChances from './Ticket/hooks/useWinningChances' +import useConfData from '~/components/LaunchWeek/hooks/use-conf-data' +import useWinningChances from '../hooks/useWinningChances' export default function ReferralIndicator() { const winningChances = useWinningChances() diff --git a/apps/www/components/LaunchWeek/Releases/LW7/Day5.tsx b/apps/www/components/LaunchWeek/7/Releases/Day5.tsx similarity index 99% rename from apps/www/components/LaunchWeek/Releases/LW7/Day5.tsx rename to apps/www/components/LaunchWeek/7/Releases/Day5.tsx index b36d06f1c32..01371b11a04 100644 --- a/apps/www/components/LaunchWeek/Releases/LW7/Day5.tsx +++ b/apps/www/components/LaunchWeek/7/Releases/Day5.tsx @@ -11,8 +11,8 @@ import { StyledArticleBadge, } from './components' -import { WeekDayProps } from '~/components/LaunchWeek/lw7_days' -import { opacityVariant4, scaleOpacityVariant2 } from './LW7Releases' +import { WeekDayProps } from '~/components/LaunchWeek/7/lw7_days' +import { opacityVariant4, scaleOpacityVariant2 } from '.' import styles from './day5.module.css' import Link from 'next/link' diff --git a/apps/www/components/LaunchWeek/Releases/LW7/components/index.tsx b/apps/www/components/LaunchWeek/7/Releases/components/index.tsx similarity index 100% rename from apps/www/components/LaunchWeek/Releases/LW7/components/index.tsx rename to apps/www/components/LaunchWeek/7/Releases/components/index.tsx diff --git a/apps/www/components/LaunchWeek/Releases/LW7/day5.module.css b/apps/www/components/LaunchWeek/7/Releases/day5.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Releases/LW7/day5.module.css rename to apps/www/components/LaunchWeek/7/Releases/day5.module.css diff --git a/apps/www/components/LaunchWeek/Releases/LW7/LW7Releases.tsx b/apps/www/components/LaunchWeek/7/Releases/index.tsx similarity index 99% rename from apps/www/components/LaunchWeek/Releases/LW7/LW7Releases.tsx rename to apps/www/components/LaunchWeek/7/Releases/index.tsx index bf6cf2c54be..6445cb5f8a2 100644 --- a/apps/www/components/LaunchWeek/Releases/LW7/LW7Releases.tsx +++ b/apps/www/components/LaunchWeek/7/Releases/index.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react' import { Accordion } from 'ui' import Lottie from 'lottie-react' -import days, { WeekDayProps, endOfLW7 } from '~/components/LaunchWeek/lw7_days' +import days, { WeekDayProps, endOfLW7 } from '~/components/LaunchWeek/7/lw7_days' import SectionContainer from '~/components/Layouts/SectionContainer' import { AccordionHeader, diff --git a/apps/www/components/LaunchWeek/Releases/LW7/styles/launchWeek7.module.css b/apps/www/components/LaunchWeek/7/Releases/styles/launchWeek7.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Releases/LW7/styles/launchWeek7.module.css rename to apps/www/components/LaunchWeek/7/Releases/styles/launchWeek7.module.css diff --git a/apps/www/components/LaunchWeek/Releases/LW7/styles/utils7.module.css b/apps/www/components/LaunchWeek/7/Releases/styles/utils7.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Releases/LW7/styles/utils7.module.css rename to apps/www/components/LaunchWeek/7/Releases/styles/utils7.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx b/apps/www/components/LaunchWeek/7/Ticket/ActualTicket.tsx similarity index 97% rename from apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx rename to apps/www/components/LaunchWeek/7/Ticket/ActualTicket.tsx index bb59bd906de..72afd641dfb 100644 --- a/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx +++ b/apps/www/components/LaunchWeek/7/Ticket/ActualTicket.tsx @@ -1,14 +1,14 @@ import cn from 'classnames' import { useRef, useState } from 'react' import styles from './ticket.module.css' -import styleUtils from './utils.module.css' +import styleUtils from '../../utils.module.css' import TicketVisual from './TicketVisual' import TicketActions from './TicketActions' import TicketCopy from './ticket-copy' -import { UserData } from './hooks/use-conf-data' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' import ReferralIndicator from '../ReferralIndicator' -import useWinningChances from './hooks/useWinningChances' +import useWinningChances from '../../hooks/useWinningChances' import { SITE_URL } from '~/lib/constants' import { useBreakpoint } from 'common/hooks/useBreakpoint' diff --git a/apps/www/components/LaunchWeek/Ticket/TicketActions.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketActions.tsx similarity index 97% rename from apps/www/components/LaunchWeek/Ticket/TicketActions.tsx rename to apps/www/components/LaunchWeek/7/Ticket/TicketActions.tsx index ee351e58f04..ad775d8a483 100644 --- a/apps/www/components/LaunchWeek/Ticket/TicketActions.tsx +++ b/apps/www/components/LaunchWeek/7/Ticket/TicketActions.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from 'react' import { SITE_URL, TWEET_TEXT, TWEET_TEXT_GOLDEN } from '~/lib/constants' import { IconCheckCircle } from 'ui' -import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' +import useConfData from '~/components/LaunchWeek/hooks/use-conf-data' import { useParams } from '~/hooks/useParams' import TicketForm from './TicketForm' @@ -62,7 +62,7 @@ export default function TicketActions({
- Connect with Github + Connect with GitHub
{ + .then(async ({ error }: any) => { // If error because of duplicate email, ignore and proceed, otherwise sign out. if (error && error?.code !== '23505') return supabase.auth.signOut() const { data } = await supabase @@ -79,7 +79,7 @@ export default function TicketForm({ defaultUsername = '', setTicketGenerationSt table: 'lw7_tickets', filter: `username=eq.${username}`, }, - (payload) => { + (payload: any) => { const golden = !!payload.new.sharedOnTwitter && !!payload.new.sharedOnLinkedIn setUserData({ ...payload.new, @@ -157,12 +157,12 @@ export default function TicketForm({ defaultUsername = '', setTicketGenerationSt {session ? ( <> - Connect with Github + Connect with GitHub ) : ( {formState === 'loading' && } - Connect with Github + Connect with GitHub )} diff --git a/apps/www/components/LaunchWeek/Ticket/TicketHeader.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketHeader.tsx similarity index 81% rename from apps/www/components/LaunchWeek/Ticket/TicketHeader.tsx rename to apps/www/components/LaunchWeek/7/Ticket/TicketHeader.tsx index da3d1e79401..2b082bb954a 100644 --- a/apps/www/components/LaunchWeek/Ticket/TicketHeader.tsx +++ b/apps/www/components/LaunchWeek/7/Ticket/TicketHeader.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { DATE } from '~/lib/constants' +import { LW7_DATE } from '~/lib/constants' interface Props { size?: 'default' | 'small' @@ -12,7 +12,7 @@ export default function TicketHeader({ size }: Props) { className={size === 'small' ? 'w-[165px]' : 'w-[220px] md:w-[230px]'} src={`/images/launchweek/ticket-header-logo.png`} /> - {DATE} + {LW7_DATE}
) diff --git a/apps/www/components/LaunchWeek/Ticket/TicketNumber.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketNumber.tsx similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/TicketNumber.tsx rename to apps/www/components/LaunchWeek/7/Ticket/TicketNumber.tsx diff --git a/apps/www/components/LaunchWeek/Ticket/TicketProfile.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketProfile.tsx similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/TicketProfile.tsx rename to apps/www/components/LaunchWeek/7/Ticket/TicketProfile.tsx diff --git a/apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx b/apps/www/components/LaunchWeek/7/Ticket/TicketVisual.tsx similarity index 98% rename from apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx rename to apps/www/components/LaunchWeek/7/Ticket/TicketVisual.tsx index be31e8c231c..7dffd9a3439 100644 --- a/apps/www/components/LaunchWeek/Ticket/TicketVisual.tsx +++ b/apps/www/components/LaunchWeek/7/Ticket/TicketVisual.tsx @@ -2,7 +2,7 @@ import styles from './ticket-visual.module.css' import TicketProfile from './TicketProfile' import TicketNumber from './TicketNumber' import Tilt from 'vanilla-tilt' -import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' +import useConfData from '~/components/LaunchWeek/hooks/use-conf-data' import TicketHeader from './TicketHeader' import { useEffect, useRef, useState } from 'react' import Image from 'next/image' diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.module.css b/apps/www/components/LaunchWeek/7/Ticket/conf-container.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/conf-container.module.css rename to apps/www/components/LaunchWeek/7/Ticket/conf-container.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.tsx b/apps/www/components/LaunchWeek/7/Ticket/conf-container.tsx similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/conf-container.tsx rename to apps/www/components/LaunchWeek/7/Ticket/conf-container.tsx diff --git a/apps/www/components/LaunchWeek/Ticket/form.module.css b/apps/www/components/LaunchWeek/7/Ticket/form.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/form.module.css rename to apps/www/components/LaunchWeek/7/Ticket/form.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/form.tsx b/apps/www/components/LaunchWeek/7/Ticket/form.tsx similarity index 96% rename from apps/www/components/LaunchWeek/Ticket/form.tsx rename to apps/www/components/LaunchWeek/7/Ticket/form.tsx index d8a9c9dbc3d..15b20e2edb8 100644 --- a/apps/www/components/LaunchWeek/Ticket/form.tsx +++ b/apps/www/components/LaunchWeek/7/Ticket/form.tsx @@ -1,10 +1,10 @@ import { useState, useCallback } from 'react' import cn from 'classnames' -import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' +import useConfData from '~/components/LaunchWeek/hooks/use-conf-data' import { useRouter } from 'next/router' -import styleUtils from './utils.module.css' +import styleUtils from '../../utils.module.css' import styles from './form.module.css' -import useEmailQueryParam from '~/components/LaunchWeek/Ticket/hooks/use-email-query-param' +import useEmailQueryParam from '~/components/LaunchWeek/hooks/use-email-query-param' import { IconLoader } from '~/../../packages/ui' type FormState = 'default' | 'loading' | 'error' diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css b/apps/www/components/LaunchWeek/7/Ticket/loading-dots.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/loading-dots.module.css rename to apps/www/components/LaunchWeek/7/Ticket/loading-dots.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx b/apps/www/components/LaunchWeek/7/Ticket/loading-dots.tsx similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/loading-dots.tsx rename to apps/www/components/LaunchWeek/7/Ticket/loading-dots.tsx diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-actions.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css rename to apps/www/components/LaunchWeek/7/Ticket/ticket-actions.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-copy.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css rename to apps/www/components/LaunchWeek/7/Ticket/ticket-copy.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx b/apps/www/components/LaunchWeek/7/Ticket/ticket-copy.tsx similarity index 98% rename from apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx rename to apps/www/components/LaunchWeek/7/Ticket/ticket-copy.tsx index c228e815a67..2286aae4293 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx +++ b/apps/www/components/LaunchWeek/7/Ticket/ticket-copy.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useRef } from 'react' import cn from 'classnames' import { SITE_URL } from '~/lib/constants' -import styleUtils from './utils.module.css' +import styleUtils from '../../utils.module.css' import { IconCopy, IconCheck } from 'ui' type Props = { diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-form.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/ticket-form.module.css rename to apps/www/components/LaunchWeek/7/Ticket/ticket-form.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css b/apps/www/components/LaunchWeek/7/Ticket/ticket-image.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/ticket-image.module.css rename to apps/www/components/LaunchWeek/7/Ticket/ticket-image.module.css diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx b/apps/www/components/LaunchWeek/7/Ticket/ticket-image.tsx similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/ticket-image.tsx rename to apps/www/components/LaunchWeek/7/Ticket/ticket-image.tsx diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx b/apps/www/components/LaunchWeek/7/Ticket/ticket-info-footer.tsx similarity index 90% rename from apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx rename to apps/www/components/LaunchWeek/7/Ticket/ticket-info-footer.tsx index 7dd3b821630..375223f161c 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-info-footer.tsx +++ b/apps/www/components/LaunchWeek/7/Ticket/ticket-info-footer.tsx @@ -1,6 +1,6 @@ import styles from './ticket-info.module.css' import cn from 'classnames' -import { DATE, SITE_URL } from '~/lib/constants' +import { LW7_DATE, SITE_URL } from '~/lib/constants' const siteUrl = new URL(SITE_URL) const siteUrlForTicket = `${siteUrl.host}${siteUrl.pathname}`.replace(/\/$/, '') @@ -16,7 +16,7 @@ export default function TicketInfoFooter({ golden && '!text-white' }`} > -
{DATE}
+
{LW7_DATE}
window.scrollTo(0, 0)} > diff --git a/apps/www/components/LaunchWeek/lw7-bg-graphic.module.css b/apps/www/components/LaunchWeek/7/lw7-bg-graphic.module.css similarity index 100% rename from apps/www/components/LaunchWeek/lw7-bg-graphic.module.css rename to apps/www/components/LaunchWeek/7/lw7-bg-graphic.module.css diff --git a/apps/www/components/LaunchWeek/lw7_days.ts b/apps/www/components/LaunchWeek/7/lw7_days.ts similarity index 100% rename from apps/www/components/LaunchWeek/lw7_days.ts rename to apps/www/components/LaunchWeek/7/lw7_days.ts diff --git a/apps/www/components/LaunchWeek/8/AnimatedParticles/Particle.tsx b/apps/www/components/LaunchWeek/8/AnimatedParticles/Particle.tsx new file mode 100644 index 00000000000..29908f79016 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/AnimatedParticles/Particle.tsx @@ -0,0 +1,49 @@ +import React, { useRef } from 'react' +import { useFrame } from '@react-three/fiber' + +interface Props { + animate?: boolean + children: any + config: any + user?: any +} + +const Particle = ({ animate = true, children, config }: Props) => { + const particle = useRef(null) + + const pathOffsetShape = + Math.pow( + Math.random() * config.xRandomnessShape, + config.xRandomness - 1 + 1 - config.xRandomness / 2 + ) * config.xThickness + + const pathOffset = pathOffsetShape * (pathOffsetShape < 1 || Math.random() < 0.5 ? 1 : -1) + const verticalRandomness = Math.random() * (config.yThickness - 1) + 1 - config.yThickness / 2 + + const speed = Math.random() * (config.min_speed - config.max_speed) + config.max_speed + + const circumference = (config.widthRadius * Math.PI * 2) / 100 + const delayOffsetFactor = 100 + const delayOffset = Math.random() * delayOffsetFactor + + useFrame(({ clock }) => { + if (animate) { + const timer = clock.getElapsedTime() * speed + delayOffset + // When the loop count is even, draw bottom 8 shape + // if odd, draw top 8 shape + const isEven = Math.floor(timer / circumference) % 2 == 0 + particle.current.position.x = isEven + ? Math.sin(timer) * config.widthRadius * config.widthRatio + pathOffset + : Math.sin(timer) * config.widthRadius + pathOffset + particle.current.position.y = isEven + ? Math.cos(timer) * config.bottomHeightRadius - + config.bottomHeightRadius + + verticalRandomness + : -Math.cos(timer) * config.topHeightRadius + config.topHeightRadius + verticalRandomness + } + }) + + return {children} +} + +export default Particle diff --git a/apps/www/components/LaunchWeek/8/AnimatedParticles/ParticlesCanvas.tsx b/apps/www/components/LaunchWeek/8/AnimatedParticles/ParticlesCanvas.tsx new file mode 100644 index 00000000000..904710ded6a --- /dev/null +++ b/apps/www/components/LaunchWeek/8/AnimatedParticles/ParticlesCanvas.tsx @@ -0,0 +1,140 @@ +import React, { useMemo, useEffect, useState } from 'react' +import { Canvas } from '@react-three/fiber' +import { AdditiveBlending } from 'three' +import Particle from './Particle' +import useParticlesConfig from './hooks/useParticlesConfig' +import { SupabaseClient } from '@supabase/supabase-js' + +const ParticlesCanvas = ({ supabase, users }: { supabase?: SupabaseClient; users: any }) => { + const isWindowUndefined = typeof window === 'undefined' + if (isWindowUndefined) return null + + const canvasRef = React.useRef(null) + + const [animate, setAnimate] = useState(true) + const { config, handleSetConfig, particles, setParticles, isDebugMode } = + useParticlesConfig(users) + const [realtimeChannel, setRealtimeChannel] = useState | null>(null) + + const loadUsers = async () => { + return await supabase!.from('lw8_tickets_golden').select('id, golden', { count: 'exact' }) + } + + // Update particles live when new tickets are generated + useEffect(() => { + if (!!supabase) { + const channel = supabase + .channel('lw8_tickets_changes') + .on( + 'postgres_changes', + { + event: 'INSERT', + schema: 'public', + table: 'lw8_tickets_golden', + }, + async () => { + const { data: users } = await loadUsers() + + handleSetConfig('particles', users?.length) + setParticles((prev: any) => users! ?? prev) + } + ) + .subscribe() + setRealtimeChannel(channel) + } + + return () => { + // Cleanup realtime subscription on unmount + realtimeChannel?.unsubscribe() + } + }, []) + + // stop animation if canvas if is not in viewport + // to avoid unnecessary computations + const handleScroll = () => { + if (canvasRef.current && typeof window !== 'undefined') { + const rect = (canvasRef.current as HTMLDivElement)?.getBoundingClientRect() + const isInView = rect.bottom > 0 + + setAnimate(isInView) + } + } + + useEffect(() => { + window.addEventListener('scroll', handleScroll) + + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + /* ThreeJs setup */ + + const Geometry = useMemo( + () => () => , + [] + ) + const GoldGeometry = useMemo( + () => () => , + [] + ) + const Material = () => + useMemo( + () => ( + + ), + [] + ) + const GoldMaterial = () => + useMemo( + () => ( + + ), + [] + ) + + return ( + + + + {/* Animated 8 shape particles */} + {particles?.map((user: any, index: number) => ( + + {(!!user.id && user.golden) || (config.showGold && Math.random() <= 0.5) ? ( + <> + + + + ) : ( + <> + + + + )} + + ))} + + + ) +} + +export default ParticlesCanvas diff --git a/apps/www/components/LaunchWeek/8/AnimatedParticles/hooks/useParticlesConfig.tsx b/apps/www/components/LaunchWeek/8/AnimatedParticles/hooks/useParticlesConfig.tsx new file mode 100644 index 00000000000..6eb0dadb087 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/AnimatedParticles/hooks/useParticlesConfig.tsx @@ -0,0 +1,182 @@ +import { useEffect, useState } from 'react' +import { range } from 'lodash' + +let defaultConfig = { + particles: 20, + particlesSize: 1.2, + goldParticlesSize: 1.5, + particlesSides: 5, + particlesBlending: true, + lightIntensity: 0.3, + widthRadius: 100, + widthRatio: 1.2, + topHeightRadius: 80, + bottomHeightRadius: 100, + trails: 0, + trailWidth: 40, + trailLength: 100, + trailDecay: 450, + color: 'white', + colorGold: '#b89d18', + xThickness: 7, + xRandomnessFactor: 2.2, + xRandomnessShape: 2.2, + xRandomness: 5, + yThickness: 20, + max_speed: 0.1, + min_speed: -0.1, + showGold: true, +} + +const useParticlesConfig = (users: any): any => { + const isWindowUndefined = typeof window === 'undefined' + if (isWindowUndefined) return null + const hash = window.location.hash + const isDebugMode = hash.includes('#debug') + const [particles, setParticles] = useState(users) + + const [config, setConfig] = useState(defaultConfig) + + const handleSetConfig = (name: string, value: any) => { + setConfig((prevConfig) => ({ ...prevConfig, [name]: value })) + } + + const init = async () => { + if (!isDebugMode) return + const dat = await import('dat.gui') + const gui = new dat.GUI() + const particlesFolder = gui.addFolder('Particles') + const shapeFolder = gui.addFolder('Shape') + const speedFolder = gui.addFolder('Speed') + // const trailFolder = gui.addFolder('Trail') + gui.width = 500 + particlesFolder + .add(config, 'particles') + .min(1) + .max(5000) + .step(1) + .name('Count') + .onChange((value) => { + handleSetConfig('particles', value) + setParticles(range(0, value)) + }) + particlesFolder + .add(config, 'particlesSize') + .min(1) + .max(10) + .step(0.05) + .name('Size') + .onChange((value) => handleSetConfig('particlesSize', value)) + particlesFolder + .add(config, 'goldParticlesSize') + .min(1) + .max(10) + .step(0.05) + .name('Gold Particles Size') + .onChange((value) => handleSetConfig('goldParticlesSize', value)) + particlesFolder + .add(config, 'particlesSides') + .min(3) + .max(20) + .step(1) + .name('Sides') + .onChange((value) => handleSetConfig('particlesSides', value)) + particlesFolder + .add(config, 'lightIntensity') + .min(0) + .max(10) + .step(0.05) + .name('Light intensity') + .onChange((value) => handleSetConfig('lightIntensity', value)) + particlesFolder + .add(config, 'showGold') + .name('Show Gold Particles') + .onChange((value) => handleSetConfig('showGold', value)) + particlesFolder + .add(config, 'particlesBlending') + .name('Blending') + .onChange((value) => handleSetConfig('particlesBlending', value)) + shapeFolder + .add(config, 'widthRadius') + .min(1) + .max(200) + .step(1) + .name('Width Radius') + .onChange((value) => handleSetConfig('widthRadius', value)) + shapeFolder + .add(config, 'widthRatio') + .min(0.5) + .max(3) + .step(0.01) + .name('Top/Bottom Ratio') + .onChange((value) => handleSetConfig('widthRatio', value)) + shapeFolder + .add(config, 'topHeightRadius') + .min(1) + .max(200) + .step(1) + .name('Height Radius - Top') + .onChange((value) => handleSetConfig('topHeightRadius', value)) + shapeFolder + .add(config, 'bottomHeightRadius') + .min(1) + .max(200) + .step(1) + .name('Height Radius - Bottom') + .onChange((value) => handleSetConfig('bottomHeightRadius', value)) + shapeFolder + .add(config, 'xThickness') + .min(1) + .max(100) + .step(0.1) + .name('Stroke Width') + .onChange((value) => handleSetConfig('xThickness', value)) + shapeFolder + .add(config, 'xRandomnessShape') + .min(0) + .max(5) + .step(0.001) + .name('Randomness shape') + .onChange((value) => handleSetConfig('xRandomnessShape', value)) + shapeFolder + .add(config, 'xRandomness') + .min(0) + .max(50) + .step(0.01) + .name('Randomness') + .onChange((value) => handleSetConfig('xRandomness', value)) + shapeFolder + .add(config, 'yThickness') + .min(1) + .max(50) + .step(0.1) + .name('y thickness') + .onChange((value) => handleSetConfig('yThickness', value)) + speedFolder + .add(config, 'min_speed') + .min(-6) + .max(6) + .step(0.01) + .name('Min speed') + .onChange((value) => handleSetConfig('min_speed', value)) + speedFolder + .add(config, 'max_speed') + .min(-6) + .max(6) + .step(0.01) + .name('Max speed') + .onChange((value) => handleSetConfig('max_speed', value)) + + particlesFolder.open() + shapeFolder.open() + speedFolder.open() + } + + useEffect(() => { + init() + }, []) + + return { config, handleSetConfig, particles, setParticles, isDebugMode } +} + +export default useParticlesConfig diff --git a/apps/www/components/LaunchWeek/8/Countdown.tsx b/apps/www/components/LaunchWeek/8/Countdown.tsx new file mode 100644 index 00000000000..322b7a944db --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Countdown.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import Countdown from 'react-countdown' +import { CountdownWidget } from 'ui' +import { LW8_LAUNCH_DATE } from '~/lib/constants' + +const CountdownComponent = () => { + const renderer = ({ days, hours, minutes, seconds, completed }: any) => { + if (completed) { + // Render a completed state + return null + } else { + // Render countdown + return + } + } + + return +} + +export default CountdownComponent diff --git a/apps/www/components/LaunchWeek/8/LabelBadge.tsx b/apps/www/components/LaunchWeek/8/LabelBadge.tsx new file mode 100644 index 00000000000..521970158fa --- /dev/null +++ b/apps/www/components/LaunchWeek/8/LabelBadge.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +interface Props { + text: string + className?: string +} + +export default function LabelBadge({ text, className }: Props) { + return ( + + {text} + + ) +} diff --git a/apps/www/components/LaunchWeek/8/LaunchWeekLogoHeader.tsx b/apps/www/components/LaunchWeek/8/LaunchWeekLogoHeader.tsx new file mode 100644 index 00000000000..4c0786c62c3 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/LaunchWeekLogoHeader.tsx @@ -0,0 +1,31 @@ +import Link from 'next/link' +import CountdownComponent from './Countdown' + +export function LaunchWeekLogoHeader() { + return ( + + ) +} diff --git a/apps/www/components/LaunchWeek/8/LaunchWeekPrizeCard.tsx b/apps/www/components/LaunchWeek/8/LaunchWeekPrizeCard.tsx new file mode 100644 index 00000000000..353fb85499e --- /dev/null +++ b/apps/www/components/LaunchWeek/8/LaunchWeekPrizeCard.tsx @@ -0,0 +1,69 @@ +import Image from 'next/image' +import React from 'react' +import { motion } from 'framer-motion' + +type animateFromType = 'left' | 'right' | 'up' | 'down' | 'opacityOnly' + +export default function LaunchWeekPrizeCard({ + imageUrl = '', + imageWrapperClassName, + content, + className, + animateFrom, + imgObjectPosition = 'center', +}: { + imageUrl: string + imageWrapperClassName?: string + content: any + className?: string + animateFrom?: animateFromType + imgObjectPosition?: string +}) { + const finalState = { x: 0, y: 0, opacity: 1 } + + function getAnimationFrom(animationDirection: animateFromType) { + switch (animationDirection) { + case 'left': + return { x: -20, opacity: 0 } + case 'up': + return { y: -20, opacity: 0 } + case 'right': + return { x: 20, opacity: 0 } + case 'down': + return { y: 20, opacity: 0 } + case 'opacityOnly': + return { opacity: 0 } + } + } + + return ( + +
+
+ +
+
{content && content}
+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/LaunchWeekPrizeSection.tsx b/apps/www/components/LaunchWeek/8/LaunchWeekPrizeSection.tsx new file mode 100644 index 00000000000..b59b35da825 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/LaunchWeekPrizeSection.tsx @@ -0,0 +1,99 @@ +import React from 'react' +import LabelBadge from './LabelBadge' +import LaunchWeekPrizeCard from './LaunchWeekPrizeCard' +import Image from 'next/image' + +export default function LaunchWeekPrizeSection({ + className, + ticket, +}: { + className?: string + ticket?: any +}) { + const Ticket = ticket + + return ( +
+
+ prizes background +
+
+
+
+
+
+ prizes +
+

Get your winning ticket

+

+ Join us on August 11th for Launch Week 8's final day +
and find out if you are one of the lucky + winners. Get sharing! +

+
+
+ {ticket && } +
+
+ + Supabase Desk Mat + + } + animateFrom="down" + /> + + Supabase T-Shirt + + } + animateFrom="down" + /> + + Supabase Cap + + } + /> + + Supabase Sticker Pack + + } + /> +
+
+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/Ticket.tsx b/apps/www/components/LaunchWeek/8/Ticket/Ticket.tsx new file mode 100644 index 00000000000..71a0b3113ed --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/Ticket.tsx @@ -0,0 +1,79 @@ +import styles from './ticket-visual.module.css' +import TicketProfile from './TicketProfile' +import TicketNumber from './TicketNumber' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' +import TicketHeader from './TicketHeader' +import Image from 'next/image' +import TicketForm from './TicketForm' +import TicketFooter from './TicketFooter' + +type TicketGenerationState = 'default' | 'loading' +type Props = { + user: UserData + ticketGenerationState?: TicketGenerationState + setTicketGenerationState?: any +} + +export default function TicketVisual({ + user, + ticketGenerationState = 'default', + setTicketGenerationState, +}: Props) { + const { username, golden = false, bg_image_id: bgImageId, ticketNumber } = user + + const ticketBg = { + regular: { + background: `/images/launchweek/8/ticket-bg/regular.png`, + }, + golden: { + background: `/images/launchweek/8/ticket-bg/golden.png`, + }, + } + + const CURRENT_TICKET = golden ? 'golden' : 'regular' + const CURRENT_TICKET_BG = ticketBg[CURRENT_TICKET].background + + return ( +
+
+ {username ? ( +
+ +
+ +
+ + +
+ ticket background +
+
+ ) : ( + + )} +
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketActions.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketActions.tsx new file mode 100644 index 00000000000..3a81766bc57 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketActions.tsx @@ -0,0 +1,119 @@ +import { useEffect, useRef, useState } from 'react' +import { SITE_URL, TWEET_TEXT, TWEET_TEXT_GOLDEN } from '~/lib/constants' +import { IconCheckCircle } from 'ui' +import useConfData from '~/components/LaunchWeek/hooks/use-conf-data' +import { useParams } from '~/hooks/useParams' +import TicketForm from './TicketForm' + +type TicketGenerationState = 'default' | 'loading' + +type Props = { + username: string + golden?: boolean + ticketGenerationState?: TicketGenerationState + setTicketGenerationState: (ticketGenerationState: TicketGenerationState) => void +} + +export default function TicketActions({ + username, + golden = false, + ticketGenerationState, + setTicketGenerationState, +}: Props) { + const [imgReady, setImgReady] = useState(false) + const [loading, setLoading] = useState(false) + const downloadLink = useRef() + const link = `${SITE_URL}/tickets/${username}?lw=8${golden ? `&golden=true` : ''}` + const permalink = encodeURIComponent(link) + const text = golden ? TWEET_TEXT_GOLDEN : TWEET_TEXT + const encodedText = encodeURIComponent(text) + const { userData, supabase } = useConfData() + const tweetUrl = `https://twitter.com/intent/tweet?url=${permalink}&via=supabase&text=${encodedText}` + const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${permalink}` + const downloadUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw8-ticket?username=${encodeURIComponent( + username + )}` + const params = useParams() + const sharePage = params.username + const LW_TABLE = 'lw8_tickets' + + useEffect(() => { + setImgReady(false) + + const img = new Image() + + img.src = downloadUrl + img.onload = () => { + setImgReady(true) + setLoading(false) + if (downloadLink.current) { + downloadLink.current.click() + downloadLink.current = undefined + } + } + }, [downloadUrl]) + + const handleShare = async (social: 'twitter' | 'linkedin') => { + if (!supabase) return + if (social === 'twitter') { + await supabase.from(LW_TABLE).update({ sharedOnTwitter: 'now' }).eq('username', username) + window.open(tweetUrl, '_blank') + } else if (social === 'linkedin') { + await supabase.from(LW_TABLE).update({ sharedOnLinkedIn: 'now' }).eq('username', username) + window.open(linkedInUrl, '_blank') + } + } + + return ( +
+ {!sharePage ? ( + <> +
+
+
+ +
+ Connect with GitHub +
+
+ + + + ) : ( + !username && ( + + ) + )} +
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketContainer.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketContainer.tsx new file mode 100644 index 00000000000..7416c69bb09 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketContainer.tsx @@ -0,0 +1,179 @@ +import { useState } from 'react' +import { SupabaseClient } from '@supabase/supabase-js' +import Link from 'next/link' +import cn from 'classnames' +import { Button } from 'ui' + +import styles from './ticket.module.css' +import styleUtils from '../../utils.module.css' +import Ticket from './Ticket' +import TicketActions from '~/components/LaunchWeek/8/Ticket/TicketActions' +import TicketCopy from '~/components/LaunchWeek/8/Ticket/ticket-copy' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' +import useWinningChances from '~/components/LaunchWeek/hooks/useWinningChances' +import { SITE_URL } from '~/lib/constants' +import { useBreakpoint } from 'common/hooks/useBreakpoint' +import TicketCustomizationForm from './TicketCustomizationForm' +import TicketDisclaimer from './TicketDisclaimer' + +type TicketGenerationState = 'default' | 'loading' + +type Props = { + user: UserData + supabase: SupabaseClient + referrals: number + sharePage?: boolean +} + +export default function TicketContainer({ user, sharePage, referrals, supabase }: Props) { + const { username, name, golden } = user + const isMobile = useBreakpoint(1023) + const [ticketGenerationState, setTicketGenerationState] = + useState('default') + const winningChances = useWinningChances() + + if (!user.username) + return ( +
+
+ + +
+
+ ) + + return ( +
+
+
+

+ {!sharePage ? ( + name ? ( + <> + {winningChances === 1 && ( + <> + You're in! + Now make it unique and share. + + )} + {winningChances === 2 && ( + <> + That's x2! +
Share again to get a golden ticket. + + )} + {winningChances === 3 && ( + <> + You have a golden + chance of winning! + + )} + + ) : ( + <> + Generate your ticket.
+ Win the SupaKeyboard. + + ) + ) : ( + <> + {name ? name : username}'s
+ unique ticket + + )} +

+ +
+ {!sharePage ? ( + golden ? ( +

+ Join us on August 11th for Launch Week 8's final day and find out if you are one + of the lucky winners. +

+ ) : ( +

+ Customize your ticket and boost your chances of winning{' '} + + limited edition awards + {' '} + by sharing it with the community. +

+ ) + ) : ( + <> +

+ Generate and share your own custom ticket for a chance to win{' '} + + awesome swag + + . +

+ + + + )} +
+ + {!sharePage && user.username && ( + + )} +
+
+
+
+ + {username && ( +
+ + +
+ )} +
+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketCustomizationForm.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketCustomizationForm.tsx new file mode 100644 index 00000000000..dd9aa9e243c --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketCustomizationForm.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react' +import { Badge, IconCheck, Input } from 'ui' +import { UserData } from '../../hooks/use-conf-data' +import { SupabaseClient } from '@supabase/supabase-js' +import { useDebounce } from 'common' + +interface Props { + supabase: SupabaseClient + user: UserData +} + +const TicketCustomizationForm = ({ supabase, user }: Props) => { + const defaultFormValues = { + role: user.metadata?.role, + company: user.metadata?.company, + location: user.metadata?.location, + } + const [formData, setFormData] = useState(defaultFormValues) + const [formState, setFormState] = useState<'idle' | 'saved' | 'saving' | 'error'>('idle') + const IS_SAVING = formState === 'saving' + const IS_SAVED = formState === 'saved' + const HAS_ERROR = formState === 'error' + + const handleInputChange = (name: string, value: string) => { + setFormData((prev) => ({ ...prev, [name]: value })) + } + + const handleFormSubmit = async () => { + setFormState('saving') + const payload = { metadata: formData } + + if (supabase) { + await supabase + .from('lw8_tickets') + .update(payload) + .eq('username', user.username) + .then((res) => { + if (res.error) return setFormState('error') + setFormState('saved') + setTimeout(() => { + setFormState('idle') + }, 1800) + }) + } + } + + const debouncedChangeHandler = useDebounce(handleFormSubmit, 1200) + + return ( +
debouncedChangeHandler()}> +
+ {!IS_SAVED && !HAS_ERROR && ( + + Connected account + + )} + {IS_SAVED && ( + Saved + )} + {HAS_ERROR && ( + + Something went wrong + + )} + @{user.username} +
+ } + /> + { + handleInputChange('role', event.target.value) + }} + disabled={IS_SAVING} + maxLength={30} + icon={ + + } + /> + { + handleInputChange('company', event.target.value) + }} + disabled={IS_SAVING} + icon={ + + } + /> + { + handleInputChange('location', event.target.value) + }} + disabled={IS_SAVING} + maxLength={20} + icon={ + + } + /> + + ) +} + +export default TicketCustomizationForm diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketDisclaimer.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketDisclaimer.tsx new file mode 100644 index 00000000000..e23e77804e2 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketDisclaimer.tsx @@ -0,0 +1,7 @@ +const TicketDisclaimer = ({ className, children }: { className?: string; children?: any }) => ( +

+ {children || 'By registering you accept to receive email updates on Supabase Launch Week.'} +

+) + +export default TicketDisclaimer diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketFooter.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketFooter.tsx new file mode 100644 index 00000000000..ebffc62ecf2 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketFooter.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { LW8_DATE } from '~/lib/constants' + +interface Props {} + +export default function TicketFooter({}: Props) { + return ( +
+ {LW8_DATE} + supabase.com/launch-week +
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketForm.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketForm.tsx new file mode 100644 index 00000000000..51a1b7bcb41 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketForm.tsx @@ -0,0 +1,169 @@ +import { useState, useRef, useEffect } from 'react' +import { useRouter } from 'next/router' +import cn from 'classnames' +import { SITE_ORIGIN } from '~/lib/constants' +import useConfData from '~/components/LaunchWeek/hooks/use-conf-data' +import formStyles from './form.module.css' +import ticketFormStyles from './ticket-form.module.css' +import { Button, IconCheckCircle, IconLoader } from 'ui' + +type FormState = 'default' | 'loading' | 'error' +type TicketGenerationState = 'default' | 'loading' + +type Props = { + defaultUsername?: string + ticketGenerationState?: TicketGenerationState + setTicketGenerationState: any +} + +export default function TicketForm({ defaultUsername = '', setTicketGenerationState }: Props) { + const [username, setUsername] = useState(defaultUsername) + const [formState, setFormState] = useState('default') + const [errorMsg] = useState('') + const { supabase, session, setUserData, setPageState, userData } = useConfData() + const [realtimeChannel, setRealtimeChannel] = useState | null>(null) + const formRef = useRef(null) + const router = useRouter() + + useEffect(() => { + if (supabase && session?.user && !userData.id) { + document.body.classList.add('ticket-generated') + const username = session.user.user_metadata.user_name + setUsername(username) + const name = session.user.user_metadata.full_name + const email = session.user.email + supabase + .from('lw8_tickets') + .insert({ email, name, username, referred_by: router.query?.referral ?? null }) + .eq('email', email) + .select() + .single() + .then(async ({ error }: any) => { + // If error because of duplicate email, ignore and proceed, otherwise sign out. + if (error && error?.code !== '23505') return supabase.auth.signOut() + const { data } = await supabase + .from('lw8_tickets_golden') + .select('*') + .eq('username', username) + .single() + if (data) { + setUserData(data) + } + setFormState('default') + + // Prefetch GitHub avatar + new Image().src = `https://github.com/${username}.png` + + // Prefetch the twitter share URL to eagerly generate the page + fetch(`/launch-week/tickets/${username}`).catch((_) => {}) + + setPageState('ticket') + + // Listen to realtime changes + if (!realtimeChannel && !data?.golden) { + const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + table: 'lw8_tickets', + filter: `username=eq.${username}`, + }, + (payload: any) => { + const golden = !!payload.new.sharedOnTwitter && !!payload.new.sharedOnLinkedIn + setUserData({ + ...payload.new, + golden, + }) + if (golden) { + channel.unsubscribe() + } + } + ) + .subscribe() + setRealtimeChannel(channel) + } + }) + } + return () => { + // Cleanup realtime subscription on unmount + realtimeChannel?.unsubscribe() + } + }, [session]) + + return formState === 'error' ? ( +
+
+
+
{errorMsg}
+ +
+
+
+ ) : ( +
{ + e.preventDefault() + + if (formState !== 'default') { + setTicketGenerationState('default') + setFormState('default') + return + } + + setFormState('loading') + setTicketGenerationState('loading') + + await supabase.auth.signInWithOAuth({ + provider: 'github', + options: { + redirectTo: `${SITE_ORIGIN}/launch-week/${ + userData.username ? '?referral=' + userData.username : '' + }`, + }, + }) + }} + className="flex flex-col h-full items-center justify-center relative z-20" + > +
+
+ +
+ {/* {!session &&

Only public info will be used.

} */} +
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketHeader.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketHeader.tsx new file mode 100644 index 00000000000..217cf6a38bb --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketHeader.tsx @@ -0,0 +1,20 @@ +import Image from 'next/image' +import React from 'react' + +interface Props { + golden?: boolean +} + +export default function TicketHeader({ golden = false }: Props) { + return ( +
+ Launch Week 8 logo +
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketNumber.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketNumber.tsx new file mode 100644 index 00000000000..d7348a6b545 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketNumber.tsx @@ -0,0 +1,31 @@ +import styles from './ticket.module.css' + +type Props = { + number: number | undefined + golden?: boolean +} + +export default function TicketNumber({ number, golden = false }: Props) { + const numDigits = `${number}`.length + const prefix = `00000000`.slice(numDigits) + const ticketNumberText = `NO ${prefix}${number}` + + return ( + <> +
+ + {ticketNumberText} + +
+ + ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/TicketProfile.tsx b/apps/www/components/LaunchWeek/8/Ticket/TicketProfile.tsx new file mode 100644 index 00000000000..e1cacdff171 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/TicketProfile.tsx @@ -0,0 +1,42 @@ +import { UserData } from '../../hooks/use-conf-data' + +type TicketGenerationState = 'default' | 'loading' +type Props = { + user: UserData + ticketGenerationState?: TicketGenerationState + setTicketGenerationState?: (ticketGenerationState: TicketGenerationState) => void + golden?: boolean +} + +export default function TicketProfile({ user, ticketGenerationState, golden = false }: Props) { + const { username, name, metadata } = user + + const HAS_ROLE = !!metadata?.role + const HAS_COMPANY = !!metadata?.company + const HAS_LOCATION = !!metadata?.location + const HAS_NO_META = !HAS_ROLE && !HAS_COMPANY && !HAS_LOCATION + + return ( +
+
+

{name || username || 'Your Name'}

+ {HAS_NO_META && username &&

@{username}

} +
+ {HAS_ROLE && {metadata?.role}} + {HAS_COMPANY && ( + + {HAS_ROLE && ' '} + at {metadata?.company} + + )} + {HAS_LOCATION && ( + + {' '} + {(HAS_ROLE || HAS_COMPANY) && '—'} {metadata?.location} + + )} +
+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/form.module.css b/apps/www/components/LaunchWeek/8/Ticket/form.module.css new file mode 100644 index 00000000000..5b6a078561b --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/form.module.css @@ -0,0 +1,244 @@ +/* .form { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + margin-bottom: 12px; + gap: 12px; +} */ + +/* .formLeft { + align-items: center; +} +.formCenter { + align-items: center; +} */ + +/* @media (min-width: 1200px) { + .formLeft { + align-items: start; + } + .formCenter { + align-items: center; + } +} */ +/* +.formInfo { + max-width: 520px; +} */ + +.formInfo h3 { + font-size: 1.25em; + font-weight: 400; +} + +.formInfo p { + font-size: 1.1em; + line-height: 1.4; + margin-bottom: 32px; + color: var(--lw-secondary-color); +} + +@media (min-width: 1200px) { + /* .form.share-page { + justify-content: flex-start; + } */ +} + +/* .formInfoLeft { + text-align: center; +} +.formInfoCenter { + text-align: center; + margin: 0 auto; +} */ + +@media (min-width: 1200px) { + .formInfoLeft { + text-align: left; + } + .formInfoCenter { + text-align: center; + margin: 0 auto; + } +} + +.input { + border: none; + width: 100%; + background: transparent; + outline: none; + height: 56px; + padding-left: 15px; + padding-right: 15px; + font-size: var(--text-md); + color: #fff; + font-family: inherit; +} + +@media (min-width: 768px) { + .input { + width: calc(100% - var(--space-32x)); + padding-right: 0; + } +} + +.input::placeholder { + color: var(--lw-secondary-color); +} + +.input-label { + background-color: var(--gray); + border-radius: var(--space-2x); + border: 1px solid transparent; + transition: border-color 0.2s ease, background-color 0.2s ease; + display: block; +} + +.input-label.focused { + background-color: #33373c; +} + +.input-label.error { + background: red; +} + +.input-label.success { + background: #0070f3; +} + +.input-text { + display: flex; + align-items: center; + width: 100%; +} + +.form-row { + position: relative; + max-width: 400px; + width: 100%; +} + +@media (min-width: 768px) { + .form-row { + max-width: 480px; + } +} + +.submit { + width: 100%; + height: 56px; + margin-top: var(--space-4x); + border-radius: var(--space-2x); + border: 1px solid var(--brand); + background: var(--brand); + cursor: pointer; + font-family: inherit; + font-size: var(--text-md); + letter-spacing: -0.02em; + outline: none; + font-weight: 500; + color: white; + transition: background-color 0.2s ease, color 0.2s ease; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.submit.generate-with-github { + display: flex; + margin-bottom: 12px; + position: relative; +} + +@media (min-width: 768px) { + .submit.register { + width: 120px; + height: 40px; + margin-top: 0; + position: absolute; + border-radius: 5px; + right: var(--space-2x); + top: var(--space-2x); + } +} + +.submit.loading { + cursor: default; +} + +.submit.default:hover, +.submit.default:focus { + background: black; + color: var(--brand); +} + +.submit.default.generate-with-github:hover path, +.submit.default.generate-with-github:focus path { + fill: var(--brand); +} + +.submit.error:hover, +.submit.error:focus { + background: #000; + color: #fff; +} + +.submit.default:disabled, +.submit.default:disabled:hover, +.submit.default:disabled:focus { + cursor: default; + background: var(--gray); + border-color: var(--gray); + color: #fff; + justify-content: flex-start; + overflow: hidden; +} + +.submit.default.generate-with-github:disabled path, +.submit.default.generate-with-github:disabled:hover path, +.submit.default.generate-with-github:disabled:focus path { + fill: #fff; +} + +.submit.default.generate-with-github.not-allowed:disabled { + cursor: not-allowed; +} + +@media (min-width: 1200px) { + .form-row { + margin: 0; + } + + .submit.generate-with-github { + width: 240px; + } +} + +.stage-btn { + background: #323332; + border: 2px solid #34b27b; +} + +.github-wrapper { + display: flex; + align-items: center; + flex-direction: column; +} + +.or-divider { + width: 100%; + text-align: center; + color: var(--lw-secondary-color); + margin: var(--space-4x) 0; +} + +@media (min-width: 1200px) { + .github-wrapper { + flex-direction: row; + } + .or-divider { + width: 240px; + margin: 0; + } +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/ticket-actions.module.css b/apps/www/components/LaunchWeek/8/Ticket/ticket-actions.module.css new file mode 100644 index 00000000000..d13c42418d7 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/ticket-actions.module.css @@ -0,0 +1,70 @@ +.button { + border-radius: var(--space-2x); + border: 1px solid #fff; + color: #000; + background: #fff; + cursor: pointer; + font-family: inherit; + font-size: var(--text-md); + letter-spacing: -0.02em; + font-weight: 500; + outline: none; + transition: background-color 0.2s ease; + display: grid; + grid-template-columns: 24px 1fr; + text-align: center; + align-items: center; + padding: 0 var(--space-4x); + height: 55px; + width: 100%; + max-width: 400px; + margin: var(--space-2x) auto; +} + +.loading { + grid-template-columns: 1fr; + justify-items: center; +} + +@media (min-width: 768px) { + .button { + width: 200px; + margin: 0 6px; + } +} + +.button:hover, +.button:focus { + background: black; + color: #fff; +} + +.first { + animation-delay: 2s; +} +.second { + animation-delay: 2.2s; +} +.third { + animation-delay: 2.4s; +} + +:global(.ticket-generated) .first { + animation-delay: 0.6s; +} +:global(.ticket-generated) .second { + animation-delay: 0.8s; +} +:global(.ticket-generated) .third { + animation-delay: 1s; +} + +.linkedin-button { + display: none !important; +} + +@media (min-width: 768px) { + .linkedin-button { + display: grid !important; + } +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/ticket-copy.module.css b/apps/www/components/LaunchWeek/8/Ticket/ticket-copy.module.css new file mode 100644 index 00000000000..65007c67559 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/ticket-copy.module.css @@ -0,0 +1,190 @@ +.wrapper { + color: var(--lw-secondary-color); + display: flex; + align-items: center; + justify-content: center; + animation-delay: 2.6s; + flex-direction: column; +} + +:global(.ticket-generated) .wrapper { + animation-delay: 1.2s; +} + +.label { + width: 100%; + margin-bottom: var(--space-2x); +} + +.label-wrapper { + display: flex; + align-items: center; + max-width: 400px; + width: 100%; + justify-content: space-between; +} + +@media (min-width: 768px) { + .wrapper { + flex-direction: row; + } + + .label { + width: auto; + margin-right: 10px; + margin-bottom: 0px; + } + + .label-wrapper { + width: auto; + } +} + +.field { + position: relative; + border-radius: var(--space-2x); + overflow: hidden; + background: var(--gray); +} + +@media (min-width: 768px) { + .field { + padding-right: 50px; + } + .field.desktop-copy-disabled { + padding-right: 0px; + } +} + +.mobile-copy { + margin-bottom: 10px; + display: flex; +} + +.mobile-copy-disabled.mobile-copy-disabled { + display: none; +} + +.mobile-copy * { + color: var(--lw-secondary-color); + transition: color 0.2s ease; +} + +.mobile-copy:hover * { + color: #fff; + transition: color 0.2s ease; +} + +.field * { + color: var(--lw-secondary-color); + transition: color 0.2s ease; +} + +.field:hover * { + color: #fff; + transition: color 0.2s ease; +} + +.url { + width: calc(100vw - 36px); + user-select: all; + overflow-x: scroll; + white-space: nowrap; + display: block; + + padding: var(--space-2x) 14px; + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +@media (min-width: 436px) { + .url { + width: 400px; + } +} + +@media (min-width: 768px) { + .url { + width: 400px; + } + .mobile-copy { + margin-bottom: 0; + display: none; + } +} + +.url::-webkit-scrollbar { + display: none; +} + +/* .fade { + position: absolute; + background: linear-gradient(90deg, rgba(37, 39, 41, 0%) 0%, #252729 100%); + top: 0; + bottom: 0; + right: 0px; + width: 50px; + z-index: 1; + display: block; + border-radius: var(--space-2x); + pointer-events: none; +} */ + +.desktop-copy.desktop-copy-disabled { + display: none; +} + +@media (min-width: 768px) { + .fade { + right: 50px; + } + .fade.desktop-copy-disabled { + right: 0px; + } +} + +.copied { + display: flex; + z-index: 2; + align-items: center; + justify-content: flex-end; + opacity: 0; + transition: opacity 0.2s ease !important; +} + +.copied.visible { + opacity: 1; +} + +.copy-button { + background: none; + outline: none; + border: none; + cursor: pointer; + z-index: 2; + border-radius: var(--space-2x); + width: 40px; + display: flex; + align-items: center; + justify-content: center; + margin-right: -10px; +} + +@media (min-width: 768px) { + .copied { + position: absolute; + right: 50px; + top: 0px; + bottom: 0px; + background: linear-gradient(90deg, rgba(37, 39, 41, 0%) 0px, #252729 40px); + padding-left: 50px; + } + .copy-button { + position: absolute; + top: 0px; + right: 0px; + bottom: 0px; + margin-right: 0px; + width: 50px; + } +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/ticket-copy.tsx b/apps/www/components/LaunchWeek/8/Ticket/ticket-copy.tsx new file mode 100644 index 00000000000..65e700c6730 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/ticket-copy.tsx @@ -0,0 +1,85 @@ +import { useEffect, useState, useRef } from 'react' +import cn from 'classnames' +import { SITE_URL } from '~/lib/constants' +import styleUtils from '../../utils.module.css' +import { IconCopy, IconCheck } from 'ui' + +type Props = { + username: string + isGolden?: boolean +} + +export default function TicketCopy({ username, isGolden }: Props) { + const [fadeOpacity, setFadeOpacity] = useState(1) + const [scrolling, setScrolling] = useState(false) + const [copyEnabled, setCopyEnabled] = useState(false) + const [copied, setCopied] = useState(false) + const scrollRef = useRef(null) + const buttonRef = useRef(null) + const url = `${SITE_URL}/tickets/${username}?lw=8${isGolden ? `&golden=true` : ''}` + useEffect(() => { + if (navigator.clipboard) { + setCopyEnabled(true) + } + }, []) + + return ( +
+ +
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/ticket-form.module.css b/apps/www/components/LaunchWeek/8/Ticket/ticket-form.module.css new file mode 100644 index 00000000000..d10b43a0284 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/ticket-form.module.css @@ -0,0 +1,65 @@ +.githubIcon { + margin-left: 12px; + margin-right: var(--space-4x); + display: inline-flex; +} + +.checkIcon { + position: absolute; + right: var(--space-4x); + display: inline-flex; +} + +.generateWithGithub { + display: flex; + align-items: center; + font-weight: 500; +} + +.stageIcon { + position: absolute; + left: var(--space-4x); + display: inline-flex; +} + +.form-row { + margin-left: auto; + margin-right: auto; + max-width: 600px; +} + +.description { + color: var(--secondary-color); + margin: 0; + text-align: center; + margin-left: var(--space-4x); +} + +@media (min-width: 768px) { + .form-row { + margin-bottom: 20px; + } +} + +@media (min-width: 1200px) { + .form-row { + margin: 0; + } + + .description { + text-align: left; + } + + .generateWithGithub { + width: 100%; + } +} + +.learn-more { + color: #fff; +} + +.learn-more:hover { + text-decoration: underline; + color: #fff; +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/ticket-profile.module.css b/apps/www/components/LaunchWeek/8/Ticket/ticket-profile.module.css new file mode 100644 index 00000000000..ac08447a22a --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/ticket-profile.module.css @@ -0,0 +1,149 @@ +.image { + width: 50px; + height: 50px; + border-radius: 50%; + border: 1px solid #999; +} + +@media (min-width: 768px) { + .image { + width: calc(100px * var(--size)); + height: calc(100px * var(--size)); + } +} + +.name { + font-size: 24px; + display: inline-block; + line-height: 1.15; + letter-spacing: -0.02em; + margin: 0; +} + +.name-golden { + color: var(--gold-primary); +} + +@media (min-width: 768px) { + .name { + font-size: calc(var(--space-8x) * var(--size)); + margin-bottom: 5px; + } +} + +.username { + display: inline-block; + font-size: calc(1em * var(--size)); + color: var(--lw-secondary-color); + display: flex; + align-items: center; + margin: 0; +} + +.username-golden { + color: var(--gold-secondary); +} + +.githubIcon { + margin-right: calc(5px * var(--size)); + display: inline-flex; +} + +.githubIcon-golden svg { + color: var(--gold-secondary); + opacity: 0.5; +} + +.empty-icon { + display: block; + background: linear-gradient(320deg, #121212, #191919); + width: 60px; +} + +.empty-icon--golden { + display: block; + background: linear-gradient(90deg, #fff9eb, #e2ba52); + width: 60px; + border: 1px solid #ecc154ad; +} + +@media (min-width: 768px) { + .empty-icon { + width: calc(80px * var(--size)); + } + + .empty-icon--golden { + width: calc(80px * var(--size)); + } +} + +@keyframes loading { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} +/* +.skeleton { + display: flex; + align-items: center; + border-radius: 25px; +} */ + +.skeleton.loaded { + width: unset !important; +} + +.skeleton:not(.wrapper):not(.show) { + display: none; +} + +.wrapper:not(.show)::before { + content: none; +} + +.skeleton:not(.wrapper):not(.loaded) { + border-radius: var(--space); + background-image: linear-gradient(270deg, #252729, #34383d, #34383d, #252729); + background-size: 200% 100%; + animation: loading 2s ease-in-out infinite; +} + +.wrapper { + position: relative; +} + +.wrapper::before { + content: ''; + position: absolute; + top: -1px; + left: -1px; + right: 0; + bottom: 0; + border-radius: var(--space); + z-index: 100; + background-image: linear-gradient(270deg, #111111, #333333, #333333, #111111); + background-size: 200% 100%; + animation: loading 2s ease-in-out infinite; +} + +.inline { + display: inline-block !important; +} + +.rounded, +.rounded.wrapper::before { + width: calc(80px * var(--size)); + height: calc(80px * var(--size)); + border-radius: 50% !important; +} + +@media (min-width: 768px) { + .rounded, + .rounded.wrapper::before { + width: calc(110px * var(--size)); + height: calc(110px * var(--size)); + } +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/ticket-visual.module.css b/apps/www/components/LaunchWeek/8/Ticket/ticket-visual.module.css new file mode 100644 index 00000000000..f399e967aa1 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/ticket-visual.module.css @@ -0,0 +1,70 @@ +.visual { + /* transform: translateZ(0); */ + background: linear-gradient( + 30deg, + hsla(274, 100%, 77%, 0.05) 0%, + hsla(200, 43%, 1%, 0.2) 30%, + hsla(200, 43%, 1%, 0.2) 70%, + hsla(151, 50%, 53%, 0.1) 100% + ), + hsla(200, 43%, 1%, 0.8); + /* background: hsla(200, 43%, 1%, 0.2); */ + box-shadow: 0px 1px 27px rgba(158, 68, 239, 0.06), + /* 0 0 0 1px rgba(121, 89, 134, 0.25), */ 0 10px 20px 0px rgba(20, 17, 21, 0.8), + inset 0 0.2px 1px 0.2px rgba(214, 210, 210, 0.4), + inset -0.4px -0.4px 1px -0.3px rgba(74, 252, 175, 0.4); + /* border: 1px solid rgba(0, 0, 0, 0.05); */ + /* border-radius: 16px; */ + + -webkit-backdrop-filter: blur(2px); + backdrop-filter: blur(2px); + + /* border: 1px solid #1c1c1c; */ + /* animation: 2s ease-in-out infinite alternate; */ + + background-repeat: no-repeat; + background-size: contain; +} + +.visual--gold { + background: radial-gradient(#fcdf9d50, #dbb33340); +} + +.visual--gold::before { + content: ' '; + position: absolute; + inset: 0; + filter: drop-shadow(0 0 60px #fcdf9d); + background: radial-gradient(#fcdf9d50, #dbb33320); + /* border: 1px solid #dbb33320; */ +} + +@keyframes opacity-pulse { + from { + opacity: 0.5; + } + + to { + opacity: 1; + } +} + +@keyframes svg-shadow { + from { + filter: drop-shadow(0 0 2px #fff) drop-shadow(0 0 4px #d99f0c) drop-shadow(0 0 6px #d99f0c); + } + + to { + filter: drop-shadow(0 0 10px #fff) drop-shadow(0 0 6px #d99f0c) drop-shadow(0 0 9px #d99f0c); + } +} + +@keyframes blank-svg-shadow { + from { + filter: drop-shadow(0 0 2px #bada55) drop-shadow(0 0 2px #bada55) drop-shadow(0 0 2px #bada55); + } + + to { + filter: drop-shadow(0 0 3px #bada55) drop-shadow(0 0 2px #bada55) drop-shadow(0 0 2px #bada55); + } +} diff --git a/apps/www/components/LaunchWeek/8/Ticket/ticket.module.css b/apps/www/components/LaunchWeek/8/Ticket/ticket.module.css new file mode 100644 index 00000000000..51d423c3b22 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/Ticket/ticket.module.css @@ -0,0 +1,150 @@ +.ticket-hero { + background: linear-gradient(137.46deg, rgba(125, 196, 176, 0.04) 0%, rgba(255, 255, 255, 0) 100%), + linear-gradient(137.46deg, rgba(255, 255, 255, 0) 6.19%, rgba(255, 255, 255, 0) 103.29%); + z-index: 10; + box-shadow: 0 1px 1px rgba(131, 6, 184, 0.1), 0 1px 1px rgba(128, 6, 184, 0.1), + 0 20px 30px -30px rgba(76, 195, 138, 0.05), 0 80px 40px -40px rgba(128, 6, 184, 0.04), + inset 0.25px 0.3px 0px 0.3px rgba(209, 243, 227, 0.1), + inset 0.25px 0.3px 0px 0.3px rgba(76, 195, 138, 0.02), + inset 0 -3px 0 -2px rgba(255, 255, 255, 0.08), 0px 4px 20px rgba(118, 7, 170, 0.1); +} + +.hero { + line-height: 1.15; + letter-spacing: -0.05em; + font-weight: 400; + margin: 0; + text-align: center; +} + +@media (min-width: 768px) { + .hero { + line-height: 1; + } +} + +.description { + font-size: 20px; + line-height: 1.4; + color: var(--lw-secondary-color); + text-align: center; + margin: 30px auto; + max-width: 350px; +} + +@media (min-width: 768px) { + .description { + font-size: 24px; + max-width: 415px; + } +} + +.ticket-text { + margin-bottom: 30px; +} + +.ticket-layout { + display: grid; + grid-template-columns: 1fr; + gap: 45px; + margin: auto; + padding: var(--space-4x); + padding-top: 64px; + padding-bottom: 64px; +} + +@media (min-width: 1200px) { + .ticket-layout { + padding-top: 0; + padding-bottom: 0; + } +} + +.ticket-share-layout { + align-items: center; + margin-bottom: auto; +} + +.ticket-visual:before { + left: 50%; + top: -7.5%; + transform: translateZ(0) translateX(-50%) rotate(90deg); +} + +.ticket-visual:after { + left: 50%; + bottom: -7.5%; + transform: translateZ(0) translateX(-50%) rotate(90deg); +} + +@media (min-width: 768px) { + .ticket-visual:before { + top: 50%; + left: -7%; + transform: translateZ(0) translateY(-50%); + } + + .ticket-visual:after { + top: 50%; + left: 96%; + transform: translateZ(0) translateY(-50%); + } +} + +.ticket-actions { + margin-top: 70px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.ticket-copy { + margin-top: 30px; +} + +.ticket-actions-placeholder { + height: 280px; +} + +@media (min-width: 768px) { + .ticket-actions { + flex-direction: row; + } + .ticket-actions-placeholder { + height: 157px; + } +} + +@media (min-width: 1200px) { + .ticket-layout { + grid-template-columns: 1fr 1fr; + } + .hero { + text-align: left; + } + .description { + text-align: left; + margin: 30px 0; + } +} + +.gold-text { + color: #f4cf72; + text-shadow: 0 0 1px rgba(0, 0, 0, 0.5), 0 0 4px rgba(0, 0, 0, 0.1); +} + +@media (min-width: 768px) { + .gold-text { + color: #e4b641; + } +} + +.ticket-number { + text-shadow: 0 0px 2px rgba(22, 22, 22, 0.9), 0 0px 6px rgba(131, 6, 184, 0.9), + 0 0px 12px rgba(217, 195, 226, 0.5); +} +.ticket-number-gold { + text-shadow: 0 0px 2px rgba(22, 22, 22, 0.9), 0 0px 6px rgba(255, 195, 125, 0.9), + 0 0px 12px rgba(226, 224, 195, 0.5); +} diff --git a/apps/www/components/LaunchWeek/8/TicketBrickWall.tsx b/apps/www/components/LaunchWeek/8/TicketBrickWall.tsx new file mode 100644 index 00000000000..11f32de267c --- /dev/null +++ b/apps/www/components/LaunchWeek/8/TicketBrickWall.tsx @@ -0,0 +1,53 @@ +import Link from 'next/link' +import React, { useEffect, useRef, useState } from 'react' +import { Button } from 'ui' +import { useBreakpoint } from 'common/hooks/useBreakpoint' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' +import { TicketBrickWallSlider } from './TicketBrickWallSlider' + +type user = UserData + +interface Props { + users: user[] +} + +export default function TicketBrickWall({ users }: Props) { + const isMobile = useBreakpoint(768) + + return ( +
+
+
+

Tickets from the community

+
+
+ + + {isMobile && } +
+ +
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/TicketBrickWallSlider.tsx b/apps/www/components/LaunchWeek/8/TicketBrickWallSlider.tsx new file mode 100644 index 00000000000..6a062befedd --- /dev/null +++ b/apps/www/components/LaunchWeek/8/TicketBrickWallSlider.tsx @@ -0,0 +1,81 @@ +import Image from 'next/image' +import Link from 'next/link' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' + +import { Swiper, SwiperSlide } from 'swiper/react' + +import 'swiper/swiper.min.css' + +interface Props { + users: UserData[] + reverse?: boolean + speed?: number + animate?: boolean +} + +export function TicketBrickWallSlider({ users, reverse, speed = 50000, animate }: Props) { + const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw8' + const BUCKET_FOLDER_VERSION = 'v1' + const getOgUrl = (username: string, isGold: boolean) => + `${STORAGE_URL}/tickets/${ + isGold ? 'golden' : 'regular' + }/${BUCKET_FOLDER_VERSION}/${username}.png` + + return ( +
+
+ + {users.map((user, i) => ( + + + +
+
+ {user.username} +
+
+
+ +
+ ))} +
+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/TicketsGrid.tsx b/apps/www/components/LaunchWeek/8/TicketsGrid.tsx new file mode 100644 index 00000000000..25e5172e9e6 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/TicketsGrid.tsx @@ -0,0 +1,100 @@ +import { motion } from 'framer-motion' +import Image from 'next/image' +import Link from 'next/link' +import { useBreakpoint } from 'common/hooks/useBreakpoint' +import { UserData } from '../hooks/use-conf-data' + +interface Props { + loadedUsers: UserData[] + isLoading: boolean + pageCount: number + offset: number +} + +export default function TicketsGrid({ loadedUsers, isLoading, pageCount, offset }: Props) { + const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw8' + const BUCKET_FOLDER_VERSION = 'v1' + const getTicketImageUrl = (username: string, isGold: boolean) => + `${STORAGE_URL}/tickets/${ + isGold ? 'golden' : 'regular' + }/${BUCKET_FOLDER_VERSION}/${username}.png` + const isMobile = useBreakpoint(1024) + const isTablet = useBreakpoint(1280) + const horizontalOffset = isMobile ? 8 : 20 + + return ( +
+ {loadedUsers?.map((user, i) => { + const rowTickets = isMobile ? 2 : isTablet ? 3 : 4 + const divider = Math.floor(i / rowTickets) + const isOddRow = divider % 2 === 0 + // Delay should be no more than pageCount for new loaded users + const recalculatedDelay = i >= pageCount * 2 ? (i - pageCount * (offset - 1)) / 15 : i / 15 + const imgUrl = + getTicketImageUrl(user.username!, !!user.golden) ?? + '/images/launchweek/8/lw8-ticket-empty.jpg' + + return ( + window.scrollTo(0, 0)} + > + +
+ {user.username} +
+
+ + ) + })} + {isLoading && + Array.from({ length: pageCount }, (_, i) => ( + +
+
+
+ + ))} +
+ ) +} diff --git a/apps/www/components/LaunchWeek/8/ticket-brick-wall-slider.module.css b/apps/www/components/LaunchWeek/8/ticket-brick-wall-slider.module.css new file mode 100644 index 00000000000..164e683b3a2 --- /dev/null +++ b/apps/www/components/LaunchWeek/8/ticket-brick-wall-slider.module.css @@ -0,0 +1,3 @@ +.swiper-wrapper .swiper-slide { + width: 300px !important; +} diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts b/apps/www/components/LaunchWeek/hooks/use-conf-data.ts similarity index 86% rename from apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts rename to apps/www/components/LaunchWeek/hooks/use-conf-data.ts index fc8d1df8fbb..e8ba6d233c8 100644 --- a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts +++ b/apps/www/components/LaunchWeek/hooks/use-conf-data.ts @@ -5,18 +5,24 @@ export type PageState = 'registration' | 'ticket' export type UserData = { id?: string + email?: string ticketNumber?: number username?: string name?: string golden?: boolean referrals?: number bg_image_id?: number + metadata?: { + role?: string + company?: string + location?: string + } sharedOnTwitter?: string sharedOnLinkedIn?: string } type ConfDataContextType = { - supabase: SupabaseClient + supabase: SupabaseClient | any session: Session | null userData: UserData setUserData: React.Dispatch> diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts b/apps/www/components/LaunchWeek/hooks/use-email-query-param.ts similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts rename to apps/www/components/LaunchWeek/hooks/use-email-query-param.ts diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/useWinningChances.ts b/apps/www/components/LaunchWeek/hooks/useWinningChances.ts similarity index 88% rename from apps/www/components/LaunchWeek/Ticket/hooks/useWinningChances.ts rename to apps/www/components/LaunchWeek/hooks/useWinningChances.ts index b843fd0f5fe..46783c103d3 100644 --- a/apps/www/components/LaunchWeek/Ticket/hooks/useWinningChances.ts +++ b/apps/www/components/LaunchWeek/hooks/useWinningChances.ts @@ -1,5 +1,5 @@ import { UserData } from './use-conf-data' -import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' +import useConfData from '~/components/LaunchWeek/hooks/use-conf-data' function calculateWinChances(userData: UserData): number { // Just signing up gives you 1 diff --git a/apps/www/components/LaunchWeek/Ticket/utils.module.css b/apps/www/components/LaunchWeek/utils.module.css similarity index 100% rename from apps/www/components/LaunchWeek/Ticket/utils.module.css rename to apps/www/components/LaunchWeek/utils.module.css diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index ec9a2c5d075..1869751d9f2 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import Link from 'next/link' import { useRouter } from 'next/router' -import { Button, Badge, IconStar, IconChevronDown } from 'ui' +import { Button, Badge, IconStar, IconChevronDown, Announcement, LW8CountdownBanner } from 'ui' import FlyOut from '~/components/UI/FlyOut' import Transition from 'lib/Transition' @@ -23,14 +23,16 @@ import * as supabaseLogoWordmarkWhite from 'common/assets/images/supabase-logo-w const Nav = () => { const { isDarkMode } = useTheme() - const { pathname } = useRouter() + const router = useRouter() const [open, setOpen] = useState(false) const [openProduct, setOpenProduct] = useState(false) const [openDevelopers, setOpenDevelopers] = useState(false) const isLoggedIn = useIsLoggedIn() - const isLaunchWeekPage = pathname.includes('launch-week') - const showLaunchWeekNavMode = isLaunchWeekPage && !open && !openProduct && !openDevelopers + const isHomePage = router.pathname === '/' + const isLaunchWeekPage = router.pathname.includes('launch-week') + const showLaunchWeekNavMode = + (isLaunchWeekPage || isHomePage) && !open && !openProduct && !openDevelopers React.useEffect(() => { if (open) { @@ -197,8 +199,9 @@ const Nav = () => { return ( <> - {/* - */} + + +
{ className={[ `border-scale-300 border-b backdrop-blur-sm transition-opacity`, showLaunchWeekNavMode && '!opacity-100 !border-[#e0d2f430]', + isLaunchWeekPage && showLaunchWeekNavMode && '!border-b-0', ].join(' ')} > {/*
*/} @@ -225,19 +229,21 @@ const Nav = () => { Supabase Logo + + {isLaunchWeekPage && ( + + + Launch Week + + + )}
import('~/components/TwitterSocialSec type Props = { customerStories: PostTypes[] } const Index = ({ customerStories }: Props) => { + const { isDarkMode, toggleTheme } = useTheme() + + useEffect(() => { + toggleTheme(true) + document.body.className = 'dark' + return () => { + document.body.className = '' + isDarkMode ? toggleTheme(true) : toggleTheme(false) + } + }, []) + return ( diff --git a/apps/www/pages/launch-week/6/index.tsx b/apps/www/pages/launch-week/6/index.tsx index 2c14b90cd98..7f8f8426cc1 100644 --- a/apps/www/pages/launch-week/6/index.tsx +++ b/apps/www/pages/launch-week/6/index.tsx @@ -3,7 +3,7 @@ import { NextSeo } from 'next-seo' import Image from 'next/image' -import _days from '~/components/LaunchWeek/lw6_days.json' +import _days from '~/components/LaunchWeek/6/lw6_days.json' import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' @@ -34,7 +34,7 @@ const constellation = [ ] export default function launchweek() { - const { isDarkMode } = useTheme() + const { isDarkMode, toggleTheme } = useTheme() const title = 'Launch Week 6' const description = 'Supabase Launch Week 6 | 12-18 Dec 2022' const liveDay = null @@ -64,8 +64,13 @@ export default function launchweek() { }, [supabase]) useEffect(() => { - document.body.className = isDarkMode ? 'dark bg-[#121212]' : 'light bg-[#fff]' - }, [isDarkMode]) + toggleTheme(true) + document.body.className = 'bg-[#121212]' + return () => { + document.body.className = '' + isDarkMode ? toggleTheme(true) : toggleTheme(false) + } + }, []) async function getCreators() { try { @@ -94,20 +99,20 @@ export default function launchweek() {
{shipped ? 'Shipped' : 'Coming Soon'} - + {day} ・ {date}
- {title} + {title}
) } @@ -116,18 +121,18 @@ export default function launchweek() { return (
-
+
Blog post -
+
{docs && ( -
+
Docs -
+
@@ -135,9 +140,9 @@ export default function launchweek() { )} {video && (
-
+
Video -
+
@@ -173,13 +178,13 @@ export default function launchweek() { )} >
- + /> */}

Dec 12 – 16 at 6 AM PT | 9 AM ET

@@ -214,7 +219,7 @@ export default function launchweek() {
-
+
- Who we hire at Supabase + Who we hire at Supabase Fireside chat with founders
-
+
Watch video -
+
-
+
Read blogpost -
+
@@ -250,25 +255,26 @@ export default function launchweek() {
-
+
- Wrap Up + Wrap Up Everything we shipped
-
+
Read blogpost -
+
@@ -281,7 +287,7 @@ export default function launchweek() { type="default" openBehaviour="multiple" size="large" - className="text-scale-900 dark:text-white" + className="text-white" justified={false} bordered={false} chevronAlign="right" @@ -293,7 +299,7 @@ export default function launchweek() { day5.d?.toString(), ]} > -
+
-
+
{day1.description} - + Redesigned
@@ -336,7 +342,7 @@ export default function launchweek() { )}
-
+
0 && (
-
+
{day2.steps[0].title}
- + New
- + New - {day2.steps[1].title} + {day2.steps[1].title}

{day2.steps[1].description}

@@ -395,7 +401,7 @@ export default function launchweek() { )}
-
+
0 && (
-
+
{day3.steps[0].title} - + Updated
@@ -452,7 +458,7 @@ export default function launchweek() { )}
-
+
0 && (
-
+
{day4.steps[0].title} - + New
@@ -518,7 +524,7 @@ export default function launchweek() { )}
-
+
- {day5.steps[0].title} - + {day5.steps[0].title} + New
- + New - - {day5.steps[1].title} - + {day5.steps[1].title}
-

Community

+

Community

- Community Day + Community Day
@@ -666,9 +670,9 @@ export default function launchweek() { target="_blank" rel="noopener" > -
+
Blog post -
+
-
+
Flutterflow -
+
-
+
OneSignal -
+
-
+
NextAuth -
+
-

One more thing

+

One more thing

- + Updated - - pg_graphql v1.0 - + pg_graphql v1.0
- + New - - Custom Domains - + Custom Domains
- + New - - Point-in-time recovery - + Point-in-time recovery
- + Experimental - pg_crdt + pg_crdt
- + Upgrade - - Postgres 15 - + Postgres 15
- + Upgrade - - PostgREST 11 - + PostgREST 11
Submissions Closed -

Launch Week Hackathon

+

Launch Week Hackathon

The traditional parallel Hackathon is back! Build a new open source project with Supabase and you can win $1500 in GitHub sponsorships and a coveted Supabase Darkmode @@ -1034,14 +1028,14 @@ export default function launchweek() {

-

Prizes

+

Prizes

There are 5 categories to win, with prizes for winners and runner-ups of each category. Each team member gets a prize.

-

Judges

+

Judges

The Supabase team will judge all the categories except the Best Edge Functions Project, which will be judged by our friends at Deno. @@ -1049,7 +1043,7 @@ export default function launchweek() {

-

Community

+

Community

If you need help or advice when building, find other people to join your team, or if you just want to chill and watch people build, come and join us! @@ -1066,7 +1060,7 @@ export default function launchweek() {

-

Submission

+

Submission

Submit your project through{' '}

@@ -1093,12 +1088,12 @@ export default function launchweek() { rel="noopener noreferrer" className="self-end" > -
+
-

Supabase Cache Helpers

+

Supabase Cache Helpers

Previous Best Overall Project Winner

@@ -1107,12 +1102,12 @@ export default function launchweek() { target="_blank" rel="noopener noreferrer" > -
+
-

Super UI

+

Super UI

Previous Best Overall Project Runner Up

@@ -1122,22 +1117,22 @@ export default function launchweek() { rel="noopener noreferrer" className="self-end" > -
+
-

Quid Faciam Hodie?

+

Quid Faciam Hodie?

Winner Best Flutter Project

-
+
-

That Hot Dog Game

+

That Hot Dog Game

Previous Winner: Most Fun/Interesting

@@ -1147,12 +1142,12 @@ export default function launchweek() { rel="noopener noreferrer" className="self-end" > -
Shipped -

The Supabase Content Storm

+

The Supabase Content Storm

We worked with more than 30 content creators from around the world to drop a mountain of content simultaneously! @@ -1217,7 +1212,7 @@ export default function launchweek() {

{activeCreator !== null && (
-

+

{activeCreator !== null ? `${creators[activeCreator].first_name} ${creators[activeCreator].last_name}` : 'Title'} diff --git a/apps/www/pages/launch-week/7/index.tsx b/apps/www/pages/launch-week/7/index.tsx new file mode 100644 index 00000000000..55c3b4348aa --- /dev/null +++ b/apps/www/pages/launch-week/7/index.tsx @@ -0,0 +1,104 @@ +import { GetServerSideProps } from 'next' +import { NextSeo } from 'next-seo' +import { useEffect } from 'react' +import dynamic from 'next/dynamic' +import { createClient } from '@supabase/supabase-js' + +import { SITE_ORIGIN, SITE_URL } from '~/lib/constants' + +import DefaultLayout from '~/components/Layouts/Default' +import SectionContainer from '~/components/Layouts/SectionContainer' +import { LaunchWeekLogoHeader } from '~/components/LaunchWeek/7/LaunchSection/LaunchWeekLogoHeader' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' +import LW7BgGraphic from '~/components/LaunchWeek/7/LW7BgGraphic' +import { useTheme } from 'common/Providers' + +const LW7Releases = dynamic(() => import('~/components/LaunchWeek/7/Releases')) +const LaunchWeekPrizeSection = dynamic( + () => import('~/components/LaunchWeek/7/LaunchWeekPrizeSection') +) +const TicketBrickWall = dynamic( + () => import('~/components/LaunchWeek/7/LaunchSection/TicketBrickWall') +) +const CTABanner = dynamic(() => import('~/components/CTABanner')) + +interface Props { + users: UserData[] +} + +const supabaseAdmin = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'http://localhost:54321', + process.env.SUPABASE_SERVICE_ROLE_SECRET ?? + process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_SECRET ?? + '' +) + +export default function TicketHome({ users }: Props) { + const { isDarkMode, toggleTheme } = useTheme() + + const TITLE = 'Supabase LaunchWeek 7' + const DESCRIPTION = 'Supabase Launch Week 7 | 10–14 April 2023' + const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/seven/launch-week-7-teaser.jpg` + + useEffect(() => { + toggleTheme(true) + document.body.className = 'bg-[#1C1C1C]' + return () => { + document.body.className = '' + isDarkMode ? toggleTheme(true) : toggleTheme(false) + } + }, []) + + return ( + <> + + +
+
+
+ + + + +
+
+ +
+ + +
+ {users && } +
+ +
+ + ) +} + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + // fetch users for the TicketBrickWall + const { data: users } = await supabaseAdmin! + .from('lw7_tickets_golden') + .select('*') + .order('createdAt', { ascending: false }) + .limit(17) + + return { + props: { + users, + }, + } +} diff --git a/apps/www/pages/launch-week/7/tickets/[username].tsx b/apps/www/pages/launch-week/7/tickets/[username].tsx new file mode 100644 index 00000000000..7f74869f8b9 --- /dev/null +++ b/apps/www/pages/launch-week/7/tickets/[username].tsx @@ -0,0 +1,166 @@ +import { NextSeo } from 'next-seo' +import { GetStaticProps, GetStaticPaths } from 'next' +import Error from 'next/error' +import DefaultLayout from '~/components/Layouts/Default' +import SectionContainer from '~/components/Layouts/SectionContainer' +import TicketContainer from '~/components/LaunchWeek/7/Ticket/TicketContainer' +import { SITE_URL, SAMPLE_TICKET_NUMBER } from '~/lib/constants' +import { createClient } from '@supabase/supabase-js' +import { useEffect, useState } from 'react' +import { IconArrowDown } from 'ui' +import LaunchWeekPrizeSection from '~/components/LaunchWeek/7/LaunchWeekPrizeSection' +import { LaunchWeekLogoHeader } from '~/components/LaunchWeek/7/LaunchSection/LaunchWeekLogoHeader' +import TicketBrickWall from '~/components/LaunchWeek/7/LaunchSection/TicketBrickWall' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' +import LW7BgGraphic from '~/components/LaunchWeek/7/LW7BgGraphic' +import CTABanner from '~/components/CTABanner' +import { useTheme } from 'common/Providers' + +interface Props { + user: UserData + users: UserData[] + ogImageUrl: string +} + +const supabaseAdmin = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'http://localhost:54321', + process.env.SUPABASE_SERVICE_ROLE_SECRET ?? + process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_SECRET ?? + '' +) + +export default function UsernamePage({ user, users, ogImageUrl }: Props) { + const { isDarkMode } = useTheme() + const { username, ticketNumber, name, golden, referrals, bg_image_id } = user + const TITLE = `${name ? name + '’s' : 'Get your'} #SupaLaunchWeek Ticket` + const DESCRIPTION = 'Supabase Launch Week 7 | 10–14 April 2023 | Generate your ticket. Win swag.' + const OG_URL = `${SITE_URL}/tickets/${username}` + + const [supabase] = useState(() => + createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) + ) + + if (!ticketNumber) { + return + } + + useEffect(() => { + document.body.className = '!dark bg-[#1C1C1C]' + + return () => { + document.body.className = isDarkMode ? 'dark' : 'light' + } + }, []) + + return ( + <> + + +
+ + + + + ) +} + +export const getStaticProps: GetStaticProps = async ({ params }) => { + const username = params?.username?.toString() || null + let name: string | null | undefined + let ticketNumber: number | null | undefined + let golden = false + let referrals = 0 + let bg_image_id + let ogImageUrl + + // fetch users for the TicketBrickWall + const { data: users } = await supabaseAdmin!.from('lw7_tickets_golden').select().limit(17) + + // fetch a specific user + if (username) { + const { data: user } = await supabaseAdmin! + .from('lw7_tickets_golden') + .select('name, ticketNumber, golden, referrals, bg_image_id') + .eq('username', username) + .single() + name = user?.name + ticketNumber = user?.ticketNumber + golden = user?.golden ?? false + bg_image_id = user?.bg_image_id ?? 1 + referrals = user?.referrals ?? 0 + ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent( + username ?? '' + )}${golden ? '&golden=true' : ''}` + } + + return { + props: { + user: { + username: ticketNumber ? username : null, + usernameFromParams: username || null, + name: ticketNumber ? name || username || null : null, + ticketNumber: ticketNumber || SAMPLE_TICKET_NUMBER, + golden, + referrals, + bg_image_id, + }, + ogImageUrl, + users, + key: username, + }, + revalidate: 5, + } +} + +export const getStaticPaths: GetStaticPaths = async () => { + return Promise.resolve({ + paths: [], + fallback: 'blocking', + }) +} diff --git a/apps/www/pages/launch-week/7/tickets/index.tsx b/apps/www/pages/launch-week/7/tickets/index.tsx new file mode 100644 index 00000000000..61b1b0cd3a6 --- /dev/null +++ b/apps/www/pages/launch-week/7/tickets/index.tsx @@ -0,0 +1,181 @@ +import { NextSeo } from 'next-seo' +import { GetServerSideProps } from 'next' +import DefaultLayout from '~/components/Layouts/Default' +import SectionContainer from '~/components/Layouts/SectionContainer' +import { SITE_ORIGIN, SITE_URL } from '~/lib/constants' +import { createClient } from '@supabase/supabase-js' +import { useEffect, useRef, useState } from 'react' +import { LaunchWeekLogoHeader } from '~/components/LaunchWeek/7/LaunchSection/LaunchWeekLogoHeader' +import { motion } from 'framer-motion' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' +import LW7BgGraphic from '~/components/LaunchWeek/7/LW7BgGraphic' +import CTABanner from '~/components/CTABanner' +import { debounce } from 'lodash' +import TicketsGrid from '~/components/LaunchWeek/7/TicketsGrid' +import { Button } from 'ui' +import Link from 'next/link' +import { useTheme } from 'common/Providers' + +interface Props { + users: UserData[] +} + +const supabaseAdmin = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'http://localhost:54321', + process.env.SUPABASE_SERVICE_ROLE_SECRET ?? + process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_SECRET ?? + '' +) + +const generateOgs = async (users: UserData[]) => { + users?.map(async (user) => { + const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent( + user.username ?? '' + )}${!!user.golden ? '&golden=true' : ''}` + return await fetch(ogImageUrl) + }) +} + +export default function TicketsPage({ users }: Props) { + const ref = useRef(null) + const PAGE_COUNT = 20 + const TITLE = '#SupaLaunchWeek Tickets' + const DESCRIPTION = 'Supabase Launch Week 7 | 10–14 April 2023' + const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/seven/launch-week-7-teaser.jpg` + + const { isDarkMode } = useTheme() + const [isLoading, setIsLoading] = useState(false) + const [offset, setOffset] = useState(1) + const [isLast, setIsLast] = useState(false) + const [hasLoaded, setHasLoaded] = useState(false) + const [loadedUsers, setLoadedUsers] = useState(users) + + useEffect(() => { + loadMoreUsers(offset) + }, [hasLoaded]) + + const loadUsers = async (offset: number) => { + const from = offset * PAGE_COUNT + return await supabaseAdmin! + .from('lw7_tickets_golden') + .select('*') + .range(from, from + PAGE_COUNT - 1) + .order('createdAt', { ascending: false }) + } + + const loadMoreUsers = async (offset: number) => { + if (isLast) return + setIsLoading(true) + setOffset((prev) => prev + 1) + const { data: users } = await loadUsers(offset) + setLoadedUsers((prevUsers) => [...prevUsers, ...(users as any[])]) + if ((users as any[]).length < PAGE_COUNT) setIsLast(true) + setIsLoading(false) + } + + const handleScroll = () => { + if (ref.current && typeof window !== 'undefined') { + const rect = (ref.current as HTMLDivElement)?.getBoundingClientRect() + const isInView = rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + setHasLoaded((prev) => !prev && isInView) + } + } + + useEffect(() => { + document.body.className = '!dark bg-[#1C1C1C]' + + const handleDebouncedScroll = debounce(() => !isLast && handleScroll(), 200) + window.addEventListener('scroll', handleDebouncedScroll) + + return () => { + document.body.className = isDarkMode ? 'dark' : 'light' + window.removeEventListener('scroll', handleDebouncedScroll) + } + }, []) + + return ( + <> + + +
+
+
+ + + + +
+
+
+ +
+ +

+ Check out all the tickets +

+

+ Join us on April 16th for Launch Week 7's final day{' '} +
and find out if you are one of the lucky + winners. Get sharing! +

+ +
+
+
+ +
+
+
+ + + + ) +} + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + const { data: users } = await supabaseAdmin! + .from('lw7_tickets_golden') + .select('*') + .order('createdAt', { ascending: false }) + .limit(20) + + // Generate og images of not present + generateOgs(users as any[]) + + return { + props: { + users, + }, + } +} diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index 8e12bacdf8e..da8706c61d2 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -1,35 +1,39 @@ import { GetServerSideProps } from 'next' import { NextSeo } from 'next-seo' import { useState, useEffect } from 'react' +import Image from 'next/image' import dynamic from 'next/dynamic' import { useRouter } from 'next/router' import { createClient, Session, SupabaseClient } from '@supabase/supabase-js' - import { SITE_ORIGIN, SITE_URL } from '~/lib/constants' +import { PageState, ConfDataContext, UserData } from '~/components/LaunchWeek/hooks/use-conf-data' import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' -import { LaunchWeekLogoHeader } from '~/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader' -import { UserData } from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' -import LW7BgGraphic from '~/components/LaunchWeek/LW7BgGraphic' +import { LaunchWeekLogoHeader } from '~/components/LaunchWeek/8/LaunchWeekLogoHeader' + import { useTheme } from 'common/Providers' -const TicketContainer = dynamic(() => import('~/components/LaunchWeek/Ticket/TicketContainer')) -const LW7Releases = dynamic(() => import('~/components/LaunchWeek/Releases/LW7/LW7Releases')) +import 'swiper/swiper.min.css' +import Head from 'next/head' + +const AnimatedParticles = dynamic( + () => import('~/components/LaunchWeek/8/AnimatedParticles/ParticlesCanvas') +) +const TicketContainer = dynamic(() => import('~/components/LaunchWeek/8/Ticket/TicketContainer')) const LaunchWeekPrizeSection = dynamic( - () => import('~/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection') -) -const TicketBrickWall = dynamic( - () => import('~/components/LaunchWeek/LaunchSection/TicketBrickWall') + () => import('~/components/LaunchWeek/8/LaunchWeekPrizeSection') ) +const TicketBrickWall = dynamic(() => import('~/components/LaunchWeek/8/TicketBrickWall')) const CTABanner = dynamic(() => import('~/components/CTABanner')) interface Props { - users: UserData[] + users?: UserData[] } const supabaseAdmin = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'http://localhost:54321', + // ANON KEY process.env.SUPABASE_SERVICE_ROLE_SECRET ?? process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_SECRET ?? 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9idWxkYW5ycHRsb2t0eGNmZnZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2Njk3MjcwMTIsImV4cCI6MTk4NTMwMzAxMn0.SZLqryz_-stF8dgzeVXmzZWPOqdOrBwqJROlFES8v3I' @@ -37,15 +41,16 @@ const supabaseAdmin = createClient( export default function TicketHome({ users }: Props) { const { query } = useRouter() - const [supabase, setSupabase] = useState(null) - const [session, setSession] = useState(null) - const { isDarkMode } = useTheme() - const TITLE = 'Supabase LaunchWeek 7' - const DESCRIPTION = 'Supabase Launch Week 7 | 10–14 April 2023' - const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/seven/launch-week-7-teaser.jpg` + const TITLE = 'Supabase LaunchWeek 8' + const DESCRIPTION = 'Supabase Launch Week 8 | 7–11 August 2023' + const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/8/lw8-og.jpg` + const ticketNumber = query.ticketNumber?.toString() const bgImageId = query.bgImageId?.toString() + const [supabase, setSupabase] = useState(null) + const [session, setSession] = useState(null) + const { isDarkMode, toggleTheme } = useTheme() const defaultUserData = { id: query.id?.toString(), @@ -56,6 +61,9 @@ export default function TicketHome({ users }: Props) { bgImageId: bgImageId ? parseInt(bgImageId, 10) : undefined, } + const [userData, setUserData] = useState(defaultUserData) + const [pageState, setPageState] = useState('ticket') + useEffect(() => { if (!supabase) { setSupabase( @@ -81,24 +89,14 @@ export default function TicketHome({ users }: Props) { }, [supabase]) useEffect(() => { - document.body.className = '!dark bg-[#1C1C1C]' + toggleTheme(true) + document.body.className = 'dark bg-[#020405]' return () => { - document.body.className = isDarkMode ? 'dark' : 'light' + document.body.className = '' + isDarkMode ? toggleTheme(true) : toggleTheme(false) } }, []) - const Ticket = () => - supabase && ( -
- -
- ) - return ( <> - -
-
-
- - - - + + + + + +
+
+
+ +
+ +
+
+ {supabase && } + starts background +
+
+
+ +
+
-
-
- - +
+ {supabase && ( +
+ +
+ )} +
+ + + + {users && }
- {users && } -
- - + + + ) } @@ -141,10 +187,8 @@ export default function TicketHome({ users }: Props) { export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { // fetch users for the TicketBrickWall const { data: users } = await supabaseAdmin! - .from('lw7_tickets_golden') - .select('*') - .order('createdAt', { ascending: false }) - .limit(17) + .from('lw8_tickets_golden') + .select('username, golden', { count: 'exact' }) return { props: { diff --git a/apps/www/pages/launch-week/launchWeek.css b/apps/www/pages/launch-week/launchWeek.css index bfd8b1b9187..d07b25683f7 100644 --- a/apps/www/pages/launch-week/launchWeek.css +++ b/apps/www/pages/launch-week/launchWeek.css @@ -1,5 +1,4 @@ .bg-lw7 { - /* background: radial-gradient(72.03% 66.03% at 50% 69.72%, #3fcf8e 0%, #9e44ef 100%); */ background: #9e44ef; } @@ -8,23 +7,13 @@ } .bg-lw7-gradient { - /* background: radial-gradient(72.03% 66.03% at 50% 69.72%, #3fcf8e 0%, transparent 100%); */ background: radial-gradient(80% 65% at 50% 95%, #dbb8bf 0%, transparent 100%); } -/* .bg-lw7-gradient.gold { - background: radial-gradient(80% 65% at 50% 95%, #ffd132 0%, transparent 100%); -} */ - @media (min-width: 768px) { .bg-lw7-gradient { - /* background: radial-gradient(72.03% 66.03% at 50% 69.72%, #3fcf8e 0%, transparent 100%); */ background: radial-gradient(72.03% 66.03% at 50% 69.72%, #dbb8bf 0%, transparent 100%); } - - /* .bg-lw7-gradient.gold { - background: radial-gradient(72.03% 70% at 50% 75%, #ffd132 0%, transparent 100%); - } */ } .opacity-pulse { diff --git a/apps/www/pages/launch-week/tickets/[username].tsx b/apps/www/pages/launch-week/tickets/[username].tsx index 07061ff48fc..c246ce203c9 100644 --- a/apps/www/pages/launch-week/tickets/[username].tsx +++ b/apps/www/pages/launch-week/tickets/[username].tsx @@ -1,20 +1,23 @@ +import { useEffect, useState } from 'react' import { NextSeo } from 'next-seo' import { GetStaticProps, GetStaticPaths } from 'next' +import Image from 'next/image' import Error from 'next/error' +import dynamic from 'next/dynamic' +import { Session, SupabaseClient, createClient } from '@supabase/supabase-js' +import { useTheme } from 'common/Providers' + import DefaultLayout from '~/components/Layouts/Default' import SectionContainer from '~/components/Layouts/SectionContainer' -import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer' -import { SITE_URL, SAMPLE_TICKET_NUMBER } from '~/lib/constants' -import { createClient } from '@supabase/supabase-js' -import { useEffect, useState } from 'react' -import { IconArrowDown } from 'ui' -import LaunchWeekPrizeSection from '~/components/LaunchWeek/LaunchSection/LaunchWeekPrizeSection' -import { LaunchWeekLogoHeader } from '~/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader' -import TicketBrickWall from '~/components/LaunchWeek/LaunchSection/TicketBrickWall' -import { UserData } from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' -import LW7BgGraphic from '../../../components/LaunchWeek/LW7BgGraphic' -import CTABanner from '../../../components/CTABanner' -import { useTheme } from 'common/Providers' +import TicketContainer from '~/components/LaunchWeek/8/Ticket/TicketContainer' +import { SITE_URL } from '~/lib/constants' +import { PageState, ConfDataContext, UserData } from '~/components/LaunchWeek/hooks/use-conf-data' + +const LaunchWeekPrizeSection = dynamic( + () => import('~/components/LaunchWeek/8/LaunchWeekPrizeSection') +) +const TicketBrickWall = dynamic(() => import('~/components/LaunchWeek/8/TicketBrickWall')) +const CTABanner = dynamic(() => import('~/components/CTABanner')) interface Props { user: UserData @@ -24,31 +27,47 @@ interface Props { const supabaseAdmin = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'http://localhost:54321', + // ANON KEY process.env.SUPABASE_SERVICE_ROLE_SECRET ?? process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_SECRET ?? 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9idWxkYW5ycHRsb2t0eGNmZnZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2Njk3MjcwMTIsImV4cCI6MTk4NTMwMzAxMn0.SZLqryz_-stF8dgzeVXmzZWPOqdOrBwqJROlFES8v3I' ) export default function UsernamePage({ user, users, ogImageUrl }: Props) { - const { isDarkMode } = useTheme() - const { username, ticketNumber, name, golden, referrals, bg_image_id } = user + const { username, ticketNumber, name } = user + const TITLE = `${name ? name + '’s' : 'Get your'} #SupaLaunchWeek Ticket` - const DESCRIPTION = 'Supabase Launch Week 7 | 10–14 April 2023 | Generate your ticket. Win swag.' + const DESCRIPTION = + 'Supabase Launch Week 8 | 7–11 August 2023 | Generate your ticket & win awesome swag.' const OG_URL = `${SITE_URL}/tickets/${username}` - const [supabase] = useState(() => - createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) - ) + const [supabase, setSupabase] = useState(null) + const [session, setSession] = useState(null) + const { isDarkMode, toggleTheme } = useTheme() + + const [pageState, setPageState] = useState('ticket') if (!ticketNumber) { return } useEffect(() => { - document.body.className = '!dark bg-[#1C1C1C]' + if (!supabase) { + setSupabase( + createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! + ) + ) + } + }, []) + useEffect(() => { + toggleTheme(true) + document.body.className = 'dark bg-[#020405]' return () => { - document.body.className = isDarkMode ? 'dark' : 'light' + document.body.className = '' + isDarkMode ? toggleTheme(true) : toggleTheme(false) } }, []) @@ -69,86 +88,90 @@ export default function UsernamePage({ user, users, ogImageUrl }: Props) { ], }} /> - -
-
-
- - - -
- - See the prizes{' '} - - - - + null, + setPageState, + }} + > + +
+
+
+ +
+ {supabase && ( + + )} +
+
+
+
- - +
-
+ + + + {users && }
- - -
- -
+ + +
) } export const getStaticProps: GetStaticProps = async ({ params }) => { const username = params?.username?.toString() || null - let name: string | null | undefined - let ticketNumber: number | null | undefined - let golden = false - let referrals = 0 - let bg_image_id - let ogImageUrl + let user // fetch users for the TicketBrickWall - const { data: users } = await supabaseAdmin!.from('lw7_tickets_golden').select().limit(17) + const { data: users } = await supabaseAdmin!.from('lw8_tickets_golden').select().limit(17) + + fetch( + `https://obuldanrptloktxcffvn.functions.supabase.co/lw8-ticket?username=${encodeURIComponent( + username ?? '' + )}` + ).catch((_) => {}) // fetch a specific user if (username) { - const { data: user } = await supabaseAdmin! - .from('lw7_tickets_golden') - .select('name, ticketNumber, golden, referrals, bg_image_id') + const { data } = await supabaseAdmin! + .from('lw8_tickets_golden') + .select('name, username, ticketNumber, metadata, golden') .eq('username', username) .single() - name = user?.name - ticketNumber = user?.ticketNumber - golden = user?.golden ?? false - bg_image_id = user?.bg_image_id ?? 1 - referrals = user?.referrals ?? 0 - ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent( - username ?? '' - )}${golden ? '&golden=true' : ''}` + + user = data } + const BUCKET_FOLDER_VERSION = 'v1' + + const ogImageUrl = `https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw8/og/${ + user?.golden ? 'golden' : 'regular' + }/${BUCKET_FOLDER_VERSION}/${username}.png` + return { props: { user: { - username: ticketNumber ? username : null, - usernameFromParams: username || null, - name: ticketNumber ? name || username || null : null, - ticketNumber: ticketNumber || SAMPLE_TICKET_NUMBER, - golden, - referrals, - bg_image_id, + ...user, + username, }, ogImageUrl, users, diff --git a/apps/www/pages/launch-week/tickets/index.tsx b/apps/www/pages/launch-week/tickets/index.tsx index 84e7d8234d0..b0423f3180b 100644 --- a/apps/www/pages/launch-week/tickets/index.tsx +++ b/apps/www/pages/launch-week/tickets/index.tsx @@ -1,21 +1,22 @@ -import { NextSeo } from 'next-seo' -import { GetServerSideProps } from 'next' -import DefaultLayout from '~/components/Layouts/Default' -import SectionContainer from '~/components/Layouts/SectionContainer' -import { SITE_ORIGIN, SITE_URL } from '~/lib/constants' -import { createClient } from '@supabase/supabase-js' import { useEffect, useRef, useState } from 'react' -import { LaunchWeekLogoHeader } from '~/components/LaunchWeek/LaunchSection/LaunchWeekLogoHeader' -import { motion } from 'framer-motion' -import { UserData } from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' -import LW7BgGraphic from '../../../components/LaunchWeek/LW7BgGraphic' -import CTABanner from '../../../components/CTABanner' -import { debounce } from 'lodash' -import TicketsGrid from '../../../components/LaunchWeek/Ticket/TicketsGrid' +import { GetServerSideProps } from 'next' +import { NextSeo } from 'next-seo' +import Image from 'next/image' import { Button } from 'ui' import Link from 'next/link' +import { motion } from 'framer-motion' +import { createClient } from '@supabase/supabase-js' +import { debounce } from 'lodash' + +import { SITE_ORIGIN, SITE_URL } from '~/lib/constants' import { useTheme } from 'common/Providers' +import DefaultLayout from '~/components/Layouts/Default' +import SectionContainer from '~/components/Layouts/SectionContainer' +import { UserData } from '~/components/LaunchWeek/hooks/use-conf-data' +import CTABanner from '~/components/CTABanner' +import TicketsGrid from '~/components/LaunchWeek/8/TicketsGrid' + interface Props { users: UserData[] } @@ -29,7 +30,7 @@ const supabaseAdmin = createClient( const generateOgs = async (users: UserData[]) => { users?.map(async (user) => { - const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw7-ticket-og?username=${encodeURIComponent( + const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/lw8-ticket-og?username=${encodeURIComponent( user.username ?? '' )}${!!user.golden ? '&golden=true' : ''}` return await fetch(ogImageUrl) @@ -40,10 +41,10 @@ export default function TicketsPage({ users }: Props) { const ref = useRef(null) const PAGE_COUNT = 20 const TITLE = '#SupaLaunchWeek Tickets' - const DESCRIPTION = 'Supabase Launch Week 7 | 10–14 April 2023' - const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/seven/launch-week-7-teaser.jpg` + const DESCRIPTION = 'Supabase Launch Week 8 | 7–11 August 2023' + const OG_IMAGE = `${SITE_ORIGIN}/images/launchweek/8/lw8-og.jpg` - const { isDarkMode } = useTheme() + const { isDarkMode, toggleTheme } = useTheme() const [isLoading, setIsLoading] = useState(false) const [offset, setOffset] = useState(1) const [isLast, setIsLast] = useState(false) @@ -57,7 +58,7 @@ export default function TicketsPage({ users }: Props) { const loadUsers = async (offset: number) => { const from = offset * PAGE_COUNT return await supabaseAdmin! - .from('lw7_tickets_golden') + .from('lw8_tickets_golden') .select('*') .range(from, from + PAGE_COUNT - 1) .order('createdAt', { ascending: false }) @@ -82,17 +83,24 @@ export default function TicketsPage({ users }: Props) { } useEffect(() => { - document.body.className = '!dark bg-[#1C1C1C]' - const handleDebouncedScroll = debounce(() => !isLast && handleScroll(), 200) + window.addEventListener('scroll', handleDebouncedScroll) return () => { - document.body.className = isDarkMode ? 'dark' : 'light' window.removeEventListener('scroll', handleDebouncedScroll) } }, []) + useEffect(() => { + toggleTheme(true) + document.body.className = 'dark bg-[#020405]' + return () => { + document.body.className = '' + isDarkMode ? toggleTheme(true) : toggleTheme(false) + } + }, []) + return ( <> -
-
-
- - - - -
-
-
- +
+
-

- Check out all the tickets -

-

- Join us on April 16th for Launch Week 7's final day{' '} +

Launch Week 8 tickets

+

+ Join us on August 11th for Launch Week 8's final day{' '}
and find out if you are one of the lucky - winners. Get sharing! + winners.

@@ -156,8 +153,18 @@ export default function TicketsPage({ users }: Props) { />
+
+ +
- + ) @@ -165,7 +172,7 @@ export default function TicketsPage({ users }: Props) { export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { const { data: users } = await supabaseAdmin! - .from('lw7_tickets_golden') + .from('lw8_tickets_golden') .select('*') .order('createdAt', { ascending: false }) .limit(20) diff --git a/apps/www/public/images/launchweek/8/LW8-gradient.png b/apps/www/public/images/launchweek/8/LW8-gradient.png new file mode 100644 index 00000000000..5ac9a61d922 Binary files /dev/null and b/apps/www/public/images/launchweek/8/LW8-gradient.png differ diff --git a/apps/www/public/images/launchweek/8/lw8-logo-gold.png b/apps/www/public/images/launchweek/8/lw8-logo-gold.png new file mode 100644 index 00000000000..51f85e84265 Binary files /dev/null and b/apps/www/public/images/launchweek/8/lw8-logo-gold.png differ diff --git a/apps/www/public/images/launchweek/8/lw8-logo.png b/apps/www/public/images/launchweek/8/lw8-logo.png new file mode 100644 index 00000000000..8a6722e759f Binary files /dev/null and b/apps/www/public/images/launchweek/8/lw8-logo.png differ diff --git a/apps/www/public/images/launchweek/8/lw8-og.jpg b/apps/www/public/images/launchweek/8/lw8-og.jpg new file mode 100644 index 00000000000..49cbefc13be Binary files /dev/null and b/apps/www/public/images/launchweek/8/lw8-og.jpg differ diff --git a/apps/www/public/images/launchweek/8/lw8-ticket-empty.jpg b/apps/www/public/images/launchweek/8/lw8-ticket-empty.jpg new file mode 100644 index 00000000000..fb846cf7412 Binary files /dev/null and b/apps/www/public/images/launchweek/8/lw8-ticket-empty.jpg differ diff --git a/apps/www/public/images/launchweek/8/prize-icon.svg b/apps/www/public/images/launchweek/8/prize-icon.svg new file mode 100644 index 00000000000..238b8c2c317 --- /dev/null +++ b/apps/www/public/images/launchweek/8/prize-icon.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/www/public/images/launchweek/8/stars.svg b/apps/www/public/images/launchweek/8/stars.svg new file mode 100644 index 00000000000..0f077672b56 --- /dev/null +++ b/apps/www/public/images/launchweek/8/stars.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/www/public/images/launchweek/8/swag-bg.png b/apps/www/public/images/launchweek/8/swag-bg.png new file mode 100644 index 00000000000..667a6b4479c Binary files /dev/null and b/apps/www/public/images/launchweek/8/swag-bg.png differ diff --git a/apps/www/public/images/launchweek/8/swag-illustration.png b/apps/www/public/images/launchweek/8/swag-illustration.png new file mode 100644 index 00000000000..ad00d5c2ada Binary files /dev/null and b/apps/www/public/images/launchweek/8/swag-illustration.png differ diff --git a/apps/www/public/images/launchweek/8/swag/cap.jpg b/apps/www/public/images/launchweek/8/swag/cap.jpg new file mode 100644 index 00000000000..81016d0a64b Binary files /dev/null and b/apps/www/public/images/launchweek/8/swag/cap.jpg differ diff --git a/apps/www/public/images/launchweek/8/swag/deskmat-lg.jpg b/apps/www/public/images/launchweek/8/swag/deskmat-lg.jpg new file mode 100644 index 00000000000..d6e7819d404 Binary files /dev/null and b/apps/www/public/images/launchweek/8/swag/deskmat-lg.jpg differ diff --git a/apps/www/public/images/launchweek/8/swag/deskmat.jpg b/apps/www/public/images/launchweek/8/swag/deskmat.jpg new file mode 100644 index 00000000000..e0666bb9c57 Binary files /dev/null and b/apps/www/public/images/launchweek/8/swag/deskmat.jpg differ diff --git a/apps/www/public/images/launchweek/8/swag/shirt.jpg b/apps/www/public/images/launchweek/8/swag/shirt.jpg new file mode 100644 index 00000000000..e6c1cbba65a Binary files /dev/null and b/apps/www/public/images/launchweek/8/swag/shirt.jpg differ diff --git a/apps/www/public/images/launchweek/8/swag/stickers.jpg b/apps/www/public/images/launchweek/8/swag/stickers.jpg new file mode 100644 index 00000000000..74882b4c2e2 Binary files /dev/null and b/apps/www/public/images/launchweek/8/swag/stickers.jpg differ diff --git a/apps/www/public/images/launchweek/8/ticket-bg/golden.png b/apps/www/public/images/launchweek/8/ticket-bg/golden.png new file mode 100644 index 00000000000..cfd151ca8b8 Binary files /dev/null and b/apps/www/public/images/launchweek/8/ticket-bg/golden.png differ diff --git a/apps/www/public/images/launchweek/8/ticket-bg/regular.png b/apps/www/public/images/launchweek/8/ticket-bg/regular.png new file mode 100644 index 00000000000..45bc0cac006 Binary files /dev/null and b/apps/www/public/images/launchweek/8/ticket-bg/regular.png differ diff --git a/apps/www/public/images/logos/frameworks/kotlin.svg b/apps/www/public/images/logos/frameworks/kotlin.svg index 93fee789273..e9a71e02b96 100644 --- a/apps/www/public/images/logos/frameworks/kotlin.svg +++ b/apps/www/public/images/logos/frameworks/kotlin.svg @@ -1 +1,2 @@ -icon_Kotlin \ No newline at end of file + \ No newline at end of file diff --git a/apps/www/styles/index.css b/apps/www/styles/index.css index 2b755d95c61..e0843c3b687 100644 --- a/apps/www/styles/index.css +++ b/apps/www/styles/index.css @@ -177,9 +177,6 @@ h6:not(.overwrite) { .section-container { position: relative; - /* z-index: 1; */ - /* display: flex; - justify-content: center; */ } /* Database page */ @@ -188,10 +185,6 @@ h6:not(.overwrite) { @apply border border-gray-50 dark:border-gray-700; } -/* a { - @apply hover:text-black dark:hover:text-white; -} */ - .sbui-tabs--alt div[role='tablist'] .sbui-btn-primary { @apply dark:bg-scale-300 bg-gray-800 text-white dark:text-white; text-shadow: none; @@ -213,20 +206,6 @@ h6:not(.overwrite) { /* Auth page */ -/* .auth-container > div:nth-child(2) { - width: 420px; -} */ - -/* .auth-container { - @apply rounded; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%23edebf0' fill-opacity='0.4'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); -} - -.dark .auth-container { - background-color: #181818; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'%3E%3Cg fill-rule='evenodd'%3E%3Cg fill='%23313131' fill-opacity='0.4'%3E%3Cpath opacity='.5' d='M96 95h4v1h-4v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4h-9v4h-1v-4H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15v-9H0v-1h15V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h9V0h1v15h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9h4v1h-4v9zm-1 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm9-10v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-10 0v-9h-9v9h9zm-9-10h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9zm10 0h9v-9h-9v9z'/%3E%3Cpath d='M6 5V0H5v5H0v1h5v94h1V6h94V5H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); -} */ - .dark .header--light { display: none; } @@ -303,6 +282,47 @@ table { -webkit-box-orient: vertical; } +/** +animations +*/ + +.fade-in-up-2ms { + opacity: 0; + transform: translate3d(0, 10px, 0); + animation: fadeInUp 0.2s cubic-bezier(0.25, 0.25, 0, 1) both; +} + +.fade-in-up-4ms { + opacity: 0; + transform: translate3d(0, 10px, 0); + animation: fadeInUp 0.4s cubic-bezier(0.25, 0.25, 0, 1) both; +} + +.fade-in-2ms { + opacity: 0; + transform: translate3d(0, 10px, 0); + animation: fadeIn 0.2s cubic-bezier(0.25, 0.25, 0, 1) both; +} + +.fade-in-4ms { + opacity: 0; + transform: translate3d(0, 10px, 0); + animation: fadeIn 0.4s cubic-bezier(0.25, 0.25, 0, 1) both; +} + +@keyframes fadeInUp { + to { + opacity: 1; + transform: translate3d(0, 0, 0); + } +} + +@keyframes fadeIn { + to { + opacity: 1; + } +} + /** launch week */ @@ -491,13 +511,6 @@ launch week left: 0; right: 0; overflow: hidden; - /* background: radial-gradient( - circle at 50% -150%, - #39617d 0%, - #00ded1 30%, - #121212 80%, - #121212 100% - ); */ } @media screen and (max-width: 769px) { .gradient-container { @@ -524,32 +537,10 @@ launch week ); } -.light .gradient-mask { - background: radial-gradient( - circle at 50% -35%, - rgba(57, 97, 125, 0.32) 0%, - rgba(57, 97, 125, 0.32) 5%, - rgb(113 191 187 / 75%) 8%, - #ffffff 65%, - #ffffff 100% - ); -} - @media screen and (max-width: 769px) { .gradient-mask { width: 100%; } - - .light .gradient-mask { - background: radial-gradient( - circle at 50% -35%, - rgba(57, 97, 125, 0.32) 0%, - rgba(57, 97, 125, 0.32) 5%, - rgb(155 246 241 / 75%) 8%, - #90cebd 65%, - #67eae8 100% - ); - } } /* The bottom part below the stroke */ @@ -564,18 +555,10 @@ launch week background: radial-gradient(circle at 50% -60%, rgba(255, 255, 255, 0) 0%, #121212 65%); } -.light .gradient-mask--masked { - background: radial-gradient(circle at 50% -60%, rgba(255, 255, 255, 0) 0%, #fff 65%); -} - @media screen and (max-width: 769px) { .gradient-mask--masked { background: radial-gradient(circle at 50% -40%, rgba(255, 255, 255, 0) 0%, #121212 65%); } - - .light .gradient-mask--masked { - background: radial-gradient(circle at 50% -40%, rgba(255, 255, 255, 0) 0%, #fff 65%); - } } /* The stroke on the circle */ @@ -590,10 +573,6 @@ launch week clip-path: ellipse(52% 213px at 50% -75px); } -.light .flair-mask-a { - background-color: #d7d7d7; -} - /* The inside of the circle */ .flair-mask-b { position: absolute; @@ -607,10 +586,6 @@ launch week clip-path: ellipse(52% 213px at 50% -75px); } -.light .flair-mask-b { - background-color: #fff; -} - @media only screen and (min-width: 600px) { .flair-mask-a { top: 1px; @@ -645,15 +620,6 @@ launch week } } -/* @media only screen and (min-width: 1024px) { - .flair-mask-a { - width: 69.5%; - } - .flair-mask-b { - width: 70%; - } -} */ - .orbit { width: 192px; height: 192px; @@ -698,23 +664,6 @@ launch week } .radial-mask { - /* -webkit-mask-image: conic-gradient( - from 0deg at 45% 47%, - #ffffffff 0%, - #000000ff 25%, - #000000ff 50%, - #000000ff 75%, - #ffffffff 100% - ); - mask-image: conic-gradient( - from 0deg at 45% 47%, - #ffffffff 0%, - #000000ff 25%, - #000000ff 50%, - #000000ff 75%, - #ffffffff 100% - ); */ - -webkit-mask-image: linear-gradient(rgba(0, 0, 0, 1), transparent); } @@ -789,3 +738,14 @@ launch week .swiper-transition-linear .swiper-wrapper { transition-timing-function: linear; } + +/* .lw8-particle-handle { + @apply text-xs bg-black flex items-center justify-center rounded-full py-1 px-2 [&>div]:!relative; +} */ + +/* dat.GUI */ + +.dg.ac { + z-index: 9999 !important; + margin-top: 65px !important; +} diff --git a/apps/www/supabase/functions/lw8-ticket-og/handler.tsx b/apps/www/supabase/functions/lw8-ticket-og/handler.tsx new file mode 100644 index 00000000000..4794d65b5d7 --- /dev/null +++ b/apps/www/supabase/functions/lw8-ticket-og/handler.tsx @@ -0,0 +1,139 @@ +import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0' +import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts' +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.7.1' +import { corsHeaders } from '../_shared/cors.ts' + +const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw8' + +const BUCKET_FOLDER_VERSION = 'v1' + +export async function handler(req: Request) { + try { + if (req.method !== 'POST') throw new Error('method not supported') + const { + username = 'thorwebdev', + golden = false, + }: { + username?: string + golden?: boolean + } = await req.json() + + // Else, generate image and upload to storage. + const timestamp = encodeURI(new Date().toISOString()) + const ticketImg = `${STORAGE_URL}/tickets/${ + golden ? 'golden' : 'regular' + }/${BUCKET_FOLDER_VERSION}/${username}.png?t=${timestamp}` + + const bgImg = `${STORAGE_URL}/assets/backgrounds/og-${golden ? 'golden' : 'regular'}.png` + const ticketWidth = 1100 + const ticketHeight = ticketWidth / 2 + const boxOffset = 24 + const boxWidth = `${ticketWidth + boxOffset}px` + const boxHeight = `${ticketHeight + boxOffset}px` + const leftOffset = `${(1200 - ticketWidth) / 2 - boxOffset / 2}px` + const topOffset = `${(628 - ticketHeight) / 2 - boxOffset / 2}px` + + const geneartedOGImage = new ImageResponse( + ( +
+ {/* Background */} + + {/* Ticket Box */} +
+ {/* Ticket */} + +
+ ), + { + width: 1200, + height: 628, + headers: { + 'content-type': 'image/png', + 'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable', + 'cdn-cache-control': 'max-age=31536000', + }, + } + ) + + // Upload image to storage. + const supabaseAdminClient = createClient( + // Supabase API URL - env var exported by default when deployed. + Deno.env.get('SUPABASE_URL') ?? '', + // Supabase API SERVICE ROLE KEY - env var exported by default when deployed. + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' + ) + const { error: storageError } = await supabaseAdminClient.storage + .from('images') + .upload( + `lw8/og/${golden ? 'golden' : 'regular'}/${BUCKET_FOLDER_VERSION}/${username}.png`, + geneartedOGImage.body!, + { + contentType: 'image/png', + cacheControl: '0', + upsert: true, + } + ) + if (storageError) throw new Error(`storageError: ${storageError.message}`) + // return geneartedOGImage + + return new Response(JSON.stringify({ success: true }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 200, + }) + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }) + } +} diff --git a/apps/www/supabase/functions/lw8-ticket-og/index.ts b/apps/www/supabase/functions/lw8-ticket-og/index.ts new file mode 100644 index 00000000000..26986700825 --- /dev/null +++ b/apps/www/supabase/functions/lw8-ticket-og/index.ts @@ -0,0 +1,11 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +import { serve } from 'https://deno.land/std@0.177.0/http/server.ts' + +import { handler } from './handler.tsx' + +console.log(`Function "lw8-ticket-og" up and running`) + +serve(handler) diff --git a/apps/www/supabase/functions/lw8-ticket/README.md b/apps/www/supabase/functions/lw8-ticket/README.md new file mode 100644 index 00000000000..9c5b3d4e49e --- /dev/null +++ b/apps/www/supabase/functions/lw8-ticket/README.md @@ -0,0 +1,21 @@ +# Open Graph (OG) Image Generation with Supabase Storage CDN Caching + +Generate Open Graph images with Deno and Supabase Edge Functions and cache the generated image with Supabase Storage CDN. + +- Docs: https://deno.land/x/og_edge@0.0.2 +- Examples: https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples +- Demo: https://obuldanrptloktxcffvn.functions.supabase.co/lw8-ticket?username=thorwebdev + +## Run locally + +```bash +supabase functions serve lw8-ticket --no-verify-jwt +``` + +Navigate to http://localhost:54321/functions/v1/lw8-ticket?username=thorwebdev + +## Deploy + +```bash +supabase functions deploy lw8-ticket --no-verify-jwt +``` diff --git a/apps/www/supabase/functions/lw8-ticket/handler.tsx b/apps/www/supabase/functions/lw8-ticket/handler.tsx new file mode 100644 index 00000000000..cda66443332 --- /dev/null +++ b/apps/www/supabase/functions/lw8-ticket/handler.tsx @@ -0,0 +1,279 @@ +import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0' +import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts' +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.7.1' +import { corsHeaders } from '../_shared/cors.ts' + +const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw8' + +// Load custom font +const FONT_URL = `${STORAGE_URL}/font/CircularStd-Book.otf` +const MONO_FONT_URL = `${STORAGE_URL}/font/SourceCodePro-Regular.ttf?t=2023-07-18T13%3A03%3A29.474Z` +const font = fetch(new URL(FONT_URL, import.meta.url)).then((res) => res.arrayBuffer()) +const mono_font = fetch(new URL(MONO_FONT_URL, import.meta.url)).then((res) => res.arrayBuffer()) +const BUCKET_FOLDER_VERSION = 'v1' + +const LW_TABLE = 'lw8_tickets_golden' + +export async function handler(req: Request) { + const url = new URL(req.url) + const username = url.searchParams.get('username') ?? url.searchParams.get('amp;username') + const assumeGolden = url.searchParams.get('golden') ?? url.searchParams.get('amp;golden') + const userAgent = req.headers.get('user-agent') + + try { + if (!username) throw new Error('missing username param') + + const supabaseAdminClient = createClient( + // Supabase API URL - env var exported by default when deployed. + Deno.env.get('SUPABASE_URL') ?? '', + // Supabase API SERVICE ROLE KEY - env var exported by default when deployed. + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' + ) + + // Track social shares + if (userAgent?.toLocaleLowerCase().includes('twitter')) { + await supabaseAdminClient + .from(LW_TABLE) + .update({ sharedOnTwitter: 'now' }) + .eq('username', username) + .is('sharedOnTwitter', null) + } else if (userAgent?.toLocaleLowerCase().includes('linkedin')) { + await supabaseAdminClient + .from(LW_TABLE) + .update({ sharedOnLinkedIn: 'now' }) + .eq('username', username) + .is('sharedOnLinkedIn', null) + } + + // Get ticket data + const { data, error } = await supabaseAdminClient + .from(LW_TABLE) + .select('name, ticketNumber, sharedOnTwitter, sharedOnLinkedIn, metadata') + .eq('username', username) + .maybeSingle() + + if (error) console.log(error.message) + if (!data) throw new Error(error?.message ?? 'user not found') + const { name, ticketNumber, metadata } = data + + const golden = (!!data?.sharedOnTwitter && !!data?.sharedOnLinkedIn) ?? false + if (assumeGolden && !golden) return await fetch(`${STORAGE_URL}/golden_no_meme.png`) + + // Else, generate image and upload to storage. + const BACKGROUND = { + REG: { + BG: `${STORAGE_URL}/assets/backgrounds/regular.png`, + LOGO: `${STORAGE_URL}/assets/lw8-logo-regular.png`, + }, + GOLD: { + BG: `${STORAGE_URL}/assets/backgrounds/golden.png`, + LOGO: `${STORAGE_URL}/assets/lw8-logo-gold.png`, + }, + } + + const fontData = await font + const monoFontData = await mono_font + const numDigits = `${Number(ticketNumber)}`.length + const prefix = `00000000`.slice(numDigits) + const HAS_ROLE = !!metadata?.role + const HAS_COMPANY = !!metadata?.company + const HAS_LOCATION = !!metadata?.location + const HAS_NO_META = !HAS_ROLE && !HAS_COMPANY && !HAS_LOCATION + + const generatedTicketImage = new ImageResponse( + ( + <> +
+ {/* Background */} + +
+ +
+ + {/* Name & username */} +
+
+

+ {name ?? username} +

+
+ + {/* Username */} +
+ {HAS_NO_META && username && `@${username}`} + {HAS_ROLE && `${metadata.role}`} + {HAS_COMPANY && `${HAS_ROLE ? ' at ' : ''}${metadata.company} `} + {HAS_LOCATION && `${(HAS_ROLE || HAS_COMPANY) && ' —'} ${metadata.location}`} +
+
+

+ August 7-11 + supabase.com/launch-week +

+
+ {/* Ticket No */} +

+ {`NO ${prefix}${ticketNumber}`} +

+ + ), + { + width: 1200, + height: 628, + fonts: [ + { + name: 'Circular', + data: fontData, + style: 'normal', + }, + { + name: 'SourceCodePro', + data: monoFontData, + style: 'normal', + }, + ], + headers: { + 'content-type': 'image/png', + 'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable', + 'cdn-cache-control': 'max-age=31536000', + }, + } + ) + + // Upload image to storage. + const { error: storageError } = await supabaseAdminClient.storage + .from('images') + .upload( + `lw8/tickets/${golden ? 'golden' : 'regular'}/${BUCKET_FOLDER_VERSION}/${username}.png`, + generatedTicketImage.body!, + { + contentType: 'image/png', + // cacheControl: `${60 * 60 * 24 * 7}`, + cacheControl: `0`, + // Update cached og image, people might need to update info + upsert: true, + } + ) + if (storageError) throw new Error(`storageError: ${storageError.message}`) + + // Generate og image + fetch('https://obuldanrptloktxcffvn.functions.supabase.co/lw8-ticket-og', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9idWxkYW5ycHRsb2t0eGNmZnZuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2Njk3MjcwMTIsImV4cCI6MTk4NTMwMzAxMn0.SZLqryz_-stF8dgzeVXmzZWPOqdOrBwqJROlFES8v3I', + }, + body: JSON.stringify({ + username, + golden, + }), + }) + + return await fetch( + `${STORAGE_URL}/tickets/${ + golden ? 'golden' : 'regular' + }/${BUCKET_FOLDER_VERSION}/${username}.png` + ) + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + }) + } +} diff --git a/apps/www/supabase/functions/lw8-ticket/index.ts b/apps/www/supabase/functions/lw8-ticket/index.ts new file mode 100644 index 00000000000..84d78bdf295 --- /dev/null +++ b/apps/www/supabase/functions/lw8-ticket/index.ts @@ -0,0 +1,11 @@ +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. + +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' + +import { handler } from './handler.tsx' + +console.log(`Function "lw8-ticket" up and running`) + +serve(handler) diff --git a/apps/www/tailwind.config.js b/apps/www/tailwind.config.js index 66c96328173..3d8cfeba688 100644 --- a/apps/www/tailwind.config.js +++ b/apps/www/tailwind.config.js @@ -17,10 +17,20 @@ module.exports = config({ '0%': { backgroundColor: 'rgba(63, 207, 142, 0.1)' }, '100%': { backgroundColor: 'transparent' }, }, + slideIn: { + '0%, 100%': { transform: 'translate3d(0,0,0)' }, + }, + slideIn: { + '0%, 100%': { transform: 'translate3d(0,0,0)' }, + }, + spin: { + '0%, 100%': { transform: 'rotate(360deg)' }, + }, }, animation: { 'flash-code': 'flash-code 1s forwards', 'flash-code-slow': 'flash-code 2s forwards', + spin: 'spin 1s both infinite', }, }, }, diff --git a/examples/product-sample-supabase-kt/local.properties b/examples/product-sample-supabase-kt/local.properties new file mode 100644 index 00000000000..46d870433f4 --- /dev/null +++ b/examples/product-sample-supabase-kt/local.properties @@ -0,0 +1,11 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Mon Jul 24 23:36:03 ICT 2023 +sdk.dir=/Users/hieuvu/Library/Android/sdk +API_KEY=YOUR_SUPABASE_API_KEY +SECRET=YOUR_SUPABASE_SECRET +SUPABASE_URL=YOUR_SUPABASE_URL \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 084b2d064df..f1e6f72d0c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -311,12 +311,15 @@ "@mdx-js/react": "^2.1.5", "@next/bundle-analyzer": "^13.4.0", "@next/mdx": "^12.3.2", + "@react-three/drei": "^8.20.2", + "@react-three/fiber": "^7.0.29", "@supabase/auth-ui-react": "^0.1.6", "@supabase/supabase-js": "^2.23.0", "classnames": "^2.3.1", "cobe": "^0.6.2", "common": "*", "config": "*", + "dat.gui": "^0.7.9", "date-fns": "2.10.0", "dayjs": "^1.11.7", "framer-motion": "^6.5.1", @@ -334,6 +337,7 @@ "parse-numeric-range": "^1.3.0", "react": "^17.0.2", "react-copy-to-clipboard": "^5.1.0", + "react-countdown": "^2.3.5", "react-dom": "^17.0.2", "react-ga": "^3.3.0", "react-markdown": "^8.0.3", @@ -349,6 +353,7 @@ "shared-data": "*", "swiper": "^6.5.0", "swr": "^0.5.4", + "three": "^0.154.0", "typescript": "^5.0.4", "ui": "*", "use-debounce": "^7.0.1", @@ -359,6 +364,7 @@ "devDependencies": { "@tailwindcss/typography": "^0.5.9", "@types/classnames": "^2.3.1", + "@types/dat.gui": "^0.7.10", "@types/jest": "^27.4.0", "@types/mdx-js__react": "^1.5.5", "@types/node": "^17.0.24", @@ -368,6 +374,7 @@ "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^17.0.11", "@types/react-syntax-highlighter": "^15.5.6", + "@types/three": "^0.154.0", "autoprefixer": "^10.4.14", "config": "*", "file-loader": "^6.2.0", @@ -2674,6 +2681,35 @@ "version": "6.0.2", "license": "MIT" }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/@chevrotain/types": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==" + }, + "node_modules/@chevrotain/utils": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==" + }, "node_modules/@cnakazawa/watch": { "version": "1.0.4", "dev": true, @@ -6646,6 +6682,130 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/@react-spring/animated": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", + "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", + "dependencies": { + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", + "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/shared": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", + "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", + "dependencies": { + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/three": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.7.3.tgz", + "integrity": "sha512-Q1p512CqUlmMK8UMBF/Rj79qndhOWq4XUTayxMP9S892jiXzWQuj+xC3Xvm59DP/D4JXusXpxxqfgoH+hmOktA==", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/core": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "@react-three/fiber": ">=6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "three": ">=0.126" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", + "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==" + }, + "node_modules/@react-three/drei": { + "version": "8.20.2", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-8.20.2.tgz", + "integrity": "sha512-V2P/YHYg2+N58eWtIqmOJf0t5nWGTcmpT+ci4tB5gt3CsWU2vuoeQFhvBmqEF+cXwZD5ZYKPfQDf/06NzTx/lw==", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@react-spring/three": "^9.3.1", + "@use-gesture/react": "^10.2.0", + "detect-gpu": "^4.0.14", + "glsl-noise": "^0.0.0", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "meshline": "^2.0.4", + "react-composer": "^5.0.2", + "react-merge-refs": "^1.1.0", + "stats.js": "^0.17.0", + "suspend-react": "^0.0.8", + "three-mesh-bvh": "^0.5.7", + "three-stdlib": "^2.8.9", + "troika-three-text": "^0.46.3", + "utility-types": "^3.10.0", + "zustand": "^3.5.13" + }, + "peerDependencies": { + "@react-three/fiber": ">=6.0", + "react": ">=17.0", + "react-dom": ">=17.0", + "three": ">=0.137" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber": { + "version": "7.0.29", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-7.0.29.tgz", + "integrity": "sha512-TE+/iaGrjluGsPwN2RkLXxKD4ak+unsh0YKIFKGcuAOW9jYcAyMGKAtgFO8MS7/HNCrZZQQSg23sJ8IPgtdkDg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@types/react-reconciler": "^0.26.2", + "react-merge-refs": "^1.1.0", + "react-reconciler": "^0.26.2", + "react-three-fiber": "0.0.0-deprecated", + "react-use-measure": "^2.1.1", + "resize-observer-polyfill": "^1.5.1", + "scheduler": "^0.20.2", + "use-asset": "^1.0.4", + "utility-types": "^3.10.0", + "zustand": "^3.5.1" + }, + "peerDependencies": { + "react": ">=17.0", + "react-dom": ">=17.0", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.3.0", "license": "MIT" @@ -13941,6 +14101,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==", + "dev": true + }, "node_modules/@types/acorn": { "version": "4.0.6", "license": "MIT", @@ -14084,6 +14250,12 @@ "version": "3.0.0", "license": "MIT" }, + "node_modules/@types/dat.gui": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@types/dat.gui/-/dat.gui-0.7.10.tgz", + "integrity": "sha512-sLifoNrvs4lgJkAUZcTB28dbWy0AM05Yc0BzTqHheK0tRYRVsJHoFLocB5heDbb5amaFLEXna41GOM6IG06oqA==", + "dev": true + }, "node_modules/@types/debounce-promise": { "version": "3.1.6", "license": "MIT" @@ -14105,6 +14277,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/draco3d": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.2.tgz", + "integrity": "sha512-goh23EGr6CLV6aKPwN1p8kBD/7tT5V/bLpToSbarKrwVejqNrspVrv8DhliteYkkhZYrlq/fwKZRRUzH4XN88w==" + }, "node_modules/@types/ejs": { "version": "3.1.2", "dev": true, @@ -14358,6 +14535,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.0", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz", + "integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==" + }, "node_modules/@types/papaparse": { "version": "5.3.7", "dev": true, @@ -14468,6 +14650,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-redux": { "version": "7.1.25", "license": "MIT", @@ -14564,6 +14754,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-9w+a7bR8PeB0dCT/HBULU2fMqf6BAzvKbxFboYhmDtDkKPiyXYbjoe2auwsXlEFI7CFNMF1dCv3dFH5Poy9R1w==", + "dev": true + }, "node_modules/@types/strip-ansi": { "version": "5.2.1", "dev": true, @@ -14587,6 +14783,20 @@ "@types/jest": "*" } }, + "node_modules/@types/three": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.154.0.tgz", + "integrity": "sha512-IioqpGhch6FdLDh4zazRn3rXHj6Vn2nVOziJdXVbJFi9CaI65LtP9qqUtpzbsHK2Ezlox8NtsLNHSw3AQzucjA==", + "dev": true, + "dependencies": { + "@tweenjs/tween.js": "~18.6.4", + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.9", + "lil-gui": "~0.17.0", + "meshoptimizer": "~0.18.1" + } + }, "node_modules/@types/uglify-js": { "version": "3.17.1", "dev": true, @@ -14665,6 +14875,11 @@ "@types/node": "*" } }, + "node_modules/@types/webxr": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.2.tgz", + "integrity": "sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw==" + }, "node_modules/@types/yargs": { "version": "15.0.15", "dev": true, @@ -14883,6 +15098,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@use-gesture/core": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.2.27.tgz", + "integrity": "sha512-V4XV7hn9GAD2MYu8yBBVi5iuWBsAMfjPRMsEVzoTNGYH72tf0kFP+OKqGKc8YJFQIJx6yj+AOqxmEHOmx2/MEA==" + }, + "node_modules/@use-gesture/react": { + "version": "10.2.27", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.2.27.tgz", + "integrity": "sha512-7E5vnWCxeslWlxwZ8uKIcnUZVMTRMZ8cvSnLLKF1NkyNb3PnNiAzoXM4G1vTKJKRhgOTeI6wK1YsEpwo9ABV5w==", + "dependencies": { + "@use-gesture/core": "10.2.27" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.9.0", "license": "MIT", @@ -16418,6 +16649,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bidi-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.2.tgz", + "integrity": "sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/big-integer": { "version": "1.6.51", "license": "Unlicense", @@ -17236,6 +17475,19 @@ "node": "*" } }, + "node_modules/chevrotain": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "funding": [ @@ -19032,6 +19284,11 @@ "version": "1.0.8", "license": "BSD-2-Clause" }, + "node_modules/dat.gui": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/dat.gui/-/dat.gui-0.7.9.tgz", + "integrity": "sha512-sCNc1OHobc+Erc1HqiswYgHdVNpSJUlk/Hz8vzOCsER7rl+oF/4+v8GXFUyCgtXpoCX6+bnmg07DedLvBLwYKQ==" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "dev": true, @@ -19075,6 +19332,11 @@ "node": ">=0.11.0" } }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==" + }, "node_modules/debounce-promise": { "version": "3.1.2", "license": "MIT" @@ -19485,6 +19747,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/detect-gpu": { + "version": "4.0.50", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-4.0.50.tgz", + "integrity": "sha512-T67HE5+ONONN8rPXCBJPupyCg2QT8+l2NUUMuPxAppsMJBDPG/Jg0URLs6GyDzLm2niUE+oncIHSuy3VinoPeQ==", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "dev": true, @@ -19759,6 +20029,11 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/draco3d": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.6.tgz", + "integrity": "sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ==" + }, "node_modules/duplexer": { "version": "0.1.2", "license": "MIT" @@ -21368,6 +21643,11 @@ "dev": true, "license": "MIT" }, + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==" + }, "node_modules/figgy-pudding": { "version": "3.5.2", "license": "ISC" @@ -22224,6 +22504,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==" + }, "node_modules/goober": { "version": "2.1.13", "license": "MIT", @@ -26077,6 +26362,11 @@ "node": ">= 8" } }, + "node_modules/ktx-parse": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/ktx-parse/-/ktx-parse-0.4.5.tgz", + "integrity": "sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==" + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "license": "CC0-1.0" @@ -26218,6 +26508,12 @@ "immediate": "~3.0.5" } }, + "node_modules/lil-gui": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/lil-gui/-/lil-gui-0.17.0.tgz", + "integrity": "sha512-MVBHmgY+uEbmJNApAaPbtvNh1RCAeMnKym82SBjtp5rODTYKWtM+MXHCifLe2H2Ti1HuBGBtK/5SyG4ShQ3pUQ==", + "dev": true + }, "node_modules/lilconfig": { "version": "2.1.0", "license": "MIT", @@ -26372,6 +26668,16 @@ "version": "4.6.2", "license": "MIT" }, + "node_modules/lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==" + }, "node_modules/lodash.template": { "version": "4.5.0", "license": "MIT", @@ -27284,6 +27590,20 @@ } } }, + "node_modules/meshline": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-2.0.4.tgz", + "integrity": "sha512-Jh6DJl/zLqA4xsKvGv5950jr2ukyXQE1wgxs8u94cImHrvL6soVIggqjP+2hVHZXGYaKnWszhtjuCbKNeQyYiw==", + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "dev": true + }, "node_modules/methods": { "version": "1.1.2", "dev": true, @@ -28259,6 +28579,11 @@ "dev": true, "license": "MIT" }, + "node_modules/mmd-parser": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mmd-parser/-/mmd-parser-1.0.4.tgz", + "integrity": "sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==" + }, "node_modules/mobx": { "version": "6.9.0", "license": "MIT", @@ -30295,6 +30620,21 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/opentype.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", + "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", + "dependencies": { + "string.prototype.codepointat": "^0.2.1", + "tiny-inflate": "^1.0.3" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/optionator": { "version": "0.9.1", "license": "MIT", @@ -32382,6 +32722,11 @@ "node": ">=0.10.0" } }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "node_modules/preact": { "version": "10.15.1", "license": "MIT", @@ -32890,6 +33235,17 @@ "react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-composer": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz", + "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", + "dependencies": { + "prop-types": "^15.6.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-contexify": { "version": "5.0.0", "license": "MIT", @@ -33278,7 +33634,6 @@ }, "node_modules/react-merge-refs": { "version": "1.1.0", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -33314,6 +33669,22 @@ "version": "3.2.2", "license": "MIT" }, + "node_modules/react-reconciler": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.26.2.tgz", + "integrity": "sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^17.0.2" + } + }, "node_modules/react-redux": { "version": "7.2.9", "license": "MIT", @@ -33490,6 +33861,11 @@ "react": ">= 0.14.0" } }, + "node_modules/react-three-fiber": { + "version": "0.0.0-deprecated", + "resolved": "https://registry.npmjs.org/react-three-fiber/-/react-three-fiber-0.0.0-deprecated.tgz", + "integrity": "sha512-EblIqTAsIpkYeM8bZtC4lcpTE0A2zCEGipFB52RgcQq/q+0oryrk7Sxt+sqhIjUu6xMNEVywV8dr74lz5yWO6A==" + }, "node_modules/react-tooltip": { "version": "4.5.1", "license": "MIT", @@ -33597,6 +33973,18 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-use-measure": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", + "integrity": "sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==", + "dependencies": { + "debounce": "^1.2.1" + }, + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + } + }, "node_modules/react-virtualized-auto-sizer": { "version": "1.0.18", "license": "MIT", @@ -34002,6 +34390,11 @@ "dev": true, "license": "MIT" }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.0", "license": "MIT", @@ -35979,6 +36372,11 @@ "node": ">=0.10.0" } }, + "node_modules/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==" + }, "node_modules/statuses": { "version": "2.0.1", "dev": true, @@ -36351,6 +36749,11 @@ "version": "8.0.0", "license": "MIT" }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "license": "MIT", @@ -36722,6 +37125,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/suspend-react": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.0.8.tgz", + "integrity": "sha512-ZC3r8Hu1y0dIThzsGw0RLZplnX9yXwfItcvaIzJc2VQVi8TGyGDlu92syMB5ulybfvGLHAI5Ghzlk23UBPF8xg==", + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/swiper": { "version": "6.8.4", "funding": [ @@ -37097,6 +37508,40 @@ "node": ">=0.8" } }, + "node_modules/three": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.154.0.tgz", + "integrity": "sha512-Uzz8C/5GesJzv8i+Y2prEMYUwodwZySPcNhuJUdsVMH2Yn4Nm8qlbQe6qRN5fOhg55XB0WiLfTPBxVHxpE60ug==" + }, + "node_modules/three-mesh-bvh": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.5.24.tgz", + "integrity": "sha512-VTIgfjz8aFoPKTQoMIQQv9jJD4ybFRZuKKE1/kqy78FQcuHQ0+iIWv7C5cSb2inlvs7bNMVY3yRx3RXGZfrvzQ==", + "peerDependencies": { + "three": ">= 0.123.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.23.13", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.23.13.tgz", + "integrity": "sha512-WU4XHs4E6szAyoREzTs5bUAc1WFadiz5jRjNlA7aIIqf+raT4jfA320P0XmlyZz/wJwmpZnOuki4kAFsmadkTQ==", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "chevrotain": "^10.1.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "ktx-parse": "^0.4.5", + "mmd-parser": "^1.0.4", + "opentype.js": "^1.3.3", + "potpack": "^1.0.1", + "zstddec": "^0.0.2" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, "node_modules/throat": { "version": "6.0.2", "dev": true, @@ -37131,6 +37576,11 @@ "version": "2.1.0", "license": "MIT" }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "node_modules/tiny-invariant": { "version": "1.3.1", "license": "MIT" @@ -37330,6 +37780,33 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/troika-three-text": { + "version": "0.46.4", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.46.4.tgz", + "integrity": "sha512-Qsv0HhUKTZgSmAJs5wvO7YlBoJSP9TGPLmrg+K9pbQq4lseQdcevbno/WI38bwJBZ/qS56hvfqEzY0zUEFzDIw==", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.46.0", + "troika-worker-utils": "^0.46.0", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.103.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.46.0.tgz", + "integrity": "sha512-llHyrXAcwzr0bpg80GxsIp73N7FuImm4WCrKDJkAqcAsWmE5pfP9+Qzw+oMWK1P/AdHQ79eOrOl9NjyW4aOw0w==", + "peerDependencies": { + "three": ">=0.103.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.46.0.tgz", + "integrity": "sha512-bzOx5f2ZBxkFhXtIvDJlLn2AI3bzCkGVbCndl/2dL5QZrwHEKl45OEIilCxYQQWJG1rEbOD9O80tMjoYjw19OA==" + }, "node_modules/trough": { "version": "1.0.5", "license": "MIT", @@ -38370,6 +38847,17 @@ "node": ">=0.10.0" } }, + "node_modules/use-asset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/use-asset/-/use-asset-1.0.4.tgz", + "integrity": "sha512-7/hqDrWa0iMnCoET9W1T07EmD4Eg/Wmoj/X8TGBc++ECRK4m5yTsjP4O6s0yagbxfqIOuUkIxe2/sA+VR2GxZA==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "react": ">=17.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.0", "license": "MIT", @@ -38477,6 +38965,14 @@ "dev": true, "license": "MIT" }, + "node_modules/utility-types": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", + "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "dev": true, @@ -39109,6 +39605,16 @@ "version": "1.2.0", "license": "Apache-2.0" }, + "node_modules/webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==" + }, "node_modules/webidl-conversions": { "version": "6.1.0", "dev": true, @@ -39895,6 +40401,27 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zstddec": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/zstddec/-/zstddec-0.0.2.tgz", + "integrity": "sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==" + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "license": "MIT", @@ -39912,7 +40439,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "2.42.0", + "@supabase/gotrue-js": "^2.46.1", "@supabase/ui": "^0.37.0-alpha.50", "react-use": "^17.4.0" }, @@ -39925,9 +40452,9 @@ } }, "packages/common/node_modules/@supabase/gotrue-js": { - "version": "2.42.0", - "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.42.0.tgz", - "integrity": "sha512-qYyTD+WVP9gjE7Hzw/ywoL9f+1kuUxeKJbb9JSsxV4wZHdKKPFHczfna8ecbmapH7423OXp0lgQAUDX97M39qw==", + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-2.46.1.tgz", + "integrity": "sha512-tebFX3XvPqEJKHOVgkXTN20g9iUhLx6tebIYQvTggYTrqOT2af8oTpSBdgYzbwJ291G6P6CSpR6KY0cT9ade5A==", "dependencies": { "cross-fetch": "^3.1.5" } diff --git a/packages/common/gotrue.ts b/packages/common/gotrue.ts index 05c7e13eb75..f64c0dd3c63 100644 --- a/packages/common/gotrue.ts +++ b/packages/common/gotrue.ts @@ -1,16 +1,23 @@ -import { GoTrueClient } from '@supabase/gotrue-js' +import { GoTrueClient, navigatorLock } from '@supabase/gotrue-js' export const STORAGE_KEY = process.env.NEXT_PUBLIC_STORAGE_KEY || 'supabase.dashboard.auth.token' export const AUTH_DEBUG_KEY = process.env.NEXT_PUBLIC_AUTH_DEBUG_KEY || 'supabase.dashboard.auth.debug' +export const AUTH_NAVIGATOR_LOCK_KEY = + process.env.NEXT_PUBLIC_AUTH_NAVIGATOR_LOCK_KEY || 'supabase.dashboard.auth.navigatorLock.enabled' const debug = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && globalThis?.localStorage?.getItem(AUTH_DEBUG_KEY) === 'true' +const navigatorLockEnabled = + process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && + globalThis?.localStorage?.getItem(AUTH_NAVIGATOR_LOCK_KEY) === 'true' + export const gotrueClient = new GoTrueClient({ url: process.env.NEXT_PUBLIC_GOTRUE_URL, storageKey: STORAGE_KEY, detectSessionInUrl: true, debug, + lock: navigatorLockEnabled ? navigatorLock : undefined, }) diff --git a/packages/common/hooks/index.ts b/packages/common/hooks/index.ts index 65b5a76ec98..12b0a79170f 100644 --- a/packages/common/hooks/index.ts +++ b/packages/common/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useBreakpoint' +export * from './useDebounce' export * from './useParams' export * from './useTelemetryProps' diff --git a/packages/common/hooks/useDebounce.tsx b/packages/common/hooks/useDebounce.tsx new file mode 100644 index 00000000000..9f70e50df7f --- /dev/null +++ b/packages/common/hooks/useDebounce.tsx @@ -0,0 +1,20 @@ +import { useEffect, useMemo, useRef } from 'react' +import { debounce } from 'lodash' + +export const useDebounce = (callback: () => void, delay: number = 1000) => { + const ref = useRef() + + useEffect(() => { + ref.current = callback + }, [callback]) + + const debouncedCallback = useMemo(() => { + const func = () => { + ref.current?.() + } + + return debounce(func, delay) + }, []) + + return debouncedCallback +} diff --git a/packages/common/package.json b/packages/common/package.json index edc67f89aac..c713b87226a 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -6,7 +6,7 @@ "license": "MIT", "dependencies": { "@headlessui/react": "^1.7.14", - "@supabase/gotrue-js": "2.42.0", + "@supabase/gotrue-js": "^2.46.1", "@supabase/ui": "^0.37.0-alpha.50", "react-use": "^17.4.0" }, diff --git a/packages/config/ui.config.js b/packages/config/ui.config.js index 1683f2178a5..be34a9cf667 100644 --- a/packages/config/ui.config.js +++ b/packages/config/ui.config.js @@ -257,8 +257,8 @@ const uiConfig = { }, }, animation: { - 'fade-in': 'fadeIn 300ms', - 'fade-out': 'fadeOut 300ms', + 'fade-in': 'fadeIn 300ms both', + 'fade-out': 'fadeOut 300ms both', 'dropdown-content-show': 'overlayContentShow 100ms cubic-bezier(0.16, 1, 0.3, 1)', 'dropdown-content-hide': 'overlayContentHide 100ms cubic-bezier(0.16, 1, 0.3, 1)', diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index ae5581d03ea..88f5d9b0ef5 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -62,6 +62,9 @@ export * from './src/components/Form' // CMD+K export * from './src/components/Command' +// Widgets +export * from './src/components/CountdownWidget' + // layout export * from './src/components/LoadingLine' diff --git a/packages/ui/src/components/CountdownWidget/CountdownStep.tsx b/packages/ui/src/components/CountdownWidget/CountdownStep.tsx new file mode 100644 index 00000000000..b1521b5ff24 --- /dev/null +++ b/packages/ui/src/components/CountdownWidget/CountdownStep.tsx @@ -0,0 +1,17 @@ +interface CountdownStepProps { + value: string | number + unit: string +} + +function CountdownStep({ value, unit }: CountdownStepProps) { + return ( +
+
+ {value} + {unit} +
+
+ ) +} + +export default CountdownStep diff --git a/packages/ui/src/components/CountdownWidget/CountdownWidget.tsx b/packages/ui/src/components/CountdownWidget/CountdownWidget.tsx new file mode 100644 index 00000000000..ed61492064b --- /dev/null +++ b/packages/ui/src/components/CountdownWidget/CountdownWidget.tsx @@ -0,0 +1,34 @@ +import CountdownStep from './CountdownStep' + +interface CountdownWidgetProps { + days?: string + hours?: string + minutes?: string + seconds?: string +} + +function CountdownWidget({ days, hours, minutes, seconds }: CountdownWidgetProps) { + return ( +
+ {days !== undefined && ( + <> + : + + )} + {hours !== undefined && ( + <> + : + + )} + {minutes !== undefined && ( + <> + + {seconds !== undefined && ' :'} + + )} + {seconds !== undefined && } +
+ ) +} + +export default CountdownWidget diff --git a/packages/ui/src/components/CountdownWidget/index.ts b/packages/ui/src/components/CountdownWidget/index.ts new file mode 100644 index 00000000000..0c677d46517 --- /dev/null +++ b/packages/ui/src/components/CountdownWidget/index.ts @@ -0,0 +1 @@ +export { default as CountdownWidget } from './CountdownWidget' diff --git a/packages/ui/src/layout/banners/Announcement.tsx b/packages/ui/src/layout/banners/Announcement.tsx index 505c20b36bc..6613c5f4aee 100644 --- a/packages/ui/src/layout/banners/Announcement.tsx +++ b/packages/ui/src/layout/banners/Announcement.tsx @@ -58,11 +58,11 @@ const Announcement = ({ return (
window.location.assign(announcement.link)} - className={['relative w-full cursor-pointer', className].join(' ')} + className={['relative z-40 w-full cursor-pointer', className].join(' ')} > {!isLaunchWeekSection && (
diff --git a/packages/ui/src/layout/banners/Announcement.Countdown.tsx b/packages/ui/src/layout/banners/LW8CountdownBanner/LW8CountdownBanner.tsx similarity index 52% rename from packages/ui/src/layout/banners/Announcement.Countdown.tsx rename to packages/ui/src/layout/banners/LW8CountdownBanner/LW8CountdownBanner.tsx index 8031b9ab36d..e1b9a590ff1 100644 --- a/packages/ui/src/layout/banners/Announcement.Countdown.tsx +++ b/packages/ui/src/layout/banners/LW8CountdownBanner/LW8CountdownBanner.tsx @@ -1,13 +1,10 @@ import { useRouter } from 'next/router' import { PropsWithChildren } from 'react' import Countdown from 'react-countdown' -import { AnnouncementProps } from './Announcement' -import _announcement from './data/Announcement.json' - -interface CountdownStepProps { - value: string | number - unit: string -} +import { AnnouncementProps } from '../Announcement' +import _announcement from '../data/Announcement.json' +import CountdownWidget from '../../../components/CountdownWidget/CountdownWidget' +import Lw8BannerBg from './Lw8BannerBg' interface CountdownButtonProps { dest: string @@ -15,41 +12,32 @@ interface CountdownButtonProps { const announcement = _announcement as AnnouncementProps -function CountdownStep({ value, unit }: CountdownStepProps) { - return ( -
-
- {value} - {unit} -
-
- ) -} - function CountdownButton({ children, dest }: PropsWithChildren) { return ( {children} ) } -function CountdownBanner() { +export function LW8CountdownBanner() { const { pathname } = useRouter() const isLaunchWeekPage = pathname === '/launch-week' const isLaunchWeekSection = pathname.includes('launch-week') + if (isLaunchWeekPage) return null + const renderer = ({ days, hours, minutes, seconds, completed }: any) => { if (completed) { // Render a completed state return (
-

Supabase Launch Week 7

+

Supabase Launch Week 8

- Explore + Live now
) @@ -63,17 +51,16 @@ function CountdownBanner() { ].join(' ')} >

- Supabase Launch Week 7 + Supabase Launch Week 8

- : - : - : - +
{!isLaunchWeekPage && (
- Get your ticket + + Get your ticket +
)}
@@ -82,10 +69,19 @@ function CountdownBanner() { } return ( -
- +
+
+ +
+
) } -export default CountdownBanner +export default LW8CountdownBanner diff --git a/packages/ui/src/layout/banners/LW8CountdownBanner/Lw8BannerBg.tsx b/packages/ui/src/layout/banners/LW8CountdownBanner/Lw8BannerBg.tsx new file mode 100644 index 00000000000..72fc431e99c --- /dev/null +++ b/packages/ui/src/layout/banners/LW8CountdownBanner/Lw8BannerBg.tsx @@ -0,0 +1,224 @@ +export default function Lw8BannerBg({ className }: { className?: string }) { + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} diff --git a/packages/ui/src/layout/banners/LW8CountdownBanner/index.ts b/packages/ui/src/layout/banners/LW8CountdownBanner/index.ts new file mode 100644 index 00000000000..f87f7ca1cb9 --- /dev/null +++ b/packages/ui/src/layout/banners/LW8CountdownBanner/index.ts @@ -0,0 +1 @@ +export { LW8CountdownBanner } from './LW8CountdownBanner' diff --git a/packages/ui/src/layout/banners/data/Announcement.json b/packages/ui/src/layout/banners/data/Announcement.json index f8a5db4f13e..abb5cec8362 100644 --- a/packages/ui/src/layout/banners/data/Announcement.json +++ b/packages/ui/src/layout/banners/data/Announcement.json @@ -1,7 +1,7 @@ { "show": true, - "text": "Supabase Launch Week 7", - "launchDate": "2023-04-10T07:00:00.000-07:00", + "text": "Supabase Launch Week 8", + "launchDate": "2023-08-07T09:00:00.000-07:00", "link": "https://supabase.com/launch-week", "badge": "Get your ticket" } diff --git a/packages/ui/src/layout/banners/index.tsx b/packages/ui/src/layout/banners/index.tsx index 88c3f4190ac..49a7d6ec582 100644 --- a/packages/ui/src/layout/banners/index.tsx +++ b/packages/ui/src/layout/banners/index.tsx @@ -1,2 +1,2 @@ export { default as Announcement } from './Announcement' -export { default as AnnouncementCountdown } from './Announcement.Countdown' +export { default as LW8CountdownBanner } from './LW8CountdownBanner/LW8CountdownBanner' diff --git a/spec/supabase_kt_v0.yml b/spec/supabase_kt_v0.yml index e3e0b032dd4..ebda4824eee 100644 --- a/spec/supabase_kt_v0.yml +++ b/spec/supabase_kt_v0.yml @@ -33,9 +33,9 @@ functions: *Note: OTP link verification such as sign ups are not supported on JVM. You may have to send a verification token rather than a url in your E-Mail. To send the token, rather than a redirect url, you have to change `{{ .ConfirmationURL }}` in your sign up email to `{{ .Token }}`* - **On Android, IOS & MacOS**, it uses deeplinks. Refer to the guide below on how to setup deeplinks. + **On Android, IOS & MacOS**, it uses deeplinks. Refer to the guide below on how to setup deeplinks. Alternatively you could use Native Google Auth or a WebView for OAuth. Refer to our [demo](https://github.com/supabase-community/supabase-kt/tree/master/demos/android-login) to learn more. **On JS**, it uses the website origin as the callback url. Session importing gets handled automatically. - **Windows & Linux** currently have no default implementation. Feel free to create a PR. + **Windows, tvOS, watchOS & Linux** currently have no default implementation. Feel free to create a PR. You always make your own implementation and use `gotrue.parseFragmentAndImportSession(fragment)` to let [supabase-kt](https://github.com/supabase-community/supabase-kt) handle the parsing & importing of the session after receiving a callback. @@ -45,18 +45,26 @@ functions: 1. **Setup a deeplink for you app** \ On Android you may setup a [deeplink](https://developer.android.com/training/app-links/deep-linking) in your Android manifest. \ On IOS & MacOS you may setup a [url scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app). - 2. **Configure the GoTrue plugin** + 2. **Add your deeplink to the [redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration)** \ + **Pattern**: scheme://host + 3. **Configure the GoTrue plugin** You have to set the `host` and the `scheme` in the GoTrue config: ```kotlin install(GoTrue) { - host = "deeplink host" + host = "deeplink host" // this can be anything, eg. your package name or app/company url (not your supabase url) scheme = "deeplink scheme" } ``` - 3. **Call platform specific function on startup** \ + 4. **Call platform specific function on startup** \ On Android: `client.handleDeeplinks(intent)` \ On IOS/MacOS: `client.handleDeeplinks(url)` + Then you can just login using OAuth: + ```kotlin + client.gotrue.loginWith(Google) + ``` + Or open OTP links directly in your app. + ### PKCE Authentication flow suapbase-kt supports the [PKCE authentication flow](https://supabase.com/blog/supabase-auth-sso-pkce). To use it, you just have to change the `flowType` in the GoTrue configuration: @@ -65,6 +73,7 @@ functions: flowType = FlowType.PKCE } ``` + That's it! If you already implemented deeplinks to handle OTPs and OAuth you don't have to change anything! examples: - id: initialize-client name: Initialize Client @@ -596,7 +605,7 @@ functions: `delete()`, and `rpc()` code: | ```kotlin - client.postgrest["cities"].select(columns = Columns.list("name, country_id")) { + client.postgrest["cities"].select(columns = Columns.list("name", "country_id")) { City::name eq "The Shire" //or eq("name", "The Shire") @@ -2172,8 +2181,7 @@ functions: ```kotlin client.gotrue.loginWith(IDToken) { idToken = "token" - clientId = "oauth client id" - provider = Google //Or Apple + provider = Google //Also supported: Apple, Azure and Facebook //optional: nonce = "nonce" data = buildJsonObject { @@ -2181,6 +2189,7 @@ functions: } } ``` + - id: sign-in-with-otp title: 'sendOtpTo()' $ref: '@supabase/gotrue-js.GoTrueClient.signInWithOtp' @@ -2313,9 +2322,23 @@ functions: name: Sign out isSpotlight: true code: | - ```js + ```kotlin client.gotrue.logout() ``` + - id: sign-out-all-sessions + name: Sign out all sessions + isSpotlight: true + code: | + ```kotlin + client.gotrue.logout(LogoutScope.GLOBAL) + ``` + - id: sign-out-others + name: Sign out all sessions except the current + isSpotlight: true + code: | + ```kotlin + client.gotrue.logout(LogoutScope.OTHERS) + ``` - id: verify-otp title: 'Verify OTPs' $ref: '@supabase/gotrue-js.GoTrueClient.verifyOtp' @@ -3114,6 +3137,7 @@ functions: println(it) } + client.realtime.connect() channel.join(blockUntilJoined = true) channel.broadcast(event = "message", Message("I joined!", "John")) @@ -3138,6 +3162,7 @@ functions: connectedUsers -= it.decodeLeavesAs() } + client.realtime.connect() channel.join(blockUntilJoined = true) //send own state channel.track(PresenceState(username = "John")) @@ -3160,6 +3185,7 @@ functions: } } + client.realtime.connect() channel.join() ``` - id: listen-to-a-specific-table @@ -3183,6 +3209,7 @@ functions: } } + client.realtime.connect() channel.join() ``` - id: listen-to-inserts @@ -3201,6 +3228,7 @@ functions: println(it.record) } + client.realtime.connect() channel.join() ``` - id: listen-to-updates @@ -3227,6 +3255,7 @@ functions: println(it.oldRecord) } + client.realtime.connect() channel.join() ``` - id: listen-to-deletes @@ -3252,6 +3281,7 @@ functions: println(it.oldRecord) } + client.realtime.connect() channel.join() ``` - id: listening-to-row-level-changes @@ -3272,6 +3302,7 @@ functions: println(it.oldRecord) } + client.realtime.connect() channel.join() ``` @@ -3325,13 +3356,13 @@ functions: notes: | Returns all Realtime channels. examples: - - id: get-all-channels - name: Get all channels - isSpotlight: true - code: | - ```kotlin - val channels = client.realtime.subscriptions.entries - ``` + - id: get-all-channels + name: Get all channels + isSpotlight: true + code: | + ```kotlin + val channels = client.realtime.subscriptions.entries + ``` - id: list-buckets title: listBuckets() $ref: '@supabase/storage-js.packages/StorageBucketApi.default.listBuckets' diff --git a/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx b/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx index 26cba0e4739..05d54accff0 100644 --- a/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx +++ b/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx @@ -32,7 +32,7 @@ const SmtpForm = () => { if (isLoaded && isSmtpEnabled(config)) { setEnableSmtp(true) } - }, [isLoaded]) + }, [isLoaded, config]) const schema = object({ SMTP_ADMIN_EMAIL: string().when([], { @@ -133,7 +133,7 @@ const SmtpForm = () => { const formValues = generateFormValues(config) resetForm({ values: formValues, initialValues: formValues }) } - }, [isLoaded]) + }, [isLoaded, config]) const onResetForm = () => { setEnableSmtp(isSmtpEnabled(initialValues)) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx index eeb1a84654a..bce6adb9488 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx @@ -1,25 +1,25 @@ -import { FC } from 'react' import * as Tooltip from '@radix-ui/react-tooltip' -import { - Checkbox, - Input, - IconX, - IconMenu, - Popover, - IconLink, - IconSettings, - Button, - IconArrowRight, -} from 'ui' import type { PostgresType } from '@supabase/postgres-meta' +import { + Button, + Checkbox, + IconArrowRight, + IconLink, + IconMenu, + IconSettings, + IconX, + Input, + Popover, +} from 'ui' -import { ColumnField } from '../SidePanelEditor.types' +import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' +import { noop } from 'lodash' +import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants' +import { Suggestion } from '../ColumnEditor/ColumnEditor.types' +import { getForeignKeyDeletionAction } from '../ColumnEditor/ColumnEditor.utils' import ColumnType from '../ColumnEditor/ColumnType' import InputWithSuggestions from '../ColumnEditor/InputWithSuggestions' -import { Suggestion } from '../ColumnEditor/ColumnEditor.types' -import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants' -import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' -import { getForeignKeyDeletionAction } from '../ColumnEditor/ColumnEditor.utils' +import { ColumnField } from '../SidePanelEditor.types' /** * [Joshen] For context: @@ -39,7 +39,7 @@ import { getForeignKeyDeletionAction } from '../ColumnEditor/ColumnEditor.utils' * - Cannot be both identity AND array, still checkboxes as they can be toggled off */ -interface Props { +interface ColumnProps { column: ColumnField enumTypes: PostgresType[] isNewRecord: boolean @@ -50,16 +50,16 @@ interface Props { onRemoveColumn: () => void } -const Column: FC = ({ +const Column = ({ column = {} as ColumnField, enumTypes = [] as PostgresType[], isNewRecord = false, hasImportContent = false, dragHandleProps = {}, - onEditRelation = () => {}, - onUpdateColumn = () => {}, - onRemoveColumn = () => {}, -}) => { + onEditRelation = noop, + onUpdateColumn = noop, + onRemoveColumn = noop, +}: ColumnProps) => { const suggestions: Suggestion[] = typeExpressionSuggestions?.[column.format] ?? [] const settingsCount = [ @@ -150,7 +150,8 @@ const Column: FC = ({ className="table-editor-column-type lg:gap-0 " disabled={column.foreignKey !== undefined} onOptionSelect={(format: string) => { - onUpdateColumn({ format, defaultValue: null }) + const defaultValue = format === 'uuid' ? 'gen_random_uuid()' : null + onUpdateColumn({ format, defaultValue }) }} />
diff --git a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx index c83802db398..0c07827d53c 100644 --- a/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx +++ b/studio/components/layouts/SQLEditorLayout/SQLEditorMenu.tsx @@ -29,6 +29,8 @@ const SideBarContent = observer(() => { const snap = useSqlEditorStateSnapshot() const { isLoading, isSuccess } = useSqlSnippetsQuery(ref, { + refetchOnWindowFocus: false, + staleTime: 300, // 5 minutes onSuccess(data) { if (ref) snap.setRemoteSnippets(data.snippets, ref) }, diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx index e0cd2f02f89..a6b134030f6 100644 --- a/studio/pages/authorize.tsx +++ b/studio/pages/authorize.tsx @@ -26,7 +26,7 @@ const APIAuthorizationPage: NextPageWithLayout = () => { const { data: organizations, isLoading: isLoadingOrganizations } = useOrganizationsQuery() const { data: requester, isLoading, isError, error } = useApiAuthorizationQuery({ id: auth_id }) - const isApproved = requester?.approved_at !== null + const isApproved = (requester?.approved_at ?? null) !== null const isExpired = dayjs().isAfter(dayjs(requester?.expires_at)) const { mutate: approveRequest, isLoading: isApproving } = useApiAuthorizationApproveMutation({ diff --git a/studio/pages/project/[ref]/settings/auth.tsx b/studio/pages/project/[ref]/settings/auth.tsx index be0d718ded0..942ade35fb3 100644 --- a/studio/pages/project/[ref]/settings/auth.tsx +++ b/studio/pages/project/[ref]/settings/auth.tsx @@ -2,7 +2,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { observer } from 'mobx-react-lite' import { useEffect } from 'react' -import { useParams } from 'common' import { AutoSchemaForm, SmtpForm } from 'components/interfaces/Auth' import { SettingsLayout } from 'components/layouts' import NoPermission from 'components/ui/NoPermission' @@ -10,12 +9,11 @@ import { useCheckPermissions, useStore } from 'hooks' import { NextPageWithLayout } from 'types' const PageLayout: NextPageWithLayout = () => { - const { authConfig } = useStore() - const { ref: projectRef } = useParams() + const { authConfig, ui } = useStore() useEffect(() => { authConfig.load() - }, [projectRef]) + }, [ui.selectedProjectRef]) const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue')