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/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 4e0bdaa626a..8ff54ba58c6 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -477,6 +477,7 @@ export const auth = { url: undefined, items: [ { name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha' }, + { name: 'Configuring Custom SMTP', url: '/guides/auth/auth-smtp' }, { name: 'Managing User Data', url: '/guides/auth/managing-user-data' }, { name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa' }, { name: 'Row Level Security', url: '/guides/auth/row-level-security' }, 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/ai/examples/building-chatgpt-plugins.mdx b/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx index 1f34a54c413..80d80a6cc65 100644 --- a/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx +++ b/apps/docs/pages/guides/ai/examples/building-chatgpt-plugins.mdx @@ -153,7 +153,7 @@ And after ChatGPT receives a response from the plugin it will answer your questi ## Resources - ChatGPT Retrieval Plugin: [github.com/openai/chatgpt-retrieval-plugin](https://github.com/openai/chatgpt-retrieval-plugin) -- ChatGTP Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction) +- ChatGPT Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction) export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/auth/auth-captcha.mdx b/apps/docs/pages/guides/auth/auth-captcha.mdx index 0834167875d..e36e96262bc 100644 --- a/apps/docs/pages/guides/auth/auth-captcha.mdx +++ b/apps/docs/pages/guides/auth/auth-captcha.mdx @@ -51,7 +51,7 @@ In the Settings page, look for the **Sitekey** section and copy the key. ## Enable Captcha protection for your Supabase project -Navigate to the **[Authentication](https://supabase.com/dashboard/project/_/settings/auth)** page in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section. +Navigate to the **[Auth](https://supabase.com/dashboard/project/_/settings/auth)** section of your Project Settings in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section. ![supabase_auth_general_settings.png](/docs/img/guides/auth-captcha/supabase_auth_general_settings.png) 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/auth/auth-smtp.mdx b/apps/docs/pages/guides/auth/auth-smtp.mdx new file mode 100644 index 00000000000..8f30266f81f --- /dev/null +++ b/apps/docs/pages/guides/auth/auth-smtp.mdx @@ -0,0 +1,39 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'auth-smtp', + title: 'Configure a Custom SMTP', + description: 'Moving towards production: Configuring a custom SMTP provider', +} + +# Auth SMTP + +At present, you can trial the Supabase platform by sending up to **4** emails per hour via the built-in service. The default email service as a whole is offered on a best effort basis: we will do our best to maintain it and will review usage of the service on a regular basis to see if the email service should be continued. + +As you progress toward production, you may find yourself wanting for a custom SMTP service in order to increase your limits. A custom SMTP server will allow you to set your own cap on the number of emails sent per hour. + +Beyond rate limits, an SMTP server might also help with: + +- Deliverability and Reputation Management +- Scalability +- Analytics and Tracking +- Compliance and Anti Spam measures + +## How to Set up SMTP + +Head over to the [Auth Settings Page](https://supabase.com/dashboard/project/_/settings/auth) and hit "Enable Custom SMTP" under the SMTP Provider section. + +Fill in fields below with the relevant details obtained from your custom SMTP provider: + +![SMTP settings](/docs/img/guides/auth-smtp/smtp.png) + +### SMTP Providers + +You can use Supabase Auth with any major SMTP provider of your choosing. Some SMTP providers you could consider using are: + +- [Twilio SendGrid](https://docs.sendgrid.com/for-developers/sending-email/integrating-with-the-smtp-api) +- [AWS SES](https://docs.aws.amazon.com/ses/latest/dg/send-email-smtp.html) +- [Resend](https://resend.com/docs/dashboard/emails/introduction) + +export const Page = ({ children }) => +export default Page diff --git a/apps/docs/pages/guides/auth/phone-login/twilio.mdx b/apps/docs/pages/guides/auth/phone-login/twilio.mdx index d4bae16558c..60277debe66 100644 --- a/apps/docs/pages/guides/auth/phone-login/twilio.mdx +++ b/apps/docs/pages/guides/auth/phone-login/twilio.mdx @@ -7,16 +7,42 @@ export const meta = { video: 'https://www.youtube.com/v/akScoPO01bc', } -## Overview - In this guide we'll show you how to authenticate your users with SMS based One-Time Password (OTP) tokens. There are two reasons to use Supabase SMS OTP tokens: -- You want users to log in with mobile number + password, and the mobile number should be verified via SMS -- You want users to log in with mobile number ONLY (i.e. passwordless login) +- You want users to log in with a phone number and password, and verify the phone number on signup with SMS +- You want users to log in with a phone number ONLY (i.e. passwordless login) -We'll cover: +What you'll need: + +- Twilio account ([sign up](https://www.twilio.com/try-twilio)) +- Supabase project (create one [here](https://supabase.com/dashboard)) +- Mobile phone capable of receiving SMS + +SMS Authentication can be done with either Twilio Verify or Twilio Programmable Messaging. [Twilio Verify](https://www.twilio.com/en-us/trusted-activation/verify) is a specialized OTP solution and is recommended for most developers that need over-the-phone authentication. Alternatively you can use [Twilio Programmable Messaging](https://www.twilio.com/docs/messaging) which offers generic SMS sending support. + +## Twilio Verify + +To set up Twilio Verify, you will need to: + +1. Create a new [verification service](https://support.twilio.com/hc/en-us/articles/360033309133-Getting-Started-with-Twilio-Verify-V2) in the Twilio dashboard. +2. [Switch Phone Provider to Twilio Verify](https://supabase.com/dashboard/project/_/auth/providers) +3. Configure the Twilio Verify Service ID field using the Verification Service ID obtained in 1. + +When using Twilio Verify, OTPs are generated by Twilio. This means that: + +- Unlike other providers, the OTP expiry duration and message content fields are not configurable via the Supabase dashboard. Please head to Twilio Verify to configure these settings. +- The token remains the same during its validity period until the verification is successful. This means if your user make another request within that period, they will receive the same token. +- Twilio Verify has a separate set of rate limits that apply. Visit Twilio's [Rate Limit and Timeouts page](https://www.twilio.com/docs/verify/api/rate-limits-and-timeouts) to find out more. + + + At this time, Twilio Verify is only supported on the `whatsapp` and `sms` channels. + + +## Twilio (Programmable Messaging) + +In this section we'll cover: - [Finding your Twilio credentials](#finding-your-twilio-credentials) - [Using OTP with password based logins](#using-otp-with-password-based-logins) @@ -349,6 +375,8 @@ let { data, error } = await supabase.auth.verifyOtp({ ## Resources +- [Reasons to use Twilio Verify](https://www.twilio.com/blog/9-reasons-to-use-the-verify-api) +- [Twilio Verify Documentation](https://www.twilio.com/docs/verify) - [Twilio Signup](https://www.twilio.com/try-twilio) - [Supabase Dashboard](https://supabase.com/dashboard) - [Supabase Row Level Security](/docs/guides/auth#row-level-security) diff --git a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx index d0dd5855c73..f46284137b6 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx @@ -33,7 +33,7 @@ Setting up Facebook logins for your application consists of 3 parts: -## Set up FaceBook Login for your Facebook App +## Set up Facebook Login for your Facebook App From the `Add Products to your App` screen: 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/pages/guides/platform/going-into-prod.mdx b/apps/docs/pages/guides/platform/going-into-prod.mdx index c3ac2663e8c..6e8bd43de81 100644 --- a/apps/docs/pages/guides/platform/going-into-prod.mdx +++ b/apps/docs/pages/guides/platform/going-into-prod.mdx @@ -21,7 +21,7 @@ After developing your project and deciding it's Production Ready, you should run - Go to the Authentication > Policies page in the Supabase Dashboard to enable RLS and create security policies. - Go to the Database > Replication page in the Supabase Dashboard to manage replication tables. - Enable 2FA on GitHub. Since your GitHub account gives you administrative rights to your Supabase project, you should protect it with a strong password and 2FA using a U2F key or a TOTP app. -- Ensure email confirmations are enabled in the `Auth > Settings` page. +- Ensure email confirmations are enabled in the `Settings > Auth` page. - Use a custom SMTP server for auth emails so that your users can see that the mails are coming from a trusted domain (preferably the same domain that your app is hosted on). Grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc. - Think hard about how _you_ would abuse your service as an attacker, and mitigate. - Review these [common cybersecurity threats](https://auth0.com/docs/security/prevent-threats). @@ -39,9 +39,9 @@ After developing your project and deciding it's Production Ready, you should run ## Availability -- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Auth > Settings) - - you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc. - - The default rate limit for auth emails provided by Supabase is 30 new users per hour, if doing a major public announcement you will likely require more than this. +- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Settings > Auth) + - you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc. You can refer to our [SMTP guide](/docs/guides/auth/auth-smtp) for more details. + - The default rate limit for auth emails when using a custom SMTP provider is 30 new users per hour, if doing a major public announcement you will likely require more than this. - If your application is on the free plan and is **not** expected to be queried at least once every 7 days, then it may be paused by Supabase to save on server resources. - You can restore paused projects from the Supabase dashboard. - Upgrade to Pro to guarantee that your project will not be paused for inactivity. diff --git a/apps/docs/pages/guides/self-hosting/docker.mdx b/apps/docs/pages/guides/self-hosting/docker.mdx index 756081859fa..d3d326c5c11 100644 --- a/apps/docs/pages/guides/self-hosting/docker.mdx +++ b/apps/docs/pages/guides/self-hosting/docker.mdx @@ -48,14 +48,10 @@ We can use your JWT Secret to generate new `anon` and `service` API keys using t ### Update API Keys -Replace the values in these files: +Replace the values the `.env` file: -- `.env`: - `ANON_KEY` - replace with an `anon` key - `SERVICE_ROLE_KEY` - replace with a `service` key -- `volumes/api/kong.yml` - - `anon` - replace with an `anon` key - - `service_role` - replace with a `service` key ### Update Secrets diff --git a/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx b/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx index a9531106ed8..85d22733009 100644 --- a/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx +++ b/apps/docs/pages/learn/auth-deep-dive/auth-gotrue.mdx @@ -40,7 +40,7 @@ curl -X POST 'https://.supabase.co/auth/v1/magiclink' \ }' ``` -Gotrue is responsible for issuing access tokens for your users, sends confirmation, magic-link, and password recovery emails (by default we send these from a Supabase SMTP server, but you can easily plug in your own inside the dashboard at Auth > Settings) and also transacting with third party OAuth providers to get basic user data. +Gotrue is responsible for issuing access tokens for your users, sends confirmation, magic-link, and password recovery emails (by default we send these from a Supabase SMTP server, but you can easily plug in your own inside the dashboard at Project Settings > Auth) and also transacting with third party OAuth providers to get basic user data. The community even recently built in the functionality to request custom OAuth scopes, if your users need to interact more closely with the provider. See the scopes parameter here: [https://github.com/supabase/gotrue#get-authorize](https://github.com/supabase/gotrue#get-authorize). 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/auth-smtp/smtp.png b/apps/docs/public/img/guides/auth-smtp/smtp.png new file mode 100644 index 00000000000..b111b866089 Binary files /dev/null and b/apps/docs/public/img/guides/auth-smtp/smtp.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 + + + )}
{featuredPartners.length > 0 ? ( -