diff --git a/examples/with-supabase-auth-realtime-db/.env.local.example b/examples/with-supabase-auth-realtime-db/.env.local.example index b6fedec92e30..477da3d401d6 100644 --- a/examples/with-supabase-auth-realtime-db/.env.local.example +++ b/examples/with-supabase-auth-realtime-db/.env.local.example @@ -1,2 +1,3 @@ +# Update these with your Supabase details from your project settings > API NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co -NEXT_PUBLIC_SUPABASE_KEY=your-anon-key \ No newline at end of file +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key \ No newline at end of file diff --git a/examples/with-supabase-auth-realtime-db/README.md b/examples/with-supabase-auth-realtime-db/README.md index 537029ce5f8d..0f64a1dc7140 100644 --- a/examples/with-supabase-auth-realtime-db/README.md +++ b/examples/with-supabase-auth-realtime-db/README.md @@ -1,139 +1,21 @@ -# Realtime chat example using Supabase +# Example: [Supabase authentication](https://supabase.io/docs/guides/auth) client- and server-side (API routes), and SSR with auth cookie. -This is a full-stack Slack clone example using: +This example shows how to use Supabase auth on the client and server in both [API routes](https://nextjs.org/docs/api-routes/introduction) and when using [server side rendering (SSR)](https://nextjs.org/docs/basic-features/pages#server-side-rendering). -- Frontend: - - Next.js. - - [Supabase](https://supabase.io/docs/library/getting-started) for user management and realtime data syncing. -- Backend: - - [app.supabase.io](https://app.supabase.io/): hosted Postgres database with restful API for usage with Supabase.js. +## Deploy with Vercel -![Demo animation gif](./docs/slack-clone-demo.gif) +The Vercel deployment will guide you through creating a Supabase account and project. After installation of the Supabase integration, all relevant environment variables will be set up so that the project is usable immediately after deployment 🚀 -This example is a clone of the [Slack Clone example](https://github.com/supabase/supabase/tree/master/examples/nextjs-slack-clone) in the supabase repo, feel free to check it out! +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fnextjs-with-supabase-auth&project-name=nextjs-with-supabase-auth&repository-name=nextjs-with-supabase-auth&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv) -## Deploy your own +## Feedback and issues -Once you have access to [the environment variables you'll need](#step-3-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): +Please file feedback and issues over on the [Supabase GitHub org](https://github.com/supabase/supabase/issues/new/choose). -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db&project-name=with-supabase-auth-realtime-db&repository-name=with-supabase-auth-realtime-db&env=NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_KEY&envDescription=Required%20to%20connect%20the%20app%to%Supabase&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db%23step-3-set-up-environment-variables&project-name=supabase-slack-clone) +## More Supabase examples -## How to use - -Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: - -```bash -npx create-next-app --example with-supabase-auth-realtime-db realtime-chat-app -# or -yarn create next-app --example with-supabase-auth-realtime-db realtime-chat-app -``` - -## Configuration - -### Step 1. Create a new Supabase project - -Sign up to Supabase - [https://app.supabase.io](https://app.supabase.io) and create a new project. Wait for your database to start. - -### Step 2. Run the "Slack Clone" Quickstart - -Once your database has started, run the "Slack Clone" quickstart. - -![Slack Clone Quick Start](./docs/quickstart.png) - -### Step 3. Set up environment variables - -In your Supabase project, go to Project Settings (the cog icon), open the API tab, and find your **API URL** and **anon** key, you'll need these in the next step. - -![image](https://user-images.githubusercontent.com/10214025/88916245-528c2680-d298-11ea-8a71-708f93e1ce4f.png) - -Next, copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git): - -```bash -cp .env.local.example .env.local -``` - -Then set each variable on `.env.local`: - -- `NEXT_PUBLIC_SUPABASE_URL` should be the **API URL** -- `NEXT_PUBLIC_SUPABASE_KEY` should be the **anon** key - -The **anon** key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data. Read more about this [below](#postgres-row-level-security). - -> **_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser. - -### Step 4. Run Next.js in development mode - -```bash -npm install -npm run dev - -# or - -yarn install -yarn dev -``` - -Visit [http://localhost:3000](http://localhost:3000) and start chatting! Open a channel across two browser tabs to see everything getting updated in realtime 🥳. If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions). - -### Step 5. Deploy on Vercel - -You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). - -#### Deploy Your Local Project - -To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example). - -**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file. - -#### Deploy from Our Template - -Alternatively, you can deploy using our template by clicking on the Deploy button below. - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db&project-name=with-supabase-auth-realtime-db&repository-name=with-supabase-auth-realtime-db&env=NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_KEY&envDescription=Required%20to%20connect%20the%20app%to%Supabase&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db%23step-3-set-up-environment-variables&project-name=supabase-slack-clone) - -## Supabase details - -### Postgres Row level security - -This project uses very high-level Authorization using Postgres' Role Level Security. -When you start a Postgres database on Supabase, we populate it with an `auth` schema, and some helper functions. -When a user logs in, they are issued a JWT with the role `authenticated` and thier UUID. -We can use these details to provide fine-grained control over what each user can and cannot do. - -This is a trimmed-down schema, with the policies: - -```sql --- USER PROFILES -CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE'); -CREATE TABLE public.users ( - id uuid NOT NULL PRIMARY KEY, -- UUID from auth.users (Supabase) - username text, - status user_status DEFAULT 'OFFLINE'::public.user_status -); -ALTER TABLE public.users ENABLE ROW LEVEL SECURITY; -CREATE POLICY "Allow logged-in read access" on public.users FOR SELECT USING ( auth.role() = 'authenticated' ); -CREATE POLICY "Allow individual insert access" on public.users FOR INSERT WITH CHECK ( auth.uid() = id ); -CREATE POLICY "Allow individual update access" on public.users FOR UPDATE USING ( auth.uid() = id ); - --- CHANNELS -CREATE TABLE public.channels ( - id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, - slug text NOT NULL UNIQUE -); -ALTER TABLE public.channels ENABLE ROW LEVEL SECURITY; -CREATE POLICY "Allow logged-in full access" on public.channels FOR ALL USING ( auth.role() = 'authenticated' ); - --- MESSAGES -CREATE TABLE public.messages ( - id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL, - message text, - user_id uuid REFERENCES public.users NOT NULL, - channel_id bigint REFERENCES public.channels NOT NULL -); -ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY; -CREATE POLICY "Allow logged-in read access" on public.messages USING ( auth.role() = 'authenticated' ); -CREATE POLICY "Allow individual insert access" on public.messages FOR INSERT WITH CHECK ( auth.uid() = user_id ); -CREATE POLICY "Allow individual update access" on public.messages FOR UPDATE USING ( auth.uid() = user_id ); -``` +- [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) +- [Next.js Slack Clone](https://github.com/supabase/supabase/tree/master/examples/nextjs-slack-clone) +- [Next.js Todo List](https://github.com/supabase/supabase/tree/master/examples/nextjs-todo-list) +- [Next.js Live Tracker Map](https://github.com/supabase/supabase/tree/master/examples/nextjs-live-tracker-map) +- [And many more...](https://github.com/supabase/supabase/tree/master/examples) diff --git a/examples/with-supabase-auth-realtime-db/components/Layout.js b/examples/with-supabase-auth-realtime-db/components/Layout.js deleted file mode 100644 index 1b94dea575aa..000000000000 --- a/examples/with-supabase-auth-realtime-db/components/Layout.js +++ /dev/null @@ -1,80 +0,0 @@ -import Link from 'next/link' -import { useContext } from 'react' -import UserContext from '~/lib/UserContext' -import { addChannel } from '~/lib/Store' - -export default function Layout(props) { - const { signOut } = useContext(UserContext) - - const slugify = (text) => { - return text - .toString() - .toLowerCase() - .replace(/\s+/g, '-') // Replace spaces with - - .replace(/[^\w-]+/g, '') // Remove all non-word chars - .replace(/--+/g, '-') // Replace multiple - with single - - .replace(/^-+/, '') // Trim - from start of text - .replace(/-+$/, '') // Trim - from end of text - } - - const newChannel = async () => { - const slug = prompt('Please enter your name') - if (slug) { - addChannel(slugify(slug)) - } - } - - return ( -
- {/* Sidebar */} - - - {/* Messages */} -
{props.children}
-
- ) -} - -const SidebarItem = ({ channel, isActiveChannel }) => ( - <> -
  • - - {channel.slug} - -
  • - -) diff --git a/examples/with-supabase-auth-realtime-db/components/Message.js b/examples/with-supabase-auth-realtime-db/components/Message.js deleted file mode 100644 index 1590a0ab9c09..000000000000 --- a/examples/with-supabase-auth-realtime-db/components/Message.js +++ /dev/null @@ -1,10 +0,0 @@ -const Message = ({ message }) => ( - <> -
    -

    {message.author.username}

    -

    {message.message}

    -
    - -) - -export default Message diff --git a/examples/with-supabase-auth-realtime-db/components/MessageInput.js b/examples/with-supabase-auth-realtime-db/components/MessageInput.js deleted file mode 100644 index 1c0e3501beb5..000000000000 --- a/examples/with-supabase-auth-realtime-db/components/MessageInput.js +++ /dev/null @@ -1,28 +0,0 @@ -import { useState } from 'react' - -const MessageInput = ({ onSubmit }) => { - const [messageText, setMessageText] = useState('') - - const submitOnEnter = (event) => { - // Watch for enter key - if (event.keyCode === 13) { - onSubmit(messageText) - setMessageText('') - } - } - - return ( - <> - setMessageText(e.target.value)} - onKeyDown={(e) => submitOnEnter(e)} - /> - - ) -} - -export default MessageInput diff --git a/examples/with-supabase-auth-realtime-db/docs/quickstart.png b/examples/with-supabase-auth-realtime-db/docs/quickstart.png deleted file mode 100644 index e406fc9c0826..000000000000 Binary files a/examples/with-supabase-auth-realtime-db/docs/quickstart.png and /dev/null differ diff --git a/examples/with-supabase-auth-realtime-db/docs/slack-clone-demo.gif b/examples/with-supabase-auth-realtime-db/docs/slack-clone-demo.gif deleted file mode 100644 index 8dff9b017bea..000000000000 Binary files a/examples/with-supabase-auth-realtime-db/docs/slack-clone-demo.gif and /dev/null differ diff --git a/examples/with-supabase-auth-realtime-db/jsconfig.json b/examples/with-supabase-auth-realtime-db/jsconfig.json deleted file mode 100644 index 7af8f6e0edda..000000000000 --- a/examples/with-supabase-auth-realtime-db/jsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "~/*": ["./*"] - } - } -} diff --git a/examples/with-supabase-auth-realtime-db/lib/Store.js b/examples/with-supabase-auth-realtime-db/lib/Store.js deleted file mode 100644 index b22779c735a0..000000000000 --- a/examples/with-supabase-auth-realtime-db/lib/Store.js +++ /dev/null @@ -1,169 +0,0 @@ -import { useState, useEffect } from 'react' -import { createClient } from '@supabase/supabase-js' - -const supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.NEXT_PUBLIC_SUPABASE_KEY -) - -/** - * @param {number} channelId the currently selected Channel - */ -export const useStore = (props) => { - const [channels, setChannels] = useState([]) - const [messages, setMessages] = useState([]) - const [users] = useState(new Map()) - const [newMessage, handleNewMessage] = useState(null) - const [newChannel, handleNewChannel] = useState(null) - const [newOrUpdatedUser, handleNewOrUpdatedUser] = useState(null) - - // Load initial data and set up listeners - useEffect(() => { - // Get Channels - fetchChannels(setChannels) - // Listen for new messages - const messageListener = supabase - .from('messages') - .on('INSERT', (payload) => handleNewMessage(payload.new)) - .subscribe() - // Listen for changes to our users - const userListener = supabase - .from('users') - .on('*', (payload) => handleNewOrUpdatedUser(payload.new)) - .subscribe() - // Listen for new channels - const channelListener = supabase - .from('channels') - .on('INSERT', (payload) => handleNewChannel(payload.new)) - .subscribe() - // Cleanup on unmount - return () => { - messageListener.unsubscribe() - userListener.unsubscribe() - channelListener.unsubscribe() - } - }, []) - - // Update when the route changes - useEffect(() => { - if (props?.channelId > 0) { - fetchMessages(props.channelId, (messages) => { - messages.forEach((x) => users.set(x.user_id, x.author)) - setMessages(messages) - }) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.channelId]) - - // New message recieved from Postgres - useEffect(() => { - if (newMessage && newMessage.channel_id === Number(props.channelId)) { - const handleAsync = async () => { - let authorId = newMessage.user_id - if (!users.get(authorId)) - await fetchUser(authorId, (user) => handleNewOrUpdatedUser(user)) - setMessages(messages.concat(newMessage)) - } - handleAsync() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [newMessage]) - - // New channel recieved from Postgres - useEffect(() => { - if (newChannel) setChannels(channels.concat(newChannel)) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [newChannel]) - - // New or updated user recieved from Postgres - useEffect(() => { - if (newOrUpdatedUser) users.set(newOrUpdatedUser.id, newOrUpdatedUser) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [newOrUpdatedUser]) - - return { - // We can export computed values here to map the authors to each message - messages: messages.map((x) => ({ ...x, author: users.get(x.user_id) })), - channels: channels.sort((a, b) => a.slug.localeCompare(b.slug)), - users, - } -} - -/** - * Fetch all channels - * @param {function} setState Optionally pass in a hook or callback to set the state - */ -export const fetchChannels = async (setState) => { - try { - let { body } = await supabase.from('channels').select('*') - if (setState) setState(body) - return body - } catch (error) { - console.log('error', error) - } -} - -/** - * Fetch a single user - * @param {number} userId - * @param {function} setState Optionally pass in a hook or callback to set the state - */ -export const fetchUser = async (userId, setState) => { - try { - let { body } = await supabase.from('users').eq('id', userId).select(`*`) - let user = body[0] - if (setState) setState(user) - return user - } catch (error) { - console.log('error', error) - } -} - -/** - * Fetch all messages and their authors - * @param {number} channelId - * @param {function} setState Optionally pass in a hook or callback to set the state - */ -export const fetchMessages = async (channelId, setState) => { - try { - let { body } = await supabase - .from('messages') - .eq('channel_id', channelId) - .select(`*, author:user_id(*)`) - .order('inserted_at', true) - if (setState) setState(body) - return body - } catch (error) { - console.log('error', error) - } -} - -/** - * Insert a new channel into the DB - * @param {string} slug The channel name - */ -export const addChannel = async (slug) => { - try { - let { body } = await supabase.from('channels').insert([{ slug }]) - return body - } catch (error) { - console.log('error', error) - } -} - -/** - * Insert a new message into the DB - * @param {string} message The message text - * @param {number} channel_id - * @param {number} user_id The author - */ -export const addMessage = async (message, channel_id, user_id) => { - try { - let { body } = await supabase - .from('messages') - .insert([{ message, channel_id, user_id }]) - return body - } catch (error) { - console.log('error', error) - } -} diff --git a/examples/with-supabase-auth-realtime-db/lib/UserContext.js b/examples/with-supabase-auth-realtime-db/lib/UserContext.js deleted file mode 100644 index ee09678f5089..000000000000 --- a/examples/with-supabase-auth-realtime-db/lib/UserContext.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react' - -const UserContext = createContext() - -export default UserContext diff --git a/examples/with-supabase-auth-realtime-db/package.json b/examples/with-supabase-auth-realtime-db/package.json index cd117d52fadc..b05bb8417e85 100644 --- a/examples/with-supabase-auth-realtime-db/package.json +++ b/examples/with-supabase-auth-realtime-db/package.json @@ -1,22 +1,18 @@ { - "name": "with-supabase-auth-realtime-db", - "version": "0.1.0", - "license": "MIT", + "name": "with-supabase-auth", + "version": "1.0.0", "scripts": { - "dev": "next", + "dev": "next dev", "build": "next build", "start": "next start" }, "dependencies": { - "@supabase/supabase-js": "^0.35.9", + "@supabase/supabase-js": "^1.2.1", + "@supabase/ui": "^0.6.1", "next": "latest", "react": "^16.13.1", "react-dom": "^16.13.1", - "sass": "^1.26.2" + "swr": "0.2.3" }, - "devDependencies": { - "autoprefixer": "10.1.0", - "postcss": "8.2.2", - "tailwindcss": "2.0.2" - } + "license": "MIT" } diff --git a/examples/with-supabase-auth-realtime-db/pages/_app.js b/examples/with-supabase-auth-realtime-db/pages/_app.js index 8bd38f78bcc7..62d631ce30d0 100644 --- a/examples/with-supabase-auth-realtime-db/pages/_app.js +++ b/examples/with-supabase-auth-realtime-db/pages/_app.js @@ -1,71 +1,13 @@ -import '~/styles/style.scss' -import React from 'react' -import App from 'next/app' -import Router from 'next/router' -import UserContext from 'lib/UserContext' -import { createClient } from '@supabase/supabase-js' - -const supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.NEXT_PUBLIC_SUPABASE_KEY -) - -export default class SupabaseSlackClone extends App { - state = { - authLoaded: false, - user: null, - } - - componentDidMount = () => { - const user = localStorage.getItem('supabase-slack-clone') - if (user) this.setState({ user, authLoaded: true }) - else Router.push('/') - } - - signIn = async (id, username) => { - try { - let { body } = await supabase - .from('users') - .match({ username }) - .select('id, username') - const existing = body[0] - const { body: user } = existing?.id - ? await supabase - .from('users') - .update({ id, username }) - .match({ id }) - .single() - : await supabase.from('users').insert([{ id, username }]).single() - - localStorage.setItem('supabase-slack-clone', user.id) - this.setState({ user: user.id }, () => { - Router.push('/channels/[id]', '/channels/1') - }) - } catch (error) { - console.log('error', error) - } - } - - signOut = () => { - supabase.auth.logout() - localStorage.removeItem('supabase-slack-clone') - this.setState({ user: null }) - Router.push('/') - } - - render() { - const { Component, pageProps } = this.props - return ( - +import { Auth } from '@supabase/ui' +import { supabase } from '../utils/initSupabase' +import './../style.css' + +export default function MyApp({ Component, pageProps }) { + return ( +
    + - - ) - } + +
    + ) } diff --git a/examples/with-supabase-auth-realtime-db/pages/api/auth.js b/examples/with-supabase-auth-realtime-db/pages/api/auth.js new file mode 100644 index 000000000000..2d5c38140032 --- /dev/null +++ b/examples/with-supabase-auth-realtime-db/pages/api/auth.js @@ -0,0 +1,8 @@ +/** + * NOTE: this file is only needed if you're doing SSR (getServerSideProps)! + */ +import { supabase } from '../../utils/initSupabase' + +export default function handler(req, res) { + supabase.auth.api.setAuthCookie(req, res) +} diff --git a/examples/with-supabase-auth-realtime-db/pages/api/getUser.js b/examples/with-supabase-auth-realtime-db/pages/api/getUser.js new file mode 100644 index 000000000000..697667deaf86 --- /dev/null +++ b/examples/with-supabase-auth-realtime-db/pages/api/getUser.js @@ -0,0 +1,13 @@ +import { supabase } from '../../utils/initSupabase' + +// Example of how to verify and get user data server-side. +const getUser = async (req, res) => { + const token = req.headers.token + + const { data: user, error } = await supabase.auth.api.getUser(token) + + if (error) return res.status(401).json({ error: error.message }) + return res.status(200).json(user) +} + +export default getUser diff --git a/examples/with-supabase-auth-realtime-db/pages/channels/[id].js b/examples/with-supabase-auth-realtime-db/pages/channels/[id].js deleted file mode 100644 index 7e9bd5f808eb..000000000000 --- a/examples/with-supabase-auth-realtime-db/pages/channels/[id].js +++ /dev/null @@ -1,53 +0,0 @@ -import Layout from '~/components/Layout' -import Message from '~/components/Message' -import MessageInput from '~/components/MessageInput' -import { useRouter } from 'next/router' -import { useStore, addMessage } from '~/lib/Store' -import { useContext, useEffect, useRef } from 'react' -import UserContext from '~/lib/UserContext' - -const ChannelsPage = (props) => { - const router = useRouter() - const { user, authLoaded, signOut } = useContext(UserContext) - const messagesEndRef = useRef(null) - - // Redirect if not signed in. - useEffect(() => { - if (authLoaded && !user) signOut() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [user, router]) - - // Else load up the page - const { id: channelId } = router.query - const { messages, channels } = useStore({ channelId }) - - useEffect(() => { - messagesEndRef.current.scrollIntoView({ - block: 'start', - behavior: 'smooth', - }) - }, [messages]) - - // Render the channels and messages - return ( - -
    -
    -
    - {messages.map((x) => ( - - ))} -
    -
    -
    -
    - addMessage(text, channelId, user)} - /> -
    -
    - - ) -} - -export default ChannelsPage diff --git a/examples/with-supabase-auth-realtime-db/pages/index.js b/examples/with-supabase-auth-realtime-db/pages/index.js index 7585c0be47dd..cb826d92788c 100644 --- a/examples/with-supabase-auth-realtime-db/pages/index.js +++ b/examples/with-supabase-auth-realtime-db/pages/index.js @@ -1,87 +1,121 @@ -import { useState, useContext } from 'react' -import UserContext from 'lib/UserContext' -import { createClient } from '@supabase/supabase-js' +import Link from 'next/link' +import useSWR from 'swr' +import { Auth, Card, Typography, Space, Button, Icon } from '@supabase/ui' +import { supabase } from '../utils/initSupabase' +import { useEffect, useState } from 'react' -const supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.NEXT_PUBLIC_SUPABASE_KEY -) +const fetcher = (url, token) => + fetch(url, { + method: 'GET', + headers: new Headers({ 'Content-Type': 'application/json', token }), + credentials: 'same-origin', + }).then((res) => res.json()) -const Home = () => { - const { signIn } = useContext(UserContext) - const [username, setUsername] = useState('') - const [password, setPassword] = useState('') +const Index = () => { + const { user, session } = Auth.useUser() + const { data, error } = useSWR( + session ? ['/api/getUser', session.access_token] : null, + fetcher + ) + const [authView, setAuthView] = useState('sign_in') + + useEffect(() => { + const { data: authListener } = supabase.auth.onAuthStateChange( + (event, session) => { + if (event === 'PASSWORD_RECOVERY') setAuthView('update_password') + if (event === 'USER_UPDATED') + setTimeout(() => setAuthView('sign_in'), 1000) + // Send session to /api/auth route to set the auth cookie. + // NOTE: this is only needed if you're doing SSR (getServerSideProps)! + fetch('/api/auth', { + method: 'POST', + headers: new Headers({ 'Content-Type': 'application/json' }), + credentials: 'same-origin', + body: JSON.stringify({ event, session }), + }).then((res) => res.json()) + } + ) - const handleLogin = async (type, username, password) => { - try { - const { - body: { user }, - } = - type === 'LOGIN' - ? await supabase.auth.login(username, password) - : await supabase.auth.signup(username, password) - if (!!user) signIn(user.id, user.email) - } catch (error) { - console.log('error', error) - alert(error.error_description || error) + return () => { + authListener.unsubscribe() } - } + }, []) - return ( -
    -
    -
    -
    - - setUsername(e.target.value)} - /> -
    -
    - - setPassword(e.target.value)} + const View = () => { + if (!user) + return ( + +
    + + + Welcome to Supabase Auth +
    + +
    + ) - -
    -
    + Log out + + {error && ( + Failed to fetch user! + )} + {data && !error ? ( + <> + + User data retrieved server-side (in API route): + + + +
    {JSON.stringify(data, null, 2)}
    +
    + + ) : ( +
    Loading...
    + )} + + + + SSR example with getServerSideProps + + + + )} + + ) + } + + return ( +
    + + +
    ) } -export default Home +export default Index diff --git a/examples/with-supabase-auth-realtime-db/pages/profile.js b/examples/with-supabase-auth-realtime-db/pages/profile.js new file mode 100644 index 000000000000..27c9206c2360 --- /dev/null +++ b/examples/with-supabase-auth-realtime-db/pages/profile.js @@ -0,0 +1,41 @@ +import Link from 'next/link' +import { Card, Typography, Space } from '@supabase/ui' +import { supabase } from '../utils/initSupabase' + +export default function Profile({ user }) { + return ( +
    + + + You're signed in + Email: {user.email} + + User data retrieved server-side (from Cookie in getServerSideProps): + + + +
    {JSON.stringify(user, null, 2)}
    +
    + + + + Static example with useSWR + + +
    +
    +
    + ) +} + +export async function getServerSideProps({ req }) { + const { user } = await supabase.auth.api.getUserByCookie(req) + + if (!user) { + // If no user, redirect to index. + return { props: {}, redirect: { destination: '/', permanent: false } } + } + + // If there is a user, return it. + return { props: { user } } +} diff --git a/examples/with-supabase-auth-realtime-db/postcss.config.js b/examples/with-supabase-auth-realtime-db/postcss.config.js deleted file mode 100644 index 33ad091d26d8..000000000000 --- a/examples/with-supabase-auth-realtime-db/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/examples/with-supabase-auth-realtime-db/style.css b/examples/with-supabase-auth-realtime-db/style.css new file mode 100644 index 000000000000..f1bf15cc2883 --- /dev/null +++ b/examples/with-supabase-auth-realtime-db/style.css @@ -0,0 +1,4 @@ +body { + background: #3d3d3d; + font-family: Helvetica, Arial, Sans-Serif; +} diff --git a/examples/with-supabase-auth-realtime-db/styles/style.scss b/examples/with-supabase-auth-realtime-db/styles/style.scss deleted file mode 100644 index e6f0385abc07..000000000000 --- a/examples/with-supabase-auth-realtime-db/styles/style.scss +++ /dev/null @@ -1,28 +0,0 @@ -@import '~tailwindcss/dist/base.css'; -@import '~tailwindcss/dist/components.css'; -@import '~tailwindcss/dist/utilities.css'; - -html, -body, -#__next, -.main { - max-height: 100vh; - height: 100vh; - margin: 0; - padding: 0; - overflow: hidden; -} -.channel-list { - li a:before { - content: '# '; - opacity: 0.5; - } - li a:hover { - opacity: 0.9; - } -} -.Messages { - overflow: auto; - display: flex; - flex-direction: column-reverse; -} diff --git a/examples/with-supabase-auth-realtime-db/tailwind.config.js b/examples/with-supabase-auth-realtime-db/tailwind.config.js deleted file mode 100644 index c00fe06cdbeb..000000000000 --- a/examples/with-supabase-auth-realtime-db/tailwind.config.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - purge: ['./pages/**/*.js', './components/**/*.js'], - darkMode: false, // or 'media' or 'class' - theme: { - extend: {}, - }, - variants: { - extend: {}, - }, - plugins: [], -} diff --git a/examples/with-supabase-auth-realtime-db/utils/initSupabase.js b/examples/with-supabase-auth-realtime-db/utils/initSupabase.js new file mode 100644 index 000000000000..75092fc556cd --- /dev/null +++ b/examples/with-supabase-auth-realtime-db/utils/initSupabase.js @@ -0,0 +1,6 @@ +import { createClient } from '@supabase/supabase-js' + +export const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY +) diff --git a/lerna.json b/lerna.json index b2b70c6c24c1..5274df2dcff3 100644 --- a/lerna.json +++ b/lerna.json @@ -17,5 +17,5 @@ "registry": "https://registry.npmjs.org/" } }, - "version": "10.0.6-canary.4" + "version": "10.0.6-canary.6" } diff --git a/packages/create-next-app/package.json b/packages/create-next-app/package.json index 04516d05c707..d56a927097fe 100644 --- a/packages/create-next-app/package.json +++ b/packages/create-next-app/package.json @@ -1,6 +1,6 @@ { "name": "create-next-app", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "keywords": [ "react", "next", diff --git a/packages/eslint-plugin-next/package.json b/packages/eslint-plugin-next/package.json index 062e873ea476..ead8a3cc9af8 100644 --- a/packages/eslint-plugin-next/package.json +++ b/packages/eslint-plugin-next/package.json @@ -1,6 +1,6 @@ { "name": "@next/eslint-plugin-next", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "description": "ESLint plugin for NextJS.", "main": "lib/index.js", "license": "MIT", diff --git a/packages/next-bundle-analyzer/package.json b/packages/next-bundle-analyzer/package.json index 759b5f4ab26e..d32fff2efe79 100644 --- a/packages/next-bundle-analyzer/package.json +++ b/packages/next-bundle-analyzer/package.json @@ -1,6 +1,6 @@ { "name": "@next/bundle-analyzer", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-codemod/package.json b/packages/next-codemod/package.json index c11fe602d88f..50a8e6aaab6b 100644 --- a/packages/next-codemod/package.json +++ b/packages/next-codemod/package.json @@ -1,6 +1,6 @@ { "name": "@next/codemod", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "license": "MIT", "dependencies": { "chalk": "4.1.0", diff --git a/packages/next-env/package.json b/packages/next-env/package.json index e308604ea44b..2dc21493c373 100644 --- a/packages/next-env/package.json +++ b/packages/next-env/package.json @@ -1,6 +1,6 @@ { "name": "@next/env", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "keywords": [ "react", "next", diff --git a/packages/next-mdx/package.json b/packages/next-mdx/package.json index 2a00a65fd685..cb5aecdc4130 100644 --- a/packages/next-mdx/package.json +++ b/packages/next-mdx/package.json @@ -1,6 +1,6 @@ { "name": "@next/mdx", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "main": "index.js", "license": "MIT", "repository": { diff --git a/packages/next-plugin-google-analytics/package.json b/packages/next-plugin-google-analytics/package.json index 63c028b0c707..a5a802de453c 100644 --- a/packages/next-plugin-google-analytics/package.json +++ b/packages/next-plugin-google-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-google-analytics", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-google-analytics" diff --git a/packages/next-plugin-sentry/package.json b/packages/next-plugin-sentry/package.json index 49121c83283d..3c4e33714f63 100644 --- a/packages/next-plugin-sentry/package.json +++ b/packages/next-plugin-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-sentry", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-sentry" diff --git a/packages/next-plugin-storybook/package.json b/packages/next-plugin-storybook/package.json index f800ce7f9a4e..2efc1c4ddba5 100644 --- a/packages/next-plugin-storybook/package.json +++ b/packages/next-plugin-storybook/package.json @@ -1,6 +1,6 @@ { "name": "@next/plugin-storybook", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "repository": { "url": "vercel/next.js", "directory": "packages/next-plugin-storybook" diff --git a/packages/next-polyfill-module/package.json b/packages/next-polyfill-module/package.json index f8e728eb7567..72d43361aadf 100644 --- a/packages/next-polyfill-module/package.json +++ b/packages/next-polyfill-module/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-module", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "description": "A standard library polyfill for ES Modules supporting browsers (Edge 16+, Firefox 60+, Chrome 61+, Safari 10.1+)", "main": "dist/polyfill-module.js", "license": "MIT", diff --git a/packages/next-polyfill-nomodule/package.json b/packages/next-polyfill-nomodule/package.json index fb81b787823e..4ee4f22090fb 100644 --- a/packages/next-polyfill-nomodule/package.json +++ b/packages/next-polyfill-nomodule/package.json @@ -1,6 +1,6 @@ { "name": "@next/polyfill-nomodule", - "version": "10.0.6-canary.4", + "version": "10.0.6-canary.6", "description": "A polyfill for non-dead, nomodule browsers.", "main": "dist/polyfill-nomodule.js", "license": "MIT", diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 873a0d1cc20d..2ea65df28332 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -875,6 +875,7 @@ export default async function getBaseWebpackConfig( 'next-serverless-loader', 'noop-loader', 'next-plugin-loader', + 'next-style-loader', ].reduce((alias, loader) => { // using multiple aliases to replace `resolveLoader.modules` alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader) diff --git a/packages/next/build/webpack/config/blocks/css/index.ts b/packages/next/build/webpack/config/blocks/css/index.ts index 0edf224e45de..73d2513ba851 100644 --- a/packages/next/build/webpack/config/blocks/css/index.ts +++ b/packages/next/build/webpack/config/blocks/css/index.ts @@ -53,7 +53,7 @@ export const css = curry(async function css( // To fix this, we use `resolve-url-loader` to rewrite the CSS // imports to real file paths. { - loader: require.resolve('resolve-url-loader'), + loader: require.resolve('next/dist/compiled/resolve-url-loader'), options: { // Source maps are not required here, but we may as well emit // them. diff --git a/packages/next/build/webpack/config/blocks/css/loaders/client.ts b/packages/next/build/webpack/config/blocks/css/loaders/client.ts index f09e66c1f1d2..c8d5bdc0bfda 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/client.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/client.ts @@ -10,7 +10,7 @@ export function getClientStyleLoader({ }): webpack.RuleSetUseItem { return isDevelopment ? { - loader: require.resolve('style-loader'), + loader: 'next-style-loader', options: { // By default, style-loader injects CSS into the bottom // of . This causes ordering problems between dev diff --git a/packages/next/build/webpack/config/blocks/css/loaders/global.ts b/packages/next/build/webpack/config/blocks/css/loaders/global.ts index 2efcb7559e75..1c3de40ec689 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/global.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/global.ts @@ -24,7 +24,7 @@ export function getGlobalCssLoader( // Resolve CSS `@import`s and `url()`s loaders.push({ - loader: require.resolve('css-loader'), + loader: require.resolve('next/dist/compiled/css-loader'), options: { importLoaders: 1 + preProcessors.length, sourceMap: true, diff --git a/packages/next/build/webpack/config/blocks/css/loaders/modules.ts b/packages/next/build/webpack/config/blocks/css/loaders/modules.ts index 94509987a3e4..b9888f165c22 100644 --- a/packages/next/build/webpack/config/blocks/css/loaders/modules.ts +++ b/packages/next/build/webpack/config/blocks/css/loaders/modules.ts @@ -25,7 +25,7 @@ export function getCssModuleLoader( // Resolve CSS `@import`s and `url()`s loaders.push({ - loader: require.resolve('css-loader'), + loader: require.resolve('next/dist/compiled/css-loader'), options: { importLoaders: 1 + preProcessors.length, sourceMap: true, diff --git a/packages/next/build/webpack/loaders/next-style-loader/LICENSE b/packages/next/build/webpack/loaders/next-style-loader/LICENSE new file mode 100644 index 000000000000..8c11fc7289b7 --- /dev/null +++ b/packages/next/build/webpack/loaders/next-style-loader/LICENSE @@ -0,0 +1,20 @@ +Copyright JS Foundation and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/next/build/webpack/loaders/next-style-loader/index.js b/packages/next/build/webpack/loaders/next-style-loader/index.js new file mode 100644 index 000000000000..47f56028b9c3 --- /dev/null +++ b/packages/next/build/webpack/loaders/next-style-loader/index.js @@ -0,0 +1,345 @@ +import loaderUtils from 'next/dist/compiled/loader-utils' +import path from 'path' +import { validate } from 'next/dist/compiled/schema-utils3' +import isEqualLocals from './runtime/isEqualLocals' + +const schema = { + type: 'object', + properties: { + injectType: { + description: + 'Allows to setup how styles will be injected into DOM (https://github.com/webpack-contrib/style-loader#injecttype).', + enum: [ + 'styleTag', + 'singletonStyleTag', + 'lazyStyleTag', + 'lazySingletonStyleTag', + 'linkTag', + ], + }, + attributes: { + description: + 'Adds custom attributes to tag (https://github.com/webpack-contrib/style-loader#attributes).', + type: 'object', + }, + insert: { + description: + 'Inserts `