diff --git a/apps/docs/pages/guides/auth.mdx b/apps/docs/pages/guides/auth.mdx index 75e514e5b7e..71986837e96 100644 --- a/apps/docs/pages/guides/auth.mdx +++ b/apps/docs/pages/guides/auth.mdx @@ -55,31 +55,43 @@ You can enable third-party providers with the click of a button by navigating to When using third-party providers, the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or [add additional redirect URLs](https://app.supabase.com/project/_/auth/settings). -You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile). +You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile). Use [this tool](https://www.digitalocean.com/community/tools/glob?comments=true&glob=http%3A%2F%2Flocalhost%3A3000%2F%2A%2A&matches=false&tests=http%3A%2F%2Flocalhost%3A3000&tests=http%3A%2F%2Flocalhost%3A3000%2F&tests=http%3A%2F%2Flocalhost%3A3000%2F%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest-test%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest%2Ftest%3Ftest%3Dtest) to test your patterns. + +> ⚠️ WARNING: While the "globstar" (`**`) is useful for local development and preview URLs, we recommend setting the exact redirect URL path for your site URL in production. #### Netlify preview URLs For deployments with Netlify, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews: -- `http://localhost:3000/*/*` -- `https://**--my_org.netlify.app/*` +- `http://localhost:3000/**` +- `https://**--my_org.netlify.app/**` #### Vercel preview URLs For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews: -- `http://localhost:3000/*/*` -- `https://**vercel.app/*/*` +- `http://localhost:3000/**` +- `https://**.vercel.app/**` Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment: ```js +const getURL = () => { + let url = + process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env. + process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel. + 'http://localhost:3000/'; + // Make sure to include `https://` when not localhost. + url = url.includes('http') ? url : `https://${url}`; + // Make sure to including trailing `/`. + url = url.charAt(url.length - 1) === '/' ? url : `${url}/`; + return url; +}; + const { data, error } = await supabase.auth.signInWithOAuth({ provider: 'github' options: { - redirectTo: process.env.NEXT_PUBLIC_VERCEL_URL - ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}` - : "http://localhost:3000" + redirectTo: getURL() } } ``` diff --git a/apps/docs/pages/guides/database.mdx b/apps/docs/pages/guides/database.mdx index d557ac7a79e..7efa2ad7513 100644 --- a/apps/docs/pages/guides/database.mdx +++ b/apps/docs/pages/guides/database.mdx @@ -85,7 +85,7 @@ Read about resetting your database password [here](/docs/guides/database/managin ## Next steps -- Read more about [Postgres](/docs/postgres/server/about) +- Read more about [Postgres](https://www.postgresql.org/about/) - Sign in: [app.supabase.com](https://app.supabase.com) export const Page = ({ children }) => diff --git a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx index c76f1e18ffd..7183c9fb539 100644 --- a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx +++ b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx @@ -52,7 +52,7 @@ flutter_bloc: ^8.0.0 Since the app has evolved, we also need to update our table schema. In order to store rooms data, we will add a rooms table. We will also modify the messages table to add a foreign key constraint to the rooms table so that we can tell which message belongs to which room. -We will also intorudce a `create_new_room` function, which is a [database function](https://supabase.com/docs/guides/database/functions) that handles chat room creation. It knows to create a new room if a chat room with the two users does not exist yet, or to just return the room ID if it already exists. +We will also introduce a `create_new_room` function, which is a [database function](https://supabase.com/docs/guides/database/functions) that handles chat room creation. It knows to create a new room if a chat room with the two users does not exist yet, or to just return the room ID if it already exists. ```sql -- *** Table definitions *** @@ -1154,7 +1154,7 @@ import 'package:my_chat_app/pages/rooms_page.dart'; import 'package:my_chat_app/utils/constants.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; -/// Page to redirect users to the appropreate page depending on the initial auth state +/// Page to redirect users to the appropriate page depending on the initial auth state class SplashPage extends StatefulWidget { const SplashPage({Key? key}) : super(key: key); diff --git a/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx new file mode 100644 index 00000000000..632c731bfc4 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react' +import { PageState, ConfDataContext, UserData } from './hooks/use-conf-data' +import Ticket from './ticket' +import ConfContainer from './conf-container' +import Form from './form' +import { SupabaseClient, Session } from '@supabase/supabase-js' + +type Props = { + supabase: SupabaseClient + session: Session | null + defaultUserData: UserData + sharePage?: boolean + defaultPageState?: PageState +} + +export default function Conf({ + supabase, + session, + defaultUserData, + sharePage, + defaultPageState = 'registration', +}: Props) { + const [userData, setUserData] = useState(defaultUserData) + const [pageState, setPageState] = useState(defaultPageState) + + return ( + + + {pageState === 'registration' && !sharePage ? ( + <> + {/* */} +
+ {/* */} + + ) : ( + + )} + + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.module.css b/apps/www/components/LaunchWeek/Ticket/conf-container.module.css new file mode 100644 index 00000000000..2a7eac88ac0 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/conf-container.module.css @@ -0,0 +1,11 @@ +.container { + margin: auto 0; + padding: 0 var(--space-4x); +} + +@media (min-width: 768px) { + .container { + margin: auto; + padding: 0 var(--space-8x); + } +} diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.tsx b/apps/www/components/LaunchWeek/Ticket/conf-container.tsx new file mode 100644 index 00000000000..e331c3e882c --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/conf-container.tsx @@ -0,0 +1,5 @@ +import styles from './conf-container.module.css' + +export default function ConfContainer({ children }: { children: React.ReactNode }) { + return
{children}
+} diff --git a/apps/www/components/LaunchWeek/Ticket/form.module.css b/apps/www/components/LaunchWeek/Ticket/form.module.css new file mode 100644 index 00000000000..fa1a4736cc3 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/form.module.css @@ -0,0 +1,246 @@ +.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; + margin: 0 auto; +} +.formInfoCenter { + text-align: center; + margin: 0 auto; +} + +@media (min-width: 1200px) { + .formInfoLeft { + text-align: left; + margin: inherit; + } + .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/Ticket/form.tsx b/apps/www/components/LaunchWeek/Ticket/form.tsx new file mode 100644 index 00000000000..6cbe2532aee --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/form.tsx @@ -0,0 +1,233 @@ +import { useState, useCallback, useEffect } from 'react' +import cn from 'classnames' +import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data' +import { useRouter } from 'next/router' +import LoadingDots from './loading-dots' +import styleUtils from './utils.module.css' +import styles from './form.module.css' +import useEmailQueryParam from '~/components/LaunchWeek/Ticket/hooks/use-email-query-param' +// import Captcha, { useCaptcha } from './captcha' + +type FormState = 'default' | 'loading' | 'error' + +export type ConfUser = { + id?: string + email?: string + ticketNumber?: number + name?: string + username?: string + createdAt?: number + golden?: boolean +} + +type Props = { + sharePage?: boolean + align?: 'Left' | 'Center' +} + +export default function Form({ sharePage, align = 'Center' }: Props) { + const [email, setEmail] = useState('') + const [errorMsg, setErrorMsg] = useState('') + const [errorTryAgain, setErrorTryAgain] = useState(false) + const [focused, setFocused] = useState(false) + const [formState, setFormState] = useState('default') + const { setPageState, setUserData, session, userData, supabase } = useConfData() + const router = useRouter() + const isCaptchaEnabled = false + + useEffect(() => { + if (session?.user) { + document.body.classList.add('ticket-generated') + const username = session.user.user_metadata.user_name + const name = session.user.user_metadata.full_name + const email = session.user.email + supabase + .from('lw6_tickets') + .upsert({ email, name, username }, { onConflict: 'email', ignoreDuplicates: false }) + .eq('email', email) + .select() + .single() + .then(async ({ error }) => { + if (error) return supabase.auth.signOut() + const { data } = await supabase + .from('lw6_tickets_golden') + .select('*') + .eq('username', username) + .single() + 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') + }) + } + }, [session]) + + async function register(email: string, token?: string): Promise { + const { error } = await supabase!.from('lw6_tickets').insert({ email }) + if (error) { + console.log({ error }) + return { + id: 'new', + ticketNumber: 1234, + name: '', + username: '', + golden: false, + } + } + const { data } = await supabase!.from('lw6_tickets_golden').select('*').limit(1).single() + return { + id: data?.id ?? 'new', + ticketNumber: data?.ticketNumber ?? 1234, + name: data?.name ?? '', + username: data?.username ?? '', + golden: data?.golden ?? false, + } + } + + const handleRegister = useCallback( + (token?: string) => { + register(email, token) + .then(async (params) => { + if (!params) { + throw new Error() + } + + if (sharePage) { + const queryString = Object.keys(params) + .map( + (key) => + `${encodeURIComponent(key)}=${encodeURIComponent( + params[key as keyof typeof params] || '' + )}` + ) + .join('&') + await router.replace(`/launch-week/tickets?${queryString}`, '/launch-week/tickets') + } else { + setUserData(params) + setPageState('ticket') + } + }) + .catch(async (err) => { + let message = 'Error! Please try again.' + + setErrorMsg(message) + setFormState('error') + }) + }, + [email, router, setPageState, setUserData, sharePage] + ) + + const onSubmit = useCallback( + (e: React.FormEvent) => { + e.preventDefault() + + if (formState === 'default') { + setFormState('loading') + + if (isCaptchaEnabled) { + // return executeCaptcha() + } + + return handleRegister() + } else { + setFormState('default') + } + }, + [formState, isCaptchaEnabled, handleRegister] + ) + + const onTryAgainClick = useCallback((e: React.MouseEvent) => { + e.preventDefault() + + setFormState('default') + setErrorTryAgain(true) + // resetCaptcha() + }, []) + + useEmailQueryParam('email', setEmail) + + return ( + <> +
+

Get a ticket

+

+ A few of the lucky attendees will get a limited edition Supabase goodie bag. Make sure you + don’t skip your chance. +

+
+ {formState === 'error' ? ( +
+
+
+
{errorMsg}
+ +
+
+
+ ) : ( + +
+ + +
+ {/* */} + + )} + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts new file mode 100644 index 00000000000..4855f4f9a56 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts @@ -0,0 +1,30 @@ +import { Session, SupabaseClient } from '@supabase/supabase-js' +import { createContext, useContext } from 'react' + +export type PageState = 'registration' | 'ticket' + +export type UserData = { + id?: string + ticketNumber?: number + username?: string + name?: string + golden?: boolean +} + +type ConfDataContextType = { + supabase: SupabaseClient + session: Session | null + userData: UserData + setUserData: React.Dispatch> + setPageState: React.Dispatch> +} + +export const ConfDataContext = createContext(null) + +export default function useConfData() { + const result = useContext(ConfDataContext) + if (!result) { + throw new Error() + } + return result +} diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts b/apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts new file mode 100644 index 00000000000..16304ef5f79 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts @@ -0,0 +1,32 @@ +import { useEffect } from 'react' +import { useRouter } from 'next/router' + +/** + * If `paramName` exists in query string, then call `setEmail()` with the value + * and delete it from the URL. + */ +export default function useEmailQueryParam( + paramName: string, + setEmail: (email: string) => unknown +) { + const router = useRouter() + useEffect(() => { + if ('URLSearchParams' in window) { + const { search, pathname } = window.location + const params = new URLSearchParams(search) + const email = params.get(paramName) + if (email) { + setEmail(email) + params.delete(paramName) + const newSearch = params.toString() + const newAsPath = pathname + (newSearch ? `?${newSearch}` : '') + const newPathname = router.pathname + (newSearch ? `?${newSearch}` : '') + history.replaceState( + { url: newPathname, as: newAsPath, options: { shallow: true } }, + '', + newAsPath + ) + } + } + }, [setEmail, router.pathname, paramName]) +} diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css b/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css new file mode 100644 index 00000000000..181f48448e8 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css @@ -0,0 +1,48 @@ +.loading { + display: inline-flex; + align-items: center; + --loading-dots-height: auto; + --loading-dots-size: 2px; + height: var(--loading-dots-height); +} + +.loading .spacer { + margin-right: var(--space-3x); +} + +.loading span { + animation-name: blink; + animation-duration: 1.4s; + animation-iteration-count: infinite; + animation-fill-mode: both; + width: var(--loading-dots-size); + height: var(--loading-dots-size); + border-radius: 50%; + background-color: var(--accents-6); + display: inline-block; + margin: 0 1px; +} + +.loading.reverse span { + background-color: var(--accents-2); +} + +.loading span:nth-of-type(2) { + animation-delay: 0.2s; +} + +.loading span:nth-of-type(3) { + animation-delay: 0.4s; +} + +@keyframes blink { + 0% { + opacity: 0.2; + } + 20% { + opacity: 1; + } + 100% { + opacity: 0.2; + } +} diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx b/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx new file mode 100644 index 00000000000..2d4cf1a669e --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx @@ -0,0 +1,27 @@ +import { toPixels as px } from '~/lib/helpers' +import cn from 'classnames' +import styles from './loading-dots.module.css' + +interface Props { + size?: number + height?: number | string + reverse?: boolean + children?: React.ReactNode +} + +export default function LoadingDots({ size = 2, height, children, reverse }: Props) { + return ( + + {children &&
{children}
} + + + + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css new file mode 100644 index 00000000000..d13c42418d7 --- /dev/null +++ b/apps/www/components/LaunchWeek/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/Ticket/ticket-actions.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx new file mode 100644 index 00000000000..1a86e3aed8d --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx @@ -0,0 +1,90 @@ +import { useState, useRef, useEffect } from 'react' +import cn from 'classnames' +import { SITE_URL, TWEET_TEXT, TWEET_TEXT_GOLDEN } from '~/lib/constants' +// import IconTwitter from '~/components/LaunchWeek/Ticket/icons/icon-twitter' +// import IconLinkedin from '~/components/LaunchWeek/Ticket/icons/icon-linkedin' +// import IconDownload from '~/components/LaunchWeek/Ticket/icons/icon-download' +import LoadingDots from './loading-dots' +import styleUtils from './utils.module.css' +import styles from './ticket-actions.module.css' +import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' + +type Props = { + username: string + golden?: boolean +} + +export default function TicketActions({ username, golden = false }: Props) { + const [imgReady, setImgReady] = useState(false) + const [loading, setLoading] = useState(false) + const downloadLink = useRef() + const permalink = encodeURIComponent(`${SITE_URL}/tickets/${username}`) + const text = encodeURIComponent(golden ? TWEET_TEXT_GOLDEN : TWEET_TEXT) + const { userData } = useConfData() + const tweetUrl = `https://twitter.com/intent/tweet?url=${permalink}&via=supabase&text=${text}` + const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${permalink}` + const downloadUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent( + userData.username ?? '' + )}` + + 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]) + + return ( + <> + + {/* Tweet it! */} + + + {/* Share on LinkedIn */} + + { + if (imgReady) return + + e.preventDefault() + downloadLink.current = e.currentTarget + // Wait for the image download to finish + setLoading(true) + }} + download="ticket.png" + > + {loading ? : <>{/* Download */}} + + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css new file mode 100644 index 00000000000..f9a49f205fd --- /dev/null +++ b/apps/www/components/LaunchWeek/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/Ticket/ticket-copy.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx new file mode 100644 index 00000000000..3ec81fbb729 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx @@ -0,0 +1,109 @@ +import { useEffect, useState, useRef } from 'react' +import cn from 'classnames' +import { SITE_URL } from '~/lib/constants' +import styleUtils from './utils.module.css' +// import IconCopy from '~/components/LaunchWeek/Ticket/icons/icon-copy' +import styles from './ticket-copy.module.css' + +type Props = { + username: string +} + +export default function TicketCopy({ username }: 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}` + useEffect(() => { + if (navigator.clipboard) { + setCopyEnabled(true) + } + }, []) + + const copiedText = ( + + Copied! + + ) + + const copyButton = ( + + ) + + return ( +
+
+
Your ticket URL:
+
+ {copiedText} + {copyButton} +
+
+
+ { + if (!scrolling) { + setScrolling(true) + const animationFrame = requestAnimationFrame(() => { + const scrollableWidth = + (scrollRef.current?.scrollWidth || 0) - (scrollRef.current?.clientWidth || 0) + setFadeOpacity( + (scrollableWidth - (scrollRef.current?.scrollLeft || 0)) / (scrollableWidth || 1) + ) + cancelAnimationFrame(animationFrame) + setScrolling(false) + }) + } + }} + > + {url} + + +
+ {copiedText} + {copyButton} +
+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-form.module.css new file mode 100644 index 00000000000..d10b43a0284 --- /dev/null +++ b/apps/www/components/LaunchWeek/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/Ticket/ticket-form.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx new file mode 100644 index 00000000000..1674bd7c2ba --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx @@ -0,0 +1,103 @@ +import { useState, useRef } from 'react' +// import { scrollTo } from '@lib/smooth-scroll'; +import cn from 'classnames' +// import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github' +// import CheckIcon from '~/components/LaunchWeek/Ticket/icons/icon-check' +import { SITE_ORIGIN } from '~/lib/constants' +import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data' +import LoadingDots from './loading-dots' +import formStyles from './form.module.css' +import ticketFormStyles from './ticket-form.module.css' + +type FormState = 'default' | 'loading' | 'error' +type TicketGenerationState = 'default' | 'loading' + +type Props = { + defaultUsername?: string + setTicketGenerationState: React.Dispatch> +} + +export default function Form({ defaultUsername = '', setTicketGenerationState }: Props) { + const [username] = useState(defaultUsername) + const [formState, setFormState] = useState('default') + const [errorMsg] = useState('') + const { supabase } = useConfData() + const formRef = useRef(null) + + 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/tickets` }, + }) + }} + > +
+
+ +

Only public info will be used.

+
+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css new file mode 100644 index 00000000000..8af0bcf2aa6 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css @@ -0,0 +1,17 @@ +.background { + width: 2000px; + height: 1000px; + background: #000; + color: #fff; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.page { + width: 1700px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', + 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Noto Sans', 'Noto Sans JP', + 'Noto Sans KR', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans HK', sans-serif; +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx new file mode 100644 index 00000000000..43fe8a2f68f --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx @@ -0,0 +1,37 @@ +import { useRouter } from 'next/router' +import Head from 'next/head' +import TicketVisual from './ticket-visual' +import styles from './ticket-image.module.css' + +export default function TicketImage() { + const { query } = useRouter() + if (query.ticketNumber) { + return ( +
+
+ + + + + +
+
+ ) + } + return <> +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css new file mode 100644 index 00000000000..117922b8074 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css @@ -0,0 +1,93 @@ +.info { + display: flex; + flex-direction: column; + gap: 0px; + margin-bottom: 64px; + padding: 32px; +} + +@media (min-width: 768px) { + .info { + /* display: grid; + grid-template-columns: 1fr 2fr; + gap: calc(var(--space-4x) * var(--size)); + font-size: calc(1em * var(--size)); */ + gap: calc(8px * var(--size)); + padding: 0; + margin-bottom: calc(32px * var(--size)); + margin-left: calc(160px * var(--size)); + } +} + +.logo { + /* margin-bottom: var(--space-6x); */ + /* max-width: 96px; */ + font-size: 18px; +} + +/* @media (min-width: 768px) { + .logo { + margin-bottom: 0; + font-size: calc(16px * var(--size)); + } +} */ + +.date { + /* text-transform: uppercase; */ + font-size: calc(18px * var(--size)); + + color: var(--lw-secondary-color); +} + +.date-golden { + color: var(--gold-primary); +} + +@media (min-width: 768px) { + .date { + margin-bottom: 0; + line-height: 1.15; + font-size: calc(20px * var(--size)); + } +} + +.created-by { + display: flex; + align-items: center; + color: var(--accents-4); +} + +@media (min-width: 768px) { + .created-by { + margin-right: var(--space-4x); + } +} + +.created-by-text { + white-space: nowrap; + margin-right: var(--space); +} + +/* .created-by-logo { + height: calc(16px * var(--size)); + display: inline-flex; +} */ + +.url { + margin-bottom: var(--space-12x); + font-size: calc(18px * var(--size)); + + color: var(--lw-secondary-color); +} + +.url-golden { + color: var(--gold-primary); +} + +@media (min-width: 768px) { + .url { + margin-bottom: 0; + font-size: calc(20px * var(--size)); + line-height: 1.15; + } +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx new file mode 100644 index 00000000000..0bf9bb1f9c8 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx @@ -0,0 +1,20 @@ +import styles from './ticket-info.module.css' +import cn from 'classnames' +import { DATE, SITE_URL } from '~/lib/constants' + +const siteUrl = new URL(SITE_URL) +const siteUrlForTicket = `${siteUrl.host}${siteUrl.pathname}`.replace(/\/$/, '') + +export default function TicketInfo({ + logoTextSecondaryColor = 'var(--accents-5)', + golden = false, +}) { + return ( +
+
+
{DATE}
+
+
{siteUrlForTicket}
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx new file mode 100644 index 00000000000..f55de57df16 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx @@ -0,0 +1,34 @@ +// NOTE: When you export SVG from Figma, you must: +// - Update width and height as 100% +// - Change id to "mobile-..." +// - Change url(#) to url(#mobile-) +// This is because if a page has two same IDs it will fail. +export default function TicketMonoMobile({ golden = false }: { golden?: boolean }) { + const frameColor = golden ? '#F2C94C' : '#3fcf8e' + + const perforationColor = golden ? 'var(--gold-accent)' : '#252729' + + return ( + + {/* */} + {/* */} + + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx new file mode 100644 index 00000000000..c4e5eb46b0f --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx @@ -0,0 +1,23 @@ +export default function TicketMono({ golden = false }: { golden?: boolean }) { + const frameColor = golden ? '#F2C94C' : '#252729' + + const perforationColor = golden ? 'var(--gold-accent)' : '#252729' + + return ( + + + + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx new file mode 100644 index 00000000000..f5f80bbacc6 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx @@ -0,0 +1,14 @@ +type Props = { + number: number +} + +export default function TicketNumber({ number }: Props) { + const numDigits = `${number}`.length + const prefix = `000000`.slice(numDigits) + return ( + <> + № {prefix} + {number} + + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css new file mode 100644 index 00000000000..0eda9d1fd2f --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css @@ -0,0 +1,155 @@ +.profile { + display: flex; + align-items: center; +} + +.image { + width: 60px; + height: 60px; + border-radius: 50%; + border: 1px solid #333; +} + +@media (min-width: 768px) { + .image { + width: calc(80px * var(--size)); + height: calc(80px * var(--size)); + } +} + +.text { + flex: 1; + margin-left: 10px; +} + +@media (min-width: 768px) { + .text { + margin-left: calc(15px * 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(135deg, #b4b6b8, #7f8183); + width: 60px; +} + +@media (min-width: 768px) { + .empty-icon { + 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: 5px; +} + +.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: 62px; + height: 62px; + border-radius: 50% !important; +} + +@media (min-width: 768px) { + .rounded, + .rounded.wrapper::before { + width: calc(82px * var(--size)); + height: calc(82px * var(--size)); + border-radius: 50% !important; + } +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx new file mode 100644 index 00000000000..6df6e14961d --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx @@ -0,0 +1,69 @@ +// import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github' +import cn from 'classnames' +// import IconAvatar from '~/components/LaunchWeek/Ticket/icons/icon-avatar' +import styles from './ticket-profile.module.css' + +type TicketGenerationState = 'default' | 'loading' +type Props = { + name?: string + username?: string + size?: number + ticketGenerationState: TicketGenerationState + golden?: boolean +} + +export default function TicketProfile({ + name, + username, + size = 1, + ticketGenerationState, + golden = false, +}: Props) { + return ( +
+ + {username ? ( + {username} + ) : ( + {/* */} + )} + +
+

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

+

+ + + {/* */} + + {username || <>username} + +

+
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css new file mode 100644 index 00000000000..2ee2f212a38 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css @@ -0,0 +1,173 @@ +.visual { + position: relative; + transform: translateZ(0); + display: flex; + flex-direction: column; + justify-content: space-between; + /* background-color: black; */ + border: 2px solid rgba(0, 0, 0, 0.1); + border-radius: 16px; + + background-color: rgba(0, 0, 0, 1); + -webkit-backdrop-filter: blur(5px); + backdrop-filter: blur(5px); + + /* background: linear-gradient(#fff000, var(--gold-secondary)); */ + border: 1px solid rgba(255, 255, 0, 0.629); + animation: blank-svg-shadow 2s ease-in-out infinite alternate; +} + +.visual--gold { + background-color: gold; + /* border: 10px solid; + border-image-slice: 1; + border-width: 2px; + border-radius: 16px; */ + background: linear-gradient(#fff000, var(--gold-secondary)); + border: 1px solid rgba(255, 255, 0, 0.629); + animation: svg-shadow 2s ease-in-out infinite alternate; + /* filter: drop-shadow(2.45223px 8.5828px 73.5669px #FFBD14); */ + /* border-image-source: linear-gradient(to left, var(--gold-secondary), var(--gold-accent)); */ +} + +/* .svg-shadow { +} */ + +@keyframes svg-shadow { + from { + filter: drop-shadow(0 0 5px #fff) drop-shadow(0 0 15px var(--gold-primary)) + drop-shadow(0 0 20px var(--gold-accent)); + } + + to { + filter: drop-shadow(0 0 20px #fff) drop-shadow(0 0 10px var(--gold-accent)) + drop-shadow(0 0 40px var(--gold-primary)); + } +} + +@keyframes blank-svg-shadow { + from { + filter: drop-shadow(0 0 5px #fff) drop-shadow(0 0 15px #12f28d) drop-shadow(0 0 20px #12f28d); + } + + to { + filter: drop-shadow(0 0 20px #fff) drop-shadow(0 0 10px #12f28d) drop-shadow(0 0 40px #12f28d); + } +} + +.horizontal-ticket { + display: none; +} + +.vertical-ticket { + display: flex; +} + +@media (min-width: 768px) { + .horizontal-ticket { + display: flex; + } + + .vertical-ticket { + display: none; + } +} + +.logo { + z-index: 1; + position: absolute; + top: 0; + width: 100%; + height: 100%; + /* padding: 46px 29px;/ */ +} + +.logo img { + /* margin-top: 64px; */ + margin-top: calc(64px * var(--size)); + margin-left: calc(32px * var(--size)); + margin-left: calc(32px * var(--size)); + /* width: 272px; */ + width: calc(272px * var(--size)); +} + +@media (min-width: 768px) { + .logo img { + margin-top: calc(32px * var(--size)); + /* width: 280px; */ + width: calc(280px * var(--size)); + margin-left: calc(160px * var(--size)); + } +} + +.profile { + z-index: 1; + position: absolute; + top: 90px; + width: 100%; + height: 100%; + padding: 46px 29px; +} + +@media (min-width: 768px) { + .profile { + top: calc(48px * var(--size)); + padding: calc(44px * var(--size)) calc(160px * var(--size)) calc(44px * var(--size)) + calc(63px * var(--size)); + } +} + +.info { + z-index: 1; + position: absolute; + bottom: 0; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + /* padding: calc(46px * var(--size)) calc(29px * var(--size)); */ +} + +@media (min-width: 768px) { + .info { + bottom: 0; + /* padding: 0 calc(160px * var(--size)) calc(44px * var(--size)) + calc(63px * var(--size)); */ + } +} + +.ticket-number-wrapper { + position: absolute; + bottom: 40px; + right: 0; +} + +@media (min-width: 768px) { + .ticket-number-wrapper { + bottom: 0; + } +} + +.ticket-number { + font-weight: bold; + font-size: calc(40px * var(--size)); + line-height: 1; + width: calc(330px * var(--size)); + text-align: center; +} + +.ticket-number-golden { + color: var(--gold-primary); +} + +@media (min-width: 768px) { + .ticket-number { + transform: rotate(90deg) translateY(calc(100px * var(--size))); + transform-origin: bottom right; + } + + .ticket-number-golden { + color: var(--gold-accent); + } +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx new file mode 100644 index 00000000000..bec9aceced7 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx @@ -0,0 +1,83 @@ +import styles from './ticket-visual.module.css' +import TicketProfile from './ticket-profile' +import TicketNumber from './ticket-number' +import TicketMono from './ticket-mono' +import TicketInfo from './ticket-info' +import TicketMonoMobile from './ticket-mono-mobile' +import cn from 'classnames' +import { useRouter } from 'next/router' + +type TicketGenerationState = 'default' | 'loading' +type Props = { + size?: number + name?: string + ticketNumber?: number + username?: string + ticketGenerationState?: TicketGenerationState + golden?: boolean +} + +export default function TicketVisual({ + size = 1, + name, + username, + ticketNumber, + ticketGenerationState = 'default', + golden = false, +}: Props) { + // golden = true; + + const router = useRouter() + const basePath = router.basePath + return ( + <> +
+
+ {/* {username ? : } */} + + +
+
+ {/* {username ? ( + + ) : ( + + )} */} + +
+
+ +
+
+ +
+
+ +
+ {ticketNumber && ( +
+
+ +
+
+ )} +
+ + ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.module.css b/apps/www/components/LaunchWeek/Ticket/ticket.module.css new file mode 100644 index 00000000000..4101fabfef0 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket.module.css @@ -0,0 +1,161 @@ +.hero { + font-size: 30px; + line-height: 1.15; + letter-spacing: -0.05em; + font-weight: 400; + margin: 0; + text-align: center; +} + +@media (min-width: 768px) { + .hero { + font-size: 30px; + line-height: 1; + /* font-weight: 800; */ + /* max-width: 800px; */ + /* margin-left: auto; + margin-right: auto; */ + } +} + +.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 { + position: relative; + border-radius: calc(16px * var(--size)); + /* overflow: hidden; */ + width: 330px; + margin: 0 auto; +} + +/* .ticket-visual:before, +.ticket-visual:after { */ +/* content: ''; + display: block; + width: 72px; + height: 58px; */ +/* background-color: #1C1C1C; */ +/* position: absolute; + z-index: 100; + border-radius: 50%; */ +/* } */ + +.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 { + width: 650px; + } + + .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: 30px; + 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 { + font-size: 48px; + text-align: left; + } + .description { + text-align: left; + margin: 30px 0; + } +} + +.ticket-visual-wrapper { + border-radius: calc(16px * var(--size)); + width: 100%; +} + +@media screen and (min-width: 1280px) { + .ticket-layout { + padding: 0 40px; + } +} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.tsx b/apps/www/components/LaunchWeek/Ticket/ticket.tsx new file mode 100644 index 00000000000..3af3b7eb048 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/ticket.tsx @@ -0,0 +1,132 @@ +import cn from 'classnames' +import Tilt from 'vanilla-tilt' +import { useRef, useEffect, useState } from 'react' +// import { TicketGenerationState } from '@lib/constants' +// import {isMobileOrTablet} from '~/lib/helpers' +// import { scrollTo } from '@lib/smooth-scroll' +import styles from './ticket.module.css' +import styleUtils from './utils.module.css' +import TicketForm from './ticket-form' +import TicketVisual from './ticket-visual' +import TicketActions from './ticket-actions' +import TicketCopy from './ticket-copy' +import { DATE, SITE_NAME } from '~/lib/constants' +import Form from './form' +import { UserData } from './hooks/use-conf-data' + +type TicketGenerationState = 'default' | 'loading' +type Props = { + username: UserData['username'] + ticketNumber: UserData['ticketNumber'] + name: UserData['name'] + golden: UserData['golden'] + sharePage?: boolean +} + +export default function Ticket({ username, name, ticketNumber, sharePage, golden }: Props) { + const ticketRef = useRef(null) + const [ticketGenerationState, setTicketGenerationState] = + useState('default') + const divRef = useRef(null) + + useEffect(() => { + if (ticketRef.current && !window.matchMedia('(pointer: coarse)').matches) { + Tilt.init(ticketRef.current, { + glare: true, + max: 5, + 'max-glare': 0.16, + 'full-page-listening': true, + }) + } + }, [ticketRef]) + + // useEffect(() => { + // if (!sharePage && divRef && divRef.current && isMobileOrTablet()) { + // scrollTo(divRef.current, -30) + // } + // }, [divRef, sharePage]) + + return ( +
+
+
+

+ {sharePage ? ( + name ? ( + <>{name}’s Ticket + ) : ( + <>{SITE_NAME} + ) + ) : golden ? ( + <> + You won a golden ticket!
Claim it now! + + ) : ( + <> + You're in.
Make it unique. + + )} +

+

+ {sharePage ? ( + <> + Join {name ?? 'us'} on {DATE}. + + ) : golden ? ( + <>Claim your ticket with GitHub and Tweet it to redeem your swag pack! + ) : ( + <> + Generate a unique ticket image with
+ your GitHub profile. + + )} +

+
+
+ {!sharePage ? ( + + ) : ( +
+ )} +
+
+
+
+ +
+ {!sharePage && ( + <> + {username ? ( +
+
+ +
+
+ +
+
+ ) : ( +
+ )} + + )} +
+
+ ) +} diff --git a/apps/www/components/LaunchWeek/Ticket/utils.module.css b/apps/www/components/LaunchWeek/Ticket/utils.module.css new file mode 100644 index 00000000000..c4aba2ab686 --- /dev/null +++ b/apps/www/components/LaunchWeek/Ticket/utils.module.css @@ -0,0 +1,76 @@ +.appear { + opacity: 0; + animation: appear 0.8s cubic-bezier(0.1, 0, 0.175, 1) forwards; +} + +.appear-first { + animation-delay: 0.4s; +} + +.appear-second { + animation-delay: 0.8s; +} + +.appear-third { + animation-delay: 1.2s; +} + +.appear-fourth { + animation-delay: 1.6s; +} + +.appear-fifth { + animation-delay: 2s; +} + +.appear-sixth { + animation-delay: 2.4s; +} + +@keyframes appear { + to { + opacity: 1; + } +} + +@media (min-width: 1200px) { + .hide-on-desktop { + display: none; + } +} + +.show-on-mobile { + display: block; +} + +.hide-on-mobile { + display: none; +} + +@media (min-width: 768px) { + .show-on-mobile { + display: none; + } + + .hide-on-mobile { + display: block; + } +} + +.hide-on-tablet { + display: block; +} + +.show-on-tablet { + display: none; +} + +@media (max-width: 1199px) and (min-width: 768px) { + .show-on-tablet { + display: block; + } + + .hide-on-tablet { + display: none; + } +} diff --git a/apps/www/lib/constants.ts b/apps/www/lib/constants.ts index e72ab554946..f6c16abf7f4 100644 --- a/apps/www/lib/constants.ts +++ b/apps/www/lib/constants.ts @@ -1,2 +1,15 @@ export const APP_NAME = 'Supabase' export const DESCRIPTION = 'The Open Source Alternative to Firebase.' + +// Launch Weeek +export const SAMPLE_TICKET_NUMBER = 1234 +export const SITE_URL = process.env.NEXT_PUBLIC_VERCEL_URL + ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}/launch-week` + : 'http://localhost:3000/launch-week' +export const SITE_ORIGIN = new URL(SITE_URL).origin +export const TWITTER_USER_NAME = 'supabase' +export const COOKIE = 'supa-launch-week-ticke-user-id' +export const DATE = 'December 12 8:00 PT | 11:00 ET' +export const TWEET_TEXT = 'Got my ticket for #SupaLaunchWeek 6!' +export const TWEET_TEXT_GOLDEN = `Woop! I got a Golden Ticket for #SupaLaunchWeek 6! SupaSwag on its way!` +export const SITE_NAME = 'Supabase' diff --git a/apps/www/lib/helpers.tsx b/apps/www/lib/helpers.tsx index 33b8fb8ed3f..2d9722c10f1 100644 --- a/apps/www/lib/helpers.tsx +++ b/apps/www/lib/helpers.tsx @@ -11,3 +11,21 @@ export const generateReadingTime = (text: string) => { export function capitalize(word: string) { return word[0].toUpperCase() + word.substring(1).toLowerCase() } + +export function isMobileOrTablet() { + // https://stackoverflow.com/a/8876069/114157 + const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0) + return viewportWidth < 1200 +} + +// Convert numbers or strings to pixel value +// Helpful for styled-jsx when using a prop +// height: ${toPixels(height)}; (supports height={20} and height="20px") + +export const toPixels = (value: string | number) => { + if (typeof value === 'number') { + return `${value}px` + } + + return value +} diff --git a/apps/www/lib/rewrites.js b/apps/www/lib/rewrites.js index b5f9efbea19..1fd564e3039 100644 --- a/apps/www/lib/rewrites.js +++ b/apps/www/lib/rewrites.js @@ -17,20 +17,6 @@ module.exports = [ source: '/docs/:path*', destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/:path*`, }, - // rewrite to keep existing ticket urls working - { - source: '/launch-week/tickets/:path', - destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}/tickets/:path`, - }, - // rewrite to move ticket website to another path - { - source: '/launch-week-register', - destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}`, - }, - { - source: '/launch-week-register/:path*', - destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}/:path*`, - }, { source: '/new-docs', destination: `${process.env.NEXT_PUBLIC_REFERENCE_DOCS_URL}`, diff --git a/apps/www/package.json b/apps/www/package.json index ad8d7654ba9..b8808779fdf 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -29,7 +29,7 @@ "gray-matter": "^4.0.2", "markdown-toc": "^1.2.0", "marked": "^4.0.12", - "next": "latest", + "next": "12.1.0", "next-compose-plugins": "^2.2.1", "next-mdx-remote": "^4.1.0", "next-seo": "^4.20.0", @@ -55,6 +55,7 @@ "ui": "*", "use-debounce": "^7.0.1", "uuid": "^8.3.2", + "vanilla-tilt": "1.7.0", "yup": "^0.32.11" }, "devDependencies": { diff --git a/apps/www/pages/launch-week/ticket-image.tsx b/apps/www/pages/launch-week/ticket-image.tsx new file mode 100644 index 00000000000..9fa19e85f7f --- /dev/null +++ b/apps/www/pages/launch-week/ticket-image.tsx @@ -0,0 +1,5 @@ +import TicketImage from '~/components/LaunchWeek/Ticket/ticket-image' + +export default function TicketOnlyPage() { + return +} diff --git a/apps/www/pages/launch-week/tickets/[username].tsx b/apps/www/pages/launch-week/tickets/[username].tsx new file mode 100644 index 00000000000..97ac38e47ee --- /dev/null +++ b/apps/www/pages/launch-week/tickets/[username].tsx @@ -0,0 +1,114 @@ +import { NextSeo } from 'next-seo' +import { GetStaticProps, GetStaticPaths } from 'next' +import Error from 'next/error' +import DefaultLayout from '~/components/Layouts/Default' +import SectionContainer from '~/components/Layouts/SectionContainer' +import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer' +import { SITE_URL, SAMPLE_TICKET_NUMBER } from '~/lib/constants' +import { createClient } from '@supabase/supabase-js' +import { useState } from 'react' +import { FastGlobOptionsWithoutCwd } from 'globby' + +type Props = { + username: string | null + name: string | null + ticketNumber: number | null + golden: boolean +} + +const supabaseAdmin = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SUPABASE_SERVICE_ROLE_SECRET! +) + +export default function TicketShare({ username, ticketNumber, name, golden }: Props) { + const [supabase] = useState(() => + createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) + ) + const description = 'Supabase Launch Week 6 | 12-16 Dec 2022' + + if (!ticketNumber) { + return + } + + const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent( + username ?? '' + )}` + + return ( + <> + +
+ + + + + + + + + ) +} + +export const getStaticProps: GetStaticProps = async ({ params }) => { + const username = params?.username?.toString() || null + let name: string | null | undefined + let ticketNumber: number | null | undefined + let golden = false + + if (username) { + const { data: user } = await supabaseAdmin! + .from('lw6_tickets_golden') + .select('name, ticketNumber, golden') + .eq('username', username) + .single() + name = user?.name + ticketNumber = user?.ticketNumber + golden = user?.golden ?? false + } + return { + props: { + username: ticketNumber ? username : null, + usernameFromParams: username || null, + name: ticketNumber ? name || username || null : null, + ticketNumber: ticketNumber || SAMPLE_TICKET_NUMBER, + golden, + }, + revalidate: 5, + } +} + +export const getStaticPaths: GetStaticPaths = async () => { + return Promise.resolve({ + paths: [], + fallback: 'blocking', + }) +} diff --git a/apps/www/pages/launch-week/tickets/index.tsx b/apps/www/pages/launch-week/tickets/index.tsx new file mode 100644 index 00000000000..56c1a368a47 --- /dev/null +++ b/apps/www/pages/launch-week/tickets/index.tsx @@ -0,0 +1,67 @@ +import { NextSeo } from 'next-seo' +import DefaultLayout from '~/components/Layouts/Default' +import SectionContainer from '~/components/Layouts/SectionContainer' +import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer' +import { SITE_URL } from '~/lib/constants' +import { useRouter } from 'next/router' +import { createClient, Session } from '@supabase/supabase-js' +import { useState, useEffect } from 'react' + +export default function TicketHome() { + const [supabase] = useState(() => + createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) + ) + const [session, setSession] = useState(null) + const description = 'Supabase Launch Week 6 | 12-16 Dec 2022' + const { query } = useRouter() + const ticketNumber = query.ticketNumber?.toString() + const defaultUserData = { + id: query.id?.toString(), + ticketNumber: ticketNumber ? parseInt(ticketNumber, 10) : undefined, + name: query.name?.toString(), + username: query.username?.toString(), + } + + useEffect(() => { + supabase.auth.onAuthStateChange((event, session) => { + setSession(session) + }) + }, []) + + return ( + <> + +
+ + + + + + + + + ) +} diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml index 34781c63723..4a306486453 100644 --- a/apps/www/public/rss.xml +++ b/apps/www/public/rss.xml @@ -5,924 +5,931 @@ https://supabase.com Latest news from Supabase en - Wed, 16 Nov 2022 03:03:30 +0000 + Mon, 21 Nov 2022 16:04:00 +0000 + https://supabase.com/blog/flutter-authentication-and-authorization-with-rls + Flutter Authentication and Authorization with RLS + https://supabase.com/blog/flutter-authentication-and-authorization-with-rls + Learn how you can secure your Flutter app using Supabase Row Level Security. + Mon, 21 Nov 2022 16:04:00 +0000 + + https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components Fetching and caching Supabase data in Next.js 13 Server Components https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense. - Wed, 16 Nov 2022 03:03:30 +0000 + Wed, 16 Nov 2022 16:04:00 +0000 https://supabase.com/blog/authentication-in-ionic-angular Authentication in Ionic Angular with Supabase https://supabase.com/blog/authentication-in-ionic-angular Learn how to build an Ionic Angular app with authentication, Row Level Security, and Magic Link auth. - Mon, 07 Nov 2022 03:03:30 +0000 + Mon, 07 Nov 2022 16:04:00 +0000 https://supabase.com/blog/supabase-beta-update-october-2022 Supabase Beta October 2022 https://supabase.com/blog/supabase-beta-update-october-2022 New SDKs, quickstarts, Functions tricks, and more. But, more importantly, Launch Week 6️ has a date! - Tue, 01 Nov 2022 02:02:30 +0000 + Tue, 01 Nov 2022 16:04:00 +0000 https://supabase.com/blog/postgresql-commitfest What is PostgreSQL commitfest and how to contribute https://supabase.com/blog/postgresql-commitfest A time-tested method for contributing to the core Postgres code - Wed, 26 Oct 2022 02:02:30 +0000 + Wed, 26 Oct 2022 16:04:00 +0000 https://supabase.com/blog/supabase-flutter-sdk-v1-released supabase-flutter v1 Released https://supabase.com/blog/supabase-flutter-sdk-v1-released We've released supabase-flutter v1. More intuitive way of accessing Supabase from your Flutter application. - Thu, 20 Oct 2022 02:02:30 +0000 + Thu, 20 Oct 2022 16:04:00 +0000 https://supabase.com/blog/supabase-js-v2-released supabase-js v2 Released https://supabase.com/blog/supabase-js-v2-released We've released supabase-js v2. Updated examples, quickstarts, and an improved experience. - Wed, 19 Oct 2022 02:02:30 +0000 + Wed, 19 Oct 2022 16:04:00 +0000 https://supabase.com/blog/postgres-full-text-search-vs-the-rest Postgres Full Text Search vs the rest https://supabase.com/blog/postgres-full-text-search-vs-the-rest Comparing one of the most popular Postgres features against alternatives - Thu, 13 Oct 2022 02:02:30 +0000 + Thu, 13 Oct 2022 16:04:00 +0000 https://supabase.com/blog/supabase-beta-update-september-2022 Supabase Beta September 2022 https://supabase.com/blog/supabase-beta-update-september-2022 We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?! - Tue, 04 Oct 2022 02:02:30 +0000 + Tue, 04 Oct 2022 16:04:00 +0000 https://supabase.com/blog/postgres-wasm Postgres WASM by Snaplet and Supabase https://supabase.com/blog/postgres-wasm We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet. - Sun, 02 Oct 2022 02:02:30 +0000 + Sun, 02 Oct 2022 16:04:00 +0000 https://supabase.com/blog/choosing-a-postgres-primary-key Choosing a Postgres Primary Key https://supabase.com/blog/choosing-a-postgres-primary-key Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out - Wed, 07 Sep 2022 02:02:30 +0000 + Wed, 07 Sep 2022 16:04:00 +0000 https://supabase.com/blog/supabase-beta-update-august-2022 Supabase Beta August 2022 https://supabase.com/blog/supabase-beta-update-august-2022 Launch Week Special. See everything we shipped, plus winners of the Hackathon and the extended Community Highlights - Tue, 06 Sep 2022 02:02:30 +0000 + Tue, 06 Sep 2022 16:04:00 +0000 https://supabase.com/blog/launch-week-5-hackathon-winners Launch Week 5 Hackathon Winners https://supabase.com/blog/launch-week-5-hackathon-winners Announcing the winners of the Launch Week 5 Hackathon! - Wed, 24 Aug 2022 02:02:30 +0000 + Wed, 24 Aug 2022 16:04:00 +0000 https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular Building a Realtime Trello Board with Supabase and Angular https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular Go beyond the hello world example with this real world project. - Tue, 23 Aug 2022 02:02:30 +0000 + Tue, 23 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-vault Supabase Vault https://supabase.com/blog/supabase-vault Today we're announcing Vault, a Postgres extension for managing secrets and encryption inside your database. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 16:04:00 +0000 https://supabase.com/blog/postgrest-v10 PostgREST v10: EXPLAIN and Improved Relationship Detection https://supabase.com/blog/postgrest-v10 Today, PostgREST 10 was released. Let's take a look at some of the new features that go hand in hand with supabase-js v2. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 16:04:00 +0000 https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation pg_jsonschema: JSON Schema support for Postgres https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation Today we're releasing pg_jsonschema, a Postgres extension for JSON validation. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 16:04:00 +0000 https://supabase.com/blog/launch-week-5-one-more-thing One more thing https://supabase.com/blog/launch-week-5-one-more-thing Let's be honest, it's never just one more thing. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 16:04:00 +0000 https://supabase.com/blog/launch-week-5-community-day Community Day https://supabase.com/blog/launch-week-5-community-day Wrapping up Launch Week 5 with contributors, partners, and friends. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-realtime-multiplayer-general-availability Realtime: Multiplayer Edition https://supabase.com/blog/supabase-realtime-multiplayer-general-availability Announcing the general availability of Realtime's Broadcast and Presence. - Wed, 17 Aug 2022 02:02:30 +0000 + Wed, 17 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-soc2 Supabase is SOC2 compliant https://supabase.com/blog/supabase-soc2 Supabase is now SOC2 compliant. Learn how we got here and what it means for our customers. - Tue, 16 Aug 2022 02:02:30 +0000 + Tue, 16 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-js-v2 supabase-js v2 https://supabase.com/blog/supabase-js-v2 A look at supabase-js v2, which brings type support and focuses on quality-of-life improvements for developers. - Mon, 15 Aug 2022 02:02:30 +0000 + Mon, 15 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta Supabase CLI v1 and Management API Beta https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta We are moving Supabase CLI v1 out of beta, and releasing Management API beta. - Sun, 14 Aug 2022 02:02:30 +0000 + Sun, 14 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-series-b Supabase Series B https://supabase.com/blog/supabase-series-b Supabase raised $80M in May, bringing our total funding to $116M. - Thu, 11 Aug 2022 02:02:30 +0000 + Thu, 11 Aug 2022 16:04:00 +0000 https://supabase.com/blog/launch-week-5-hackathon Launch Week 5 Hackathon https://supabase.com/blog/launch-week-5-hackathon Build to win $1500 - Friday 12th to Monday 21st August 2022 - Tue, 09 Aug 2022 02:02:30 +0000 + Tue, 09 Aug 2022 16:04:00 +0000 https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages Slack Consolidate: a slackbot built with Python and Supabase https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages A slackbot to consolidate messages from different channels using Supabase, Slack SDK and Python - Mon, 08 Aug 2022 02:02:30 +0000 + Mon, 08 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-beta-update-july-2022 Supabase Beta July 2022 https://supabase.com/blog/supabase-beta-update-july-2022 Launch Week Golden Tickets, Flutter SDK 1.0 Developer Preview and more... - Tue, 02 Aug 2022 02:02:30 +0000 + Tue, 02 Aug 2022 16:04:00 +0000 https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview Supabase Flutter SDK 1.0 Developer Preview https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview Supabase Flutter SDK is getting a major update and we need your help making it better. - Mon, 01 Aug 2022 02:02:30 +0000 + Mon, 01 Aug 2022 16:04:00 +0000 https://supabase.com/blog/seen-by-in-postgresql Implementing "seen by" functionality with Postgres https://supabase.com/blog/seen-by-in-postgresql Different approaches for tracking visitor counts with PostgreSQL. - Sun, 17 Jul 2022 02:02:30 +0000 + Sun, 17 Jul 2022 16:04:00 +0000 https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support Revamped Auth Helpers for Supabase (with SvelteKit support) https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support Supabase Auth Helpers now have improved developer experience, Sveltekit support, and more. - Tue, 12 Jul 2022 02:02:30 +0000 + Tue, 12 Jul 2022 16:04:00 +0000 https://supabase.com/blog/beta-update-june-2022 Supabase Beta June 2022 https://supabase.com/blog/beta-update-june-2022 Auth Helpers, Unlimited Free Projects, CLI and more... - Tue, 05 Jul 2022 02:02:30 +0000 + Tue, 05 Jul 2022 16:04:00 +0000 https://supabase.com/blog/flutter-tutorial-building-a-chat-app Flutter Tutorial: building a Flutter chat app https://supabase.com/blog/flutter-tutorial-building-a-chat-app Learn how to build a Flutter chat app with open source and scalable backend (inc. auth, realtime, database, and more). - Wed, 29 Jun 2022 02:02:30 +0000 + Wed, 29 Jun 2022 16:04:00 +0000 https://supabase.com/blog/visualizing-supabase-data-using-metabase Visualizing Supabase Data using Metabase https://supabase.com/blog/visualizing-supabase-data-using-metabase How to create different kinds of charts out of data stored in Supabase using Metabase. - Tue, 28 Jun 2022 02:02:30 +0000 + Tue, 28 Jun 2022 16:04:00 +0000 https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Partial data dumps using Postgres Row Level Security https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Using RLS to create seed files for local PostgreSQL testing. - Mon, 27 Jun 2022 02:02:30 +0000 + Mon, 27 Jun 2022 16:04:00 +0000 https://supabase.com/blog/loading-data-supabase-python Python data loading with Supabase https://supabase.com/blog/loading-data-supabase-python An example of how to load data into Supabase using supabase-py - Thu, 16 Jun 2022 02:02:30 +0000 + Thu, 16 Jun 2022 16:04:00 +0000 https://supabase.com/blog/beta-update-may-2022 Supabase Beta May 2022 https://supabase.com/blog/beta-update-may-2022 Product and community updates including wildcard auth redirects, edge functions with webhooks, and Prometheus endpoints for everybody. - Tue, 31 May 2022 02:02:30 +0000 + Tue, 31 May 2022 16:04:00 +0000 https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together How Mike Lyndon is using Supabase to accelerate development of AllPullTogether https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together Mike Lyndon is learning web development as he builds AllPullTogether, and Supabase is helping him accelerate what he can accomplish. - Wed, 25 May 2022 02:02:30 +0000 + Wed, 25 May 2022 16:04:00 +0000 https://supabase.com/blog/partner-gallery-works-with-supabase Works With Supabase - announcing our Partner Gallery https://supabase.com/blog/partner-gallery-works-with-supabase Introducing our Partner Gallery - open source and made with Supabase. - Tue, 19 Apr 2022 02:02:30 +0000 + Tue, 19 Apr 2022 16:04:00 +0000 https://supabase.com/blog/bring-the-func-hackathon-winners Bring the Func Hackathon Winners 2022 https://supabase.com/blog/bring-the-func-hackathon-winners Celebrating many amazing OSS Hackathon projects using GraphQL and Edge Functions. - Sun, 17 Apr 2022 02:02:30 +0000 + Sun, 17 Apr 2022 16:04:00 +0000 https://supabase.com/blog/beta-update-march-2022 Supabase Beta March 2022 https://supabase.com/blog/beta-update-march-2022 Functions, GraphQL, and much more. - Thu, 14 Apr 2022 02:02:30 +0000 + Thu, 14 Apr 2022 16:04:00 +0000 https://supabase.com/blog/supabrew Supabrew - Never Code Thirsty https://supabase.com/blog/supabrew A light and refreshing non-alcoholic beer for devs. - Fri, 01 Apr 2022 02:02:30 +0000 + Thu, 31 Mar 2022 16:04:00 +0000 https://supabase.com/blog/supabase-realtime-with-multiplayer-features Supabase Realtime, with Multiplayer Features https://supabase.com/blog/supabase-realtime-with-multiplayer-features Today we're announced Realtime, with multiplayer features. Realtime enables broadcast, presence, and listening to database changes delivered over WebSockets. - Fri, 01 Apr 2022 02:02:30 +0000 + Thu, 31 Mar 2022 16:04:00 +0000 https://supabase.com/blog/hackathon-bring-the-func Hackathon: Bring the Func(🕺) https://supabase.com/blog/hackathon-bring-the-func Build open-source projects with our latest features, win limited edition swag and plant a tree! - Fri, 01 Apr 2022 02:02:30 +0000 + Thu, 31 Mar 2022 16:04:00 +0000 https://supabase.com/blog/supabase-edge-functions Edge Functions are now available in Supabase https://supabase.com/blog/supabase-edge-functions Today we're launching Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located. - Wed, 30 Mar 2022 02:02:30 +0000 + Wed, 30 Mar 2022 16:04:00 +0000 https://supabase.com/blog/supabase-enterprise Introducing Supabase Enterprise https://supabase.com/blog/supabase-enterprise Today we are releasing Supabase Enterprise, a suite of features to scale your project. - Tue, 29 Mar 2022 02:02:30 +0000 + Tue, 29 Mar 2022 16:04:00 +0000 https://supabase.com/blog/graphql-now-available GraphQL is now available in Supabase https://supabase.com/blog/graphql-now-available GraphQL support is now in general availability on the Supabase platform via our open source PostgreSQL extension, pg_graphql (beta). - Mon, 28 Mar 2022 02:02:30 +0000 + Mon, 28 Mar 2022 16:04:00 +0000 https://supabase.com/blog/community-day-lw4 Community Day https://supabase.com/blog/community-day-lw4 Kicking off Launch Week 4 with contributors, partners, and friends. - Sun, 27 Mar 2022 02:02:30 +0000 + Sun, 27 Mar 2022 16:04:00 +0000 https://supabase.com/blog/supabase-launch-week-four Supabase Launch Week 4 https://supabase.com/blog/supabase-launch-week-four Launch Week 4: One new feature every day for an entire week. Starting Monday 28th March. - Thu, 24 Mar 2022 02:02:30 +0000 + Thu, 24 Mar 2022 16:04:00 +0000 https://supabase.com/blog/should-i-open-source-my-company Should I Open Source my Company? https://supabase.com/blog/should-i-open-source-my-company The unexpected upsides of building in public - Thu, 24 Mar 2022 02:02:30 +0000 + Thu, 24 Mar 2022 16:04:00 +0000 https://supabase.com/blog/audit Postgres Auditing in 150 lines of SQL https://supabase.com/blog/audit PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL. - Mon, 07 Mar 2022 03:03:30 +0000 + Mon, 07 Mar 2022 16:04:00 +0000 https://supabase.com/blog/supabase-beta-january-2022 Supabase Beta January 2022 https://supabase.com/blog/supabase-beta-january-2022 New auth providers, SMS providers, and new videos. - Mon, 21 Feb 2022 03:03:30 +0000 + Mon, 21 Feb 2022 16:04:00 +0000 https://supabase.com/blog/supabase-beta-december-2021 Supabase Beta December 2021 https://supabase.com/blog/supabase-beta-december-2021 New crypto extension, Postgres videos, and a bunch of cool integrations. - Wed, 19 Jan 2022 03:03:30 +0000 + Wed, 19 Jan 2022 16:04:00 +0000 https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 Golden Kitty Awards Ceremony Watch Party with Supabase https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 Hang out with us while watching the Product Hunt Golden Kitty Awards Ceremony - Wed, 19 Jan 2022 03:03:30 +0000 + Wed, 19 Jan 2022 16:04:00 +0000 https://supabase.com/blog/holiday-hackdays-winners-2021 Holiday Hackdays Winners 2021 https://supabase.com/blog/holiday-hackdays-winners-2021 Celebrating many amazing projects submitted to our Holiday Hackdays Hackathon. - Thu, 16 Dec 2021 03:03:30 +0000 + Thu, 16 Dec 2021 16:04:00 +0000 https://supabase.com/blog/beta-november-2021-launch-week-recap Supabase Beta November 2021: Launch Week Recap https://supabase.com/blog/beta-november-2021-launch-week-recap We wrapped up November with Supabase's third Launch Week. Here's all the awesome stuff that got shipped ... - Tue, 14 Dec 2021 03:03:30 +0000 + Tue, 14 Dec 2021 16:04:00 +0000 https://supabase.com/blog/supabase-holiday-hackdays-hackathon Kicking off the Holiday Hackdays https://supabase.com/blog/supabase-holiday-hackdays-hackathon Build cool stuff and celebrate open-source software with us during the Holiday Hackdays! - Thu, 02 Dec 2021 03:03:30 +0000 + Thu, 02 Dec 2021 16:04:00 +0000 https://supabase.com/blog/pg-graphql pg_graphql: A GraphQL extension for PostgreSQL https://supabase.com/blog/pg-graphql GraphQL support is in development for PostgreSQL + Supabase. - Thu, 02 Dec 2021 03:03:30 +0000 + Thu, 02 Dec 2021 16:04:00 +0000 https://supabase.com/blog/launch-week-three-friday-five-more-things Five more things https://supabase.com/blog/launch-week-three-friday-five-more-things It's never just one more thing! - Thu, 02 Dec 2021 03:03:30 +0000 + Thu, 02 Dec 2021 16:04:00 +0000 https://supabase.com/blog/supabase-acquires-logflare Supabase acquires Logflare https://supabase.com/blog/supabase-acquires-logflare Today, we're ecstatic to announce that Logflare is joining Supabase. - Wed, 01 Dec 2021 03:03:30 +0000 + Wed, 01 Dec 2021 16:04:00 +0000 https://supabase.com/blog/realtime-row-level-security-in-postgresql Realtime Postgres RLS now available on Supabase https://supabase.com/blog/realtime-row-level-security-in-postgresql Realtime database changes are now broadcast to authenticated users, respecting the same PostgreSQL policies that you use for Row Level Security. - Tue, 30 Nov 2021 03:03:30 +0000 + Tue, 30 Nov 2021 16:04:00 +0000 https://supabase.com/blog/supabase-studio Supabase Studio https://supabase.com/blog/supabase-studio The same Dashboard that you're using on our Platform is now available for local development and Self-Hosting. - Mon, 29 Nov 2021 03:03:30 +0000 + Mon, 29 Nov 2021 16:04:00 +0000 https://supabase.com/blog/community-day-lw3 Community Day https://supabase.com/blog/community-day-lw3 Kicking off launch week by highlighting the communities around Supabase. - Sun, 28 Nov 2021 03:03:30 +0000 + Sun, 28 Nov 2021 16:04:00 +0000 https://supabase.com/blog/whats-new-in-postgres-14 New in PostgreSQL 14: What every developer should know https://supabase.com/blog/whats-new-in-postgres-14 A quick look at some new features and functionality in PostgreSQL 14. - Sat, 27 Nov 2021 03:03:30 +0000 + Sat, 27 Nov 2021 16:04:00 +0000 https://supabase.com/blog/postgrest-9 PostgREST 9 https://supabase.com/blog/postgrest-9 New features and updates in PostgREST version 9. - Fri, 26 Nov 2021 03:03:30 +0000 + Fri, 26 Nov 2021 16:04:00 +0000 https://supabase.com/blog/supabase-launch-week-the-trilogy Supabase Launch Week III: Holiday Special https://supabase.com/blog/supabase-launch-week-the-trilogy Tis the season to be shipping. - Thu, 25 Nov 2021 03:03:30 +0000 + Thu, 25 Nov 2021 16:04:00 +0000 https://supabase.com/blog/supabase-how-we-launch How we launch at Supabase https://supabase.com/blog/supabase-how-we-launch The history and methodology of Supabase Launch Week. - Thu, 25 Nov 2021 03:03:30 +0000 + Thu, 25 Nov 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-october-2021 Supabase Beta October 2021 https://supabase.com/blog/supabase-beta-october-2021 Three new Auth providers, multi-schema support, and we're gearing up for another Launch Week. - Sat, 06 Nov 2021 02:02:30 +0000 + Sat, 06 Nov 2021 16:04:00 +0000 https://supabase.com/blog/supabase-series-a Supabase $30m Series A https://supabase.com/blog/supabase-series-a Supabase just raised $30M, bringing our total funding to $36M. - Wed, 27 Oct 2021 02:02:30 +0000 + Wed, 27 Oct 2021 16:04:00 +0000 https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours Replenysh uses Supabase to implement OTP in less than 24-hours https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours Learn how Replenysh uses Supabase to power the circular economy, redefining how brands interact with their customers and products. - Mon, 18 Oct 2021 02:02:30 +0000 + Mon, 18 Oct 2021 16:04:00 +0000 https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 Hacktoberfest Hackathon Winners 2021 https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 Celebrating many amazing projects submitted to our Hacktoberfest Hackathon. - Wed, 13 Oct 2021 02:02:30 +0000 + Wed, 13 Oct 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-sept-2021 Supabase Beta Sept 2021 https://supabase.com/blog/supabase-beta-sept-2021 Hackathon, Aborting request, UI updates, and now Hiring. - Sun, 03 Oct 2021 02:02:30 +0000 + Sun, 03 Oct 2021 16:04:00 +0000 https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 Supabase Hacktoberfest Hackathon 2021 https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 We're running another Supabase Hackathon during Hacktoberfest! - Mon, 27 Sep 2021 02:02:30 +0000 + Mon, 27 Sep 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-august-2021 Supabase Beta August 2021 https://supabase.com/blog/supabase-beta-august-2021 Fundraising, Realtime Security, custom SMS templates, and deployments in South Korea. - Thu, 09 Sep 2021 02:02:30 +0000 + Thu, 09 Sep 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-july-2021 Supabase Beta July 2021 https://supabase.com/blog/supabase-beta-july-2021 Discord Logins, Vercel Integration, Full text search, and OAuth guides. - Wed, 11 Aug 2021 02:02:30 +0000 + Wed, 11 Aug 2021 16:04:00 +0000 https://supabase.com/blog/hackathon-winners Open Source Hackathon Winners https://supabase.com/blog/hackathon-winners Let the medal ceremony begin for the best projects submitted during the Supabase Hackathon. - Sun, 08 Aug 2021 02:02:30 +0000 + Sun, 08 Aug 2021 16:04:00 +0000 https://supabase.com/blog/supabase-swag-store Supabase Swag Store https://supabase.com/blog/supabase-swag-store Today we are officially launching the Supabase Swag Store. - Thu, 29 Jul 2021 02:02:30 +0000 + Thu, 29 Jul 2021 16:04:00 +0000 https://supabase.com/blog/supabase-functions-updates Updates for Supabase Functions https://supabase.com/blog/supabase-functions-updates The question on everyone's mind - are we launching Supabase Functions? Well, it's complicated. - Thu, 29 Jul 2021 02:02:30 +0000 + Thu, 29 Jul 2021 16:04:00 +0000 https://supabase.com/blog/1-the-supabase-hackathon The Supabase Hackathon https://supabase.com/blog/1-the-supabase-hackathon A whole week of Hacking for Fun and Prizes. - Thu, 29 Jul 2021 02:02:30 +0000 + Thu, 29 Jul 2021 16:04:00 +0000 https://supabase.com/blog/supabase-reports-and-metrics Supabase Reports and Metrics https://supabase.com/blog/supabase-reports-and-metrics We're exposing a full set of metrics in your projects, so that you can build better (and faster) products for your users. - Wed, 28 Jul 2021 02:02:30 +0000 + Wed, 28 Jul 2021 16:04:00 +0000 https://supabase.com/blog/supabase-auth-passwordless-sms-login Supabase Auth v2: Phone Auth now available https://supabase.com/blog/supabase-auth-passwordless-sms-login Phone Auth is available today on all new and existing Supabase projects. - Tue, 27 Jul 2021 02:02:30 +0000 + Tue, 27 Jul 2021 16:04:00 +0000 https://supabase.com/blog/mobbin-supabase-200000-users Mobbin uses Supabase to authenticate 200,000 users https://supabase.com/blog/mobbin-supabase-200000-users Learn how Mobbin migrated 200,000 users from Firebase for a better authentication experience. - Tue, 27 Jul 2021 02:02:30 +0000 + Tue, 27 Jul 2021 16:04:00 +0000 https://supabase.com/blog/storage-beta Supabase Storage now in Beta https://supabase.com/blog/storage-beta Supabase Storage moves into Beta. - Mon, 26 Jul 2021 02:02:30 +0000 + Mon, 26 Jul 2021 16:04:00 +0000 https://supabase.com/blog/spot-flutter-with-postgres Spot: a video sharing app built with Flutter https://supabase.com/blog/spot-flutter-with-postgres Spot is a geolocation-based video-sharing app with some social networking features. - Mon, 26 Jul 2021 02:02:30 +0000 + Mon, 26 Jul 2021 16:04:00 +0000 https://supabase.com/blog/supabase-postgres-13 Supabase is now on Postgres 13.3 https://supabase.com/blog/supabase-postgres-13 From today, new Supabase projects will be on a version of Supabase Postgres that runs on Postgres 13.3. - Sun, 25 Jul 2021 02:02:30 +0000 + Sun, 25 Jul 2021 16:04:00 +0000 https://supabase.com/blog/supabase-community-day Supabase Community Day https://supabase.com/blog/supabase-community-day Community Day - Sun, 25 Jul 2021 02:02:30 +0000 + Sun, 25 Jul 2021 16:04:00 +0000 https://supabase.com/blog/epsilon3-self-hosting Epsilon3 Self-Host Supabase To Revolutionize Space Operations https://supabase.com/blog/epsilon3-self-hosting Learn how the team at Epsilon3 use Supabase to help teams execute secure and reliable operations in an industry that project spend runs into the billions. - Sun, 25 Jul 2021 02:02:30 +0000 + Sun, 25 Jul 2021 16:04:00 +0000 https://supabase.com/blog/supabase-launch-week-sql Supabase Launch Week II: The SQL https://supabase.com/blog/supabase-launch-week-sql Five days of Supabase. Again. - Wed, 21 Jul 2021 02:02:30 +0000 + Wed, 21 Jul 2021 16:04:00 +0000 https://supabase.com/blog/roles-postgres-hooks Protecting reserved roles with PostgreSQL Hooks https://supabase.com/blog/roles-postgres-hooks Using Postgres Hooks to protect functionality in your Postgres database. - Thu, 01 Jul 2021 02:02:30 +0000 + Thu, 01 Jul 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-june-2021 Supabase Beta June 2021 https://supabase.com/blog/supabase-beta-june-2021 Discord Logins, Vercel Integration, Full text search, and OAuth guides. - Tue, 01 Jun 2021 02:02:30 +0000 + Tue, 01 Jun 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-may-2021 Supabase Beta May 2021 https://supabase.com/blog/supabase-beta-may-2021 Apple & Twitter Logins, Supabase Grid, Go & Swift Libraries. - Tue, 01 Jun 2021 02:02:30 +0000 + Tue, 01 Jun 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-april-2021 Supabase Beta April 2021 https://supabase.com/blog/supabase-beta-april-2021 Supabase "gardening" - stability, security, and community support. - Tue, 04 May 2021 02:02:30 +0000 + Tue, 04 May 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-march-2021 Supabase Beta March 2021 https://supabase.com/blog/supabase-beta-march-2021 Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing. - Mon, 05 Apr 2021 02:02:30 +0000 + Mon, 05 Apr 2021 16:04:00 +0000 https://supabase.com/blog/supabase-workflows Workflows are coming to Supabase https://supabase.com/blog/supabase-workflows Functions are great, but you know what's better? - Thu, 01 Apr 2021 02:02:30 +0000 + Thu, 01 Apr 2021 16:04:00 +0000 https://supabase.com/blog/supabase-pgbouncer PgBouncer is now available in Supabase https://supabase.com/blog/supabase-pgbouncer Better support for Serverless and Postgres. - Thu, 01 Apr 2021 02:02:30 +0000 + Thu, 01 Apr 2021 16:04:00 +0000 https://supabase.com/blog/supabase-dot-com Supabase Dot Com https://supabase.com/blog/supabase-dot-com The Supabase Domain name is changing. - Thu, 01 Apr 2021 02:02:30 +0000 + Thu, 01 Apr 2021 16:04:00 +0000 https://supabase.com/blog/supabase-nft-marketplace Supabase Launches NFT Marketplace https://supabase.com/blog/supabase-nft-marketplace A fully encrypted NFT platform to protect and transact your digital assets - Thu, 01 Apr 2021 02:02:30 +0000 + Wed, 31 Mar 2021 16:04:00 +0000 https://supabase.com/blog/supabase-cli Supabase CLI https://supabase.com/blog/supabase-cli Local development, database migrations, and self-hosting. - Wed, 31 Mar 2021 02:02:30 +0000 + Tue, 30 Mar 2021 16:04:00 +0000 https://supabase.com/blog/supabase-storage Storage is now available in Supabase https://supabase.com/blog/supabase-storage Launching Supabase Storage and how you can use it in your apps - Tue, 30 Mar 2021 02:02:30 +0000 + Mon, 29 Mar 2021 16:04:00 +0000 https://supabase.com/blog/pricing Supabase Beta Pricing https://supabase.com/blog/pricing Supabase launches Beta pricing structure - Mon, 29 Mar 2021 02:02:30 +0000 + Sun, 28 Mar 2021 16:04:00 +0000 https://supabase.com/blog/launch-week Launch week https://supabase.com/blog/launch-week Five days of Supabase. - Wed, 24 Mar 2021 02:02:30 +0000 + Wed, 24 Mar 2021 16:04:00 +0000 https://supabase.com/blog/angels-of-supabase Angels of Supabase https://supabase.com/blog/angels-of-supabase Meet the investors of Supabase. - Wed, 24 Mar 2021 02:02:30 +0000 + Wed, 24 Mar 2021 16:04:00 +0000 https://supabase.com/blog/In-The-Loop Developers stay up to date with intheloop.dev https://supabase.com/blog/In-The-Loop Learn why Kevin is building intheloop.dev with Supabase - Mon, 22 Mar 2021 02:02:30 +0000 + Sun, 21 Mar 2021 16:04:00 +0000 https://supabase.com/blog/using-supabase-replit Using Supabase in Replit https://supabase.com/blog/using-supabase-replit Free hosted relational database from within your node.js repl - Thu, 11 Mar 2021 03:03:30 +0000 + Wed, 10 Mar 2021 16:04:00 +0000 https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders Toad, a link shortener with simple APIs for low-coders https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders An easy-to-use link shortening tool with simple APIs - Mon, 08 Mar 2021 03:03:30 +0000 + Sun, 07 Mar 2021 16:04:00 +0000 https://supabase.com/blog/postgres-as-a-cron-server Postgres as a CRON Server https://supabase.com/blog/postgres-as-a-cron-server Running repetitive tasks with your Postgres database. - Fri, 05 Mar 2021 03:03:30 +0000 + Thu, 04 Mar 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-february-2021 Supabase Beta February 2021 https://supabase.com/blog/supabase-beta-february-2021 One year of building. - Tue, 02 Mar 2021 03:03:30 +0000 + Mon, 01 Mar 2021 16:04:00 +0000 https://supabase.com/blog/cracking-postgres-interview Cracking PostgreSQL Interview Questions https://supabase.com/blog/cracking-postgres-interview Understand the top PostgreSQL Interview Questions - Sat, 27 Feb 2021 03:03:30 +0000 + Fri, 26 Feb 2021 16:04:00 +0000 https://supabase.com/blog/case-study-roboflow Roboflow.com choose Supabase to power Paint.wtf leaderboard https://supabase.com/blog/case-study-roboflow Learn how Roboflow.com used Supabase to build their Paint.wtf leaderboard - Tue, 09 Feb 2021 03:03:30 +0000 + Mon, 08 Feb 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-january-2021 Supabase Beta January 2021 https://supabase.com/blog/supabase-beta-january-2021 Eleven months of building. - Tue, 02 Feb 2021 03:03:30 +0000 + Mon, 01 Feb 2021 16:04:00 +0000 https://supabase.com/blog/supabase-beta-december-2020 Supabase Beta December 2020 https://supabase.com/blog/supabase-beta-december-2020 Ten months of building. - Sat, 02 Jan 2021 03:03:30 +0000 + Fri, 01 Jan 2021 16:04:00 +0000 https://supabase.com/blog/supabase-dashboard-performance Making the Supabase Dashboard Supa-fast https://supabase.com/blog/supabase-dashboard-performance Improving the performance of the Supabase dashboard - Sun, 13 Dec 2020 03:03:30 +0000 + Sat, 12 Dec 2020 16:04:00 +0000 https://supabase.com/blog/supabase-striveschool Supabase Partners With Strive School To Help Teach Open Source https://supabase.com/blog/supabase-striveschool Supabase Partners With Strive School To Help Teach Open Source To The Next Generation Of Developers - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 16:04:00 +0000 https://supabase.com/blog/case-study-xendit Xendit Built a Counter-Fraud Watchlist for the Fintech Industry https://supabase.com/blog/case-study-xendit See how Xendit use Supabase to build a full-text search engine. - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 16:04:00 +0000 https://supabase.com/blog/case-study-tayfa TAYFA Built a No-Code Website Builder in Seven Days https://supabase.com/blog/case-study-tayfa See how Tayfa went from idea to paying customer in less than 30 days. - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 16:04:00 +0000 https://supabase.com/blog/case-study-monitoro Monitoro Built a Web Crawler Handling Millions of API Requests https://supabase.com/blog/case-study-monitoro See how Monitoro built an automated scraping platform using Supabase. - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-november-2020 Supabase Alpha November 2020 https://supabase.com/blog/supabase-alpha-november-2020 Nine months of building. - Tue, 01 Dec 2020 03:03:30 +0000 + Mon, 30 Nov 2020 16:04:00 +0000 https://supabase.com/blog/postgresql-views Postgres Views https://supabase.com/blog/postgresql-views Creating and using a view in PostgreSQL. - Wed, 18 Nov 2020 03:03:30 +0000 + Tue, 17 Nov 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-october-2020 Supabase Alpha October 2020 https://supabase.com/blog/supabase-alpha-october-2020 Eight months of building. - Mon, 02 Nov 2020 03:03:30 +0000 + Sun, 01 Nov 2020 16:04:00 +0000 https://supabase.com/blog/improved-dx Supabase.js 1.0 https://supabase.com/blog/improved-dx We're releasing a new version of our Supabase client with some awesome new improvements. - Fri, 30 Oct 2020 02:02:30 +0000 + Thu, 29 Oct 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-september-2020 Supabase Alpha September 2020 https://supabase.com/blog/supabase-alpha-september-2020 Seven months of building. - Sat, 03 Oct 2020 02:02:30 +0000 + Fri, 02 Oct 2020 16:04:00 +0000 https://supabase.com/blog/supabase-hacktoberfest-2020 Supabase Hacktoberfest 2020 https://supabase.com/blog/supabase-hacktoberfest-2020 Join us for a celebration of open source software and learn how to contribute to Supabase. - Fri, 11 Sep 2020 02:02:30 +0000 + Thu, 10 Sep 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-august-2020 Supabase Alpha August 2020 https://supabase.com/blog/supabase-alpha-august-2020 Six months of building - Thu, 03 Sep 2020 02:02:30 +0000 + Wed, 02 Sep 2020 16:04:00 +0000 https://supabase.com/blog/supabase-auth Supabase Auth https://supabase.com/blog/supabase-auth Authenticate and authorize your users with Supabase Auth - Wed, 05 Aug 2020 02:02:30 +0000 + Tue, 04 Aug 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-july-2020 Supabase Alpha July 2020 https://supabase.com/blog/supabase-alpha-july-2020 Five months of building - Sun, 02 Aug 2020 02:02:30 +0000 + Sat, 01 Aug 2020 16:04:00 +0000 https://supabase.com/blog/continuous-postgresql-backup-walg Continuous PostgreSQL Backups using WAL-G https://supabase.com/blog/continuous-postgresql-backup-walg Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G. - Sun, 02 Aug 2020 02:02:30 +0000 + Sat, 01 Aug 2020 16:04:00 +0000 https://supabase.com/blog/alpha-launch-postmortem Alpha Launch Postmortem https://supabase.com/blog/alpha-launch-postmortem Everything that went wrong with Supabase's launch - Fri, 10 Jul 2020 02:02:30 +0000 + Thu, 09 Jul 2020 16:04:00 +0000 https://supabase.com/blog/postgresql-templates What are PostgreSQL Templates? https://supabase.com/blog/postgresql-templates What are PostgreSQL templates and what are they used for? - Thu, 09 Jul 2020 02:02:30 +0000 + Wed, 08 Jul 2020 16:04:00 +0000 https://supabase.com/blog/postgresql-physical-logical-backups Physical vs Logical Backups in PostgreSQL https://supabase.com/blog/postgresql-physical-logical-backups What are physical and logical backups in Postgres? - Tue, 07 Jul 2020 02:02:30 +0000 + Mon, 06 Jul 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-june-2020 Supabase Alpha June 2020 https://supabase.com/blog/supabase-alpha-june-2020 Four months of building - Wed, 01 Jul 2020 02:02:30 +0000 + Tue, 30 Jun 2020 16:04:00 +0000 https://supabase.com/blog/supabase-steve-chavez Steve Chavez has joined Supabase https://supabase.com/blog/supabase-steve-chavez Steve joins Supabase to help build Auth. - Mon, 15 Jun 2020 02:02:30 +0000 + Sun, 14 Jun 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-may-2020 Supabase Alpha May 2020 https://supabase.com/blog/supabase-alpha-may-2020 Three months of building - Mon, 01 Jun 2020 02:02:30 +0000 + Sun, 31 May 2020 16:04:00 +0000 https://supabase.com/blog/supabase-alpha-april-2020 Supabase Alpha April 2020 https://supabase.com/blog/supabase-alpha-april-2020 Two months of building - Mon, 01 Jun 2020 02:02:30 +0000 + Sun, 31 May 2020 16:04:00 +0000 diff --git a/apps/www/public/sitemap_www.xml b/apps/www/public/sitemap_www.xml index 3d294d2668b..3573f809d82 100644 --- a/apps/www/public/sitemap_www.xml +++ b/apps/www/public/sitemap_www.xml @@ -84,6 +84,12 @@ 0.5 + + https://supabase.com/launch-week/ticket-image + weekly + 0.5 + + https://supabase.com/legal/dpa weekly @@ -902,6 +908,12 @@ 0.5 + + https://supabase.com/blog/flutter-authentication-and-authorization-with-rls + weekly + 0.5 + + https://supabase.com/alternatives/supabase-vs-firebase weekly diff --git a/apps/www/styles/index.css b/apps/www/styles/index.css index b86beb234ef..aa7a528d138 100644 --- a/apps/www/styles/index.css +++ b/apps/www/styles/index.css @@ -373,6 +373,7 @@ launch week :root { --auth-ui-card-height: 50vh; + --lw-secondary-color: #8a8f98; } .glow-area { diff --git a/examples/edge-functions/supabase/functions/oak-server/README.md b/examples/edge-functions/supabase/functions/oak-server/README.md new file mode 100644 index 00000000000..b1db6166231 --- /dev/null +++ b/examples/edge-functions/supabase/functions/oak-server/README.md @@ -0,0 +1,24 @@ +# Writing functions using Oak Server Middleware + +This example shows how you can write functions using Oak server middleware (https://oakserver.github.io/oak/) + +## Run locally + +```bash +supabase functions serve oak-server +``` + +Use cURL or Postman to make a POST request to http://localhost:54321/functions/v1/oak-server. + +``` + curl --location --request POST 'http://localhost:54321/functions/v1/oak-server' \ +--header 'Authorization: Bearer YOUR_TOKEN' \ +--header 'Content-Type: application/json' \ +--data-raw '{ "name": "John Doe" }' +``` + +## Deploy + +```bash +supabase functions deploy oak-server +``` diff --git a/examples/edge-functions/supabase/functions/oak-server/index.ts b/examples/edge-functions/supabase/functions/oak-server/index.ts new file mode 100644 index 00000000000..8e96d1f0033 --- /dev/null +++ b/examples/edge-functions/supabase/functions/oak-server/index.ts @@ -0,0 +1,25 @@ +import { Application, Router } from 'https://deno.land/x/oak/mod.ts' + +const router = new Router() +router + // Note: path will be prefixed with function name + .get('/oak-server', (context) => { + context.response.body = 'This is an example Oak server running on Edge Functions!' + }) + .post('/oak-server/greet', (context) => { + // Note: request body will be streamed to the function as chunks, set limit to 0 to fully read it. + const result = context.request.body({ type: 'json', limit: 0 }) + const body = await result.value + const name = body.name || 'you' + + context.response.body = { msg: `Hey ${name}!` } + }) + .get('/oak-server/redirect', (context) => { + context.response.redirect('https://www.example.com') + }) + +const app = new Application() +app.use(router.routes()) +app.use(router.allowedMethods()) + +await app.listen({ port: 8000 }) diff --git a/package-lock.json b/package-lock.json index 2d306c7d1d7..b1b1707e092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -228,7 +228,7 @@ "gray-matter": "^4.0.2", "markdown-toc": "^1.2.0", "marked": "^4.0.12", - "next": "latest", + "next": "12.1.0", "next-compose-plugins": "^2.2.1", "next-mdx-remote": "^4.1.0", "next-seo": "^4.20.0", @@ -254,6 +254,7 @@ "ui": "*", "use-debounce": "^7.0.1", "uuid": "^8.3.2", + "vanilla-tilt": "1.7.0", "yup": "^0.32.11" }, "devDependencies": { @@ -35302,6 +35303,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vanilla-tilt": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vanilla-tilt/-/vanilla-tilt-1.7.0.tgz", + "integrity": "sha512-u9yUhpSasFeqQCuiTon+RSb0aHzcj9stvWVXQIswzKL5oG491lkYk7U1GmhOAEZt7yPT6EiYZRJhIh2MFBncOA==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -64972,6 +64978,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "vanilla-tilt": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vanilla-tilt/-/vanilla-tilt-1.7.0.tgz", + "integrity": "sha512-u9yUhpSasFeqQCuiTon+RSb0aHzcj9stvWVXQIswzKL5oG491lkYk7U1GmhOAEZt7yPT6EiYZRJhIh2MFBncOA==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -65997,7 +66008,7 @@ "gray-matter": "^4.0.2", "markdown-toc": "^1.2.0", "marked": "^4.0.12", - "next": "latest", + "next": "12.1.0", "next-compose-plugins": "^2.2.1", "next-mdx-remote": "^4.1.0", "next-seo": "^4.20.0", @@ -66028,6 +66039,7 @@ "ui": "*", "use-debounce": "^7.0.1", "uuid": "^8.3.2", + "vanilla-tilt": "1.7.0", "yup": "^0.32.11" }, "dependencies": { diff --git a/studio/components/interfaces/Billing/Billing.constants.tsx b/studio/components/interfaces/Billing/Billing.constants.tsx index e246a093ddd..00225246d08 100644 --- a/studio/components/interfaces/Billing/Billing.constants.tsx +++ b/studio/components/interfaces/Billing/Billing.constants.tsx @@ -1,4 +1,4 @@ -import { IconArchive, IconDatabase, IconKey } from 'ui' +import { IconArchive, IconCode, IconDatabase, IconKey } from 'ui' export const CANCELLATION_REASONS = [ 'Pricing', @@ -71,4 +71,25 @@ export const USAGE_BASED_PRODUCTS = [ }, ], }, + { + title: 'Functions', + icon: , + features: [ + { + key: 'func_count', + attribute: 'func_count', + title: 'Function Count', + units: 'absolute', + costPerUnit: 0.1, + }, + + { + key: 'func_invocations', + attribute: 'func_invocations', + title: 'Function Invocations', + units: 'absolute', + costPerUnit: 0.000002, + } + ], + }, ] diff --git a/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx b/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx index 0e703a15e96..57d837e4252 100644 --- a/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx +++ b/studio/components/interfaces/Settings/ProjectUsageBars/ProjectUsageBars.tsx @@ -146,7 +146,9 @@ const ProjectUsage: FC = ({ projectRef }) => { /> ) : ( - {feature.units === 'bytes' ? formatBytes(usageValue) : ''} + {feature.units === 'bytes' + ? formatBytes(usageValue) + : usageValue} )} diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx index a218a446c35..ebca922f3e1 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx @@ -63,7 +63,9 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = {/* Project Dropdown */} - {showOverUsageBadge && ( + + {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */} + {/* {showOverUsageBadge && ( - )} + )} */} )} diff --git a/studio/lib/constants/metrics.tsx b/studio/lib/constants/metrics.tsx index 14b79718a50..796925abace 100644 --- a/studio/lib/constants/metrics.tsx +++ b/studio/lib/constants/metrics.tsx @@ -144,6 +144,13 @@ export const METRICS = [ // provider: 'daily-stats', // category: METRIC_CATEGORIES.API_AUTH, // }, + + { + key: 'total_auth_billing_period_mau', + label: 'Auth Monthly Active User', + provider: 'daily-stats', + category: METRIC_CATEGORIES.API_AUTH, + }, { key: 'total_auth_ingress', label: 'Auth Ingress', diff --git a/studio/pages/project/[ref]/index.tsx b/studio/pages/project/[ref]/index.tsx index aad709957fa..59eaea022d1 100644 --- a/studio/pages/project/[ref]/index.tsx +++ b/studio/pages/project/[ref]/index.tsx @@ -28,7 +28,8 @@ const Home: NextPageWithLayout = () => {
- {projectTier !== undefined && } + {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */} + {/* {projectTier !== undefined && } */}
{project?.status === PROJECT_STATUS.INACTIVE && } diff --git a/studio/pages/project/[ref]/settings/billing/invoices.tsx b/studio/pages/project/[ref]/settings/billing/invoices.tsx index 29c76ba8d21..45481e4ff1b 100644 --- a/studio/pages/project/[ref]/settings/billing/invoices.tsx +++ b/studio/pages/project/[ref]/settings/billing/invoices.tsx @@ -57,7 +57,8 @@ const Settings: FC = ({ project }) => { return (
- {projectTier !== undefined && } + {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */} + {/* {projectTier !== undefined && } */}

Invoices

diff --git a/studio/pages/project/[ref]/settings/billing/subscription.tsx b/studio/pages/project/[ref]/settings/billing/subscription.tsx index 34a925b6079..9744f34e911 100644 --- a/studio/pages/project/[ref]/settings/billing/subscription.tsx +++ b/studio/pages/project/[ref]/settings/billing/subscription.tsx @@ -58,7 +58,8 @@ const Settings: FC = ({ project }) => { return (
- {projectTier !== undefined && } + {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */} + {/* {projectTier !== undefined && } */}