diff --git a/.changeset/config.json b/.changeset/config.json index 89e46cd9..8947aa48 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,11 @@ { - "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", - "changelog": "@changesets/cli/changelog", - "commit": false, - "fixed": [], - "linked": [], - "access": "restricted", - "baseBranch": "next", - "updateInternalDependencies": "patch", - "ignore": ["@example/*"] + "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "next", + "updateInternalDependencies": "patch", + "ignore": ["@example/*"] } diff --git a/.changeset/pre.json b/.changeset/pre.json index 8c439e17..99be74d2 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,26 +1,26 @@ { - "mode": "pre", - "tag": "next", - "initialVersions": { - "@example/nextjs": "0.0.0", - "@example/nextjs-server-components": "0.0.0", - "@example/sveltekit": "0.0.0", - "config": "0.1.0", - "@supabase/auth-helpers-nextjs": "0.6.0", - "@supabase/auth-helpers-react": "0.3.1", - "@supabase/auth-helpers-remix": "0.1.8", - "@supabase/auth-helpers-shared": "0.3.3", - "@supabase/auth-helpers-sveltekit": "0.9.3", - "tsconfig": "0.1.1" - }, - "changesets": [ - "brave-lizards-thank", - "cyan-dancers-care", - "eighty-chefs-protect", - "eleven-radios-share", - "lazy-cows-shake", - "plenty-seas-build", - "silly-beds-watch", - "violet-frogs-know" - ] + "mode": "pre", + "tag": "next", + "initialVersions": { + "@example/nextjs": "0.0.0", + "@example/nextjs-server-components": "0.0.0", + "@example/sveltekit": "0.0.0", + "config": "0.1.0", + "@supabase/auth-helpers-nextjs": "0.6.0", + "@supabase/auth-helpers-react": "0.3.1", + "@supabase/auth-helpers-remix": "0.1.8", + "@supabase/auth-helpers-shared": "0.3.3", + "@supabase/auth-helpers-sveltekit": "0.9.3", + "tsconfig": "0.1.1" + }, + "changesets": [ + "brave-lizards-thank", + "cyan-dancers-care", + "eighty-chefs-protect", + "eleven-radios-share", + "lazy-cows-shake", + "plenty-seas-build", + "silly-beds-watch", + "violet-frogs-know" + ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a6e0acab --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI + +on: + push: + branches: + - '**' + +# cancel in-progress runs on new commits to same PR (gitub.event.number) +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.sha }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + checks: write + +jobs: + Lint: + strategy: + matrix: + node: ['18'] + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: pnpm/action-setup@v2.2.3 + with: + version: 8.1.0 + + - name: Set up Node + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - uses: wearerequired/lint-action@v2 + with: + tsc: true + prettier: true + + # Lint: + # strategy: + # matrix: + # node: ['18'] + + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + + # - uses: pnpm/action-setup@v2 + # with: + # version: 8.1.0 + + # - uses: actions/setup-node@v3 + # with: + # node-version: 18.x + # cache: pnpm + + # - run: pnpm install --frozen-lockfile + # - run: pnpm run lint + # - run: pnpm run check diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..5ba764e8 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpx lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..96b51bac --- /dev/null +++ b/.prettierignore @@ -0,0 +1,43 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage + +# next.js +.next/ +out/ +build + +# misc +.DS_Store +*.pem + +pnpm-lock.yaml + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Distribution directories +dist/ +package/ +docs/ + +# turbo +.turbo + +# svelte kit +.svelte-kit diff --git a/.prettierrc b/.prettierrc index 32ebab4e..96af2637 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,27 @@ { - "singleQuote": true, - "trailingComma": "none" + "useTabs": true, + "singleQuote": true, + "trailingComma": "none", + "printWidth": 100, + "overrides": [ + { + "files": ["*.svelte"], + "options": { + "bracketSameLine": false + } + }, + { + "files": ["*.md", "*.yaml", "*.yml"], + "options": { + "useTabs": false, + "tabWidth": 2 + } + }, + { + "files": ["**/CHANGELOG.md"], + "options": { + "requirePragma": true + } + } + ] } diff --git a/examples/nextjs-server-components/.eslintrc.json b/examples/nextjs-server-components/.eslintrc.json index bffb357a..72cc705c 100644 --- a/examples/nextjs-server-components/.eslintrc.json +++ b/examples/nextjs-server-components/.eslintrc.json @@ -1,3 +1,3 @@ { - "extends": "next/core-web-vitals" + "extends": "next/core-web-vitals" } diff --git a/examples/nextjs-server-components/.vscode/settings.json b/examples/nextjs-server-components/.vscode/settings.json index c3d9d94c..57d84225 100644 --- a/examples/nextjs-server-components/.vscode/settings.json +++ b/examples/nextjs-server-components/.vscode/settings.json @@ -1,4 +1,4 @@ { - "typescript.tsdk": "./node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true -} \ No newline at end of file + "typescript.tsdk": "./node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true +} diff --git a/examples/nextjs-server-components/app/globals.css b/examples/nextjs-server-components/app/globals.css index 4f184216..45214039 100644 --- a/examples/nextjs-server-components/app/globals.css +++ b/examples/nextjs-server-components/app/globals.css @@ -1,26 +1,26 @@ html, body { - padding: 0; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, + Fira Sans, Droid Sans, Helvetica Neue, sans-serif; } a { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } * { - box-sizing: border-box; + box-sizing: border-box; } @media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } - body { - color: white; - background: black; - } + html { + color-scheme: dark; + } + body { + color: white; + background: black; + } } diff --git a/examples/nextjs-server-components/app/head.tsx b/examples/nextjs-server-components/app/head.tsx index f11b2596..9aa05a50 100644 --- a/examples/nextjs-server-components/app/head.tsx +++ b/examples/nextjs-server-components/app/head.tsx @@ -1,10 +1,10 @@ export default function Head() { - return ( - <> - Create Next App - - - - - ); + return ( + <> + Create Next App + + + + + ); } diff --git a/examples/nextjs-server-components/app/layout.tsx b/examples/nextjs-server-components/app/layout.tsx index f968256f..730df2ef 100644 --- a/examples/nextjs-server-components/app/layout.tsx +++ b/examples/nextjs-server-components/app/layout.tsx @@ -14,31 +14,27 @@ export type TypedSupabaseClient = SupabaseClient; // do not cache this layout export const revalidate = 0; -export default async function RootLayout({ - children -}: { - children: React.ReactNode; -}) { - const supabase = createServerClient(); +export default async function RootLayout({ children }: { children: React.ReactNode }) { + const supabase = createServerClient(); - const { - data: { session } - } = await supabase.auth.getSession(); + const { + data: { session } + } = await supabase.auth.getSession(); - return ( - - {/* + return ( + + {/* will contain the components returned by the nearest parent head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head */} - - - - - - {children} - - - - ); + + + + + + {children} + + + + ); } diff --git a/examples/nextjs-server-components/app/optional-session/page.tsx b/examples/nextjs-server-components/app/optional-session/page.tsx index 3761dd11..1dc1844b 100644 --- a/examples/nextjs-server-components/app/optional-session/page.tsx +++ b/examples/nextjs-server-components/app/optional-session/page.tsx @@ -7,8 +7,8 @@ export const revalidate = 0; // this page will display with or without a user session export default async function OptionalSession() { - const supabase = createServerClient(); - const { data } = await supabase.from('posts').select('*'); + const supabase = createServerClient(); + const { data } = await supabase.from('posts').select('*'); - return
{JSON.stringify({ data }, null, 2)}
; + return
{JSON.stringify({ data }, null, 2)}
; } diff --git a/examples/nextjs-server-components/app/page.module.css b/examples/nextjs-server-components/app/page.module.css index a978c99d..a03725de 100644 --- a/examples/nextjs-server-components/app/page.module.css +++ b/examples/nextjs-server-components/app/page.module.css @@ -1,146 +1,146 @@ .container { - padding: 0 2rem; + padding: 0 2rem; } .main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + min-height: 100vh; + padding: 4rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } .footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; + display: flex; + flex: 1; + padding: 2rem 0; + border-top: 1px solid #eaeaea; + justify-content: center; + align-items: center; } .footer a { - display: flex; - justify-content: center; - align-items: center; - flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; } .title { - margin: 0; - line-height: 1.15; - font-size: 4rem; - font-style: normal; - font-weight: 800; - letter-spacing: -0.025em; + margin: 0; + line-height: 1.15; + font-size: 4rem; + font-style: normal; + font-weight: 800; + letter-spacing: -0.025em; } .title a { - text-decoration: none; - color: #0070f3; + text-decoration: none; + color: #0070f3; } .title a:hover, .title a:focus, .title a:active { - text-decoration: underline; + text-decoration: underline; } .title, .description { - text-align: center; + text-align: center; } .description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; + margin: 4rem 0; + line-height: 1.5; + font-size: 1.5rem; } .code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; } .grid { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - max-width: 1200px; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + max-width: 1200px; } .card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; + margin: 1rem; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; + max-width: 300px; } .card:hover, .card:focus, .card:active { - color: #0070f3; - border-color: #0070f3; + color: #0070f3; + border-color: #0070f3; } .card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; + margin: 0 0 1rem 0; + font-size: 1.5rem; } .card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; + margin: 0; + font-size: 1.25rem; + line-height: 1.5; } .logo { - height: 1em; - margin-left: 0.5rem; + height: 1em; + margin-left: 0.5rem; } @media (max-width: 600px) { - .grid { - width: 100%; - flex-direction: column; - } + .grid { + width: 100%; + flex-direction: column; + } } @media (prefers-color-scheme: dark) { - .title { - background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-fill-color: transparent; - } - .title a { - background: linear-gradient(180deg, #0070f3 0%, #0153af 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - text-fill-color: transparent; - } - .card, - .footer { - border-color: #222; - } - .code { - background: #111; - } - .logo img { - filter: invert(1); - } + .title { + background: linear-gradient(180deg, #ffffff 0%, #aaaaaa 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-fill-color: transparent; + } + .title a { + background: linear-gradient(180deg, #0070f3 0%, #0153af 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-fill-color: transparent; + } + .card, + .footer { + border-color: #222; + } + .code { + background: #111; + } + .logo img { + filter: invert(1); + } } diff --git a/examples/nextjs-server-components/app/page.tsx b/examples/nextjs-server-components/app/page.tsx index ba3e43d0..484af069 100644 --- a/examples/nextjs-server-components/app/page.tsx +++ b/examples/nextjs-server-components/app/page.tsx @@ -2,49 +2,48 @@ import Image from 'next/image'; import styles from './page.module.css'; export default function Home() { - return ( -
-
-

- Welcome to Supabase on{' '} - Next.js 13! -

+ return ( +
+
+

+ Welcome to Supabase on{' '} + Next.js 13! +

-

- Get started by editing{' '} - app/page.tsx -

+

+ Get started by editing app/page.tsx +

-
+ +

Realtime →

+

Merge server and client state with realtime.

+
+
+
- -
- ); + + + ); } diff --git a/examples/nextjs-server-components/app/realtime/new-post.tsx b/examples/nextjs-server-components/app/realtime/new-post.tsx index ebe7682b..df30e4fc 100644 --- a/examples/nextjs-server-components/app/realtime/new-post.tsx +++ b/examples/nextjs-server-components/app/realtime/new-post.tsx @@ -3,27 +3,27 @@ import { useSupabase } from '../../components/supabase-provider'; export default function NewPost() { - const { supabase, session } = useSupabase(); + const { supabase, session } = useSupabase(); - const handleSubmit = async (e: React.SyntheticEvent) => { - e.preventDefault(); - const target = e.target as typeof e.target & { - post: { value: string }; - }; - const post = target.post.value; + const handleSubmit = async (e: React.SyntheticEvent) => { + e.preventDefault(); + const target = e.target as typeof e.target & { + post: { value: string }; + }; + const post = target.post.value; - await supabase.from('posts').insert({ content: post }); - // no need to refresh, as we are subscribed to db changes in `./realtime-posts.tsx` - }; + await supabase.from('posts').insert({ content: post }); + // no need to refresh, as we are subscribed to db changes in `./realtime-posts.tsx` + }; - return session ? ( - <> -
- - -
- - ) : ( -

Sign in to see posts

- ); + return session ? ( + <> +
+ + +
+ + ) : ( +

Sign in to see posts

+ ); } diff --git a/examples/nextjs-server-components/app/realtime/page.tsx b/examples/nextjs-server-components/app/realtime/page.tsx index 07317c69..27746ef9 100644 --- a/examples/nextjs-server-components/app/realtime/page.tsx +++ b/examples/nextjs-server-components/app/realtime/page.tsx @@ -10,16 +10,16 @@ export const revalidate = 0; // this component fetches the current posts server-side // and subscribes to new posts client-side export default async function Realtime() { - const supabase = createServerClient(); - const { data } = await supabase.from('posts').select('*'); + const supabase = createServerClient(); + const { data } = await supabase.from('posts').select('*'); - // data can be passed from server components to client components - // this allows us to fetch the initial posts before rendering the page - // our component will then subscribe to new posts client-side - return ( - <> - - - - ); + // data can be passed from server components to client components + // this allows us to fetch the initial posts before rendering the page + // our component will then subscribe to new posts client-side + return ( + <> + + + + ); } diff --git a/examples/nextjs-server-components/app/realtime/realtime-posts.tsx b/examples/nextjs-server-components/app/realtime/realtime-posts.tsx index 48e3a6b1..28ce0a08 100644 --- a/examples/nextjs-server-components/app/realtime/realtime-posts.tsx +++ b/examples/nextjs-server-components/app/realtime/realtime-posts.tsx @@ -10,36 +10,30 @@ type Post = Database['public']['Tables']['posts']['Row']; // realtime subscriptions need to be set up client-side // this component takes initial posts as props and automatically // updates when new posts are inserted into Supabase's `posts` table -export default function RealtimePosts({ - serverPosts -}: { - serverPosts: Post[]; -}) { - const [posts, setPosts] = useState(serverPosts); - const { supabase } = useSupabase(); - - useEffect(() => { - // this overwrites `posts` any time the `serverPosts` prop changes - // this happens when the parent Server Component is re-rendered - setPosts(serverPosts); - }, [serverPosts]); - - useEffect(() => { - // ensure you have enabled replication on the `posts` table - // https://app.supabase.com/project/_/database/replication - const channel = supabase - .channel('*') - .on( - 'postgres_changes', - { event: 'INSERT', schema: 'public', table: 'posts' }, - (payload) => setPosts([...posts, payload.new as Post]) - ) - .subscribe(); - - return () => { - supabase.removeChannel(channel); - }; - }, [supabase, setPosts, posts]); - - return
{JSON.stringify(posts, null, 2)}
; +export default function RealtimePosts({ serverPosts }: { serverPosts: Post[] }) { + const [posts, setPosts] = useState(serverPosts); + const { supabase } = useSupabase(); + + useEffect(() => { + // this overwrites `posts` any time the `serverPosts` prop changes + // this happens when the parent Server Component is re-rendered + setPosts(serverPosts); + }, [serverPosts]); + + useEffect(() => { + // ensure you have enabled replication on the `posts` table + // https://app.supabase.com/project/_/database/replication + const channel = supabase + .channel('*') + .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) => + setPosts([...posts, payload.new as Post]) + ) + .subscribe(); + + return () => { + supabase.removeChannel(channel); + }; + }, [supabase, setPosts, posts]); + + return
{JSON.stringify(posts, null, 2)}
; } diff --git a/examples/nextjs-server-components/app/required-session/page.tsx b/examples/nextjs-server-components/app/required-session/page.tsx index c184d037..1c7fcd74 100644 --- a/examples/nextjs-server-components/app/required-session/page.tsx +++ b/examples/nextjs-server-components/app/required-session/page.tsx @@ -8,8 +8,8 @@ export const revalidate = 0; // the user will be redirected to the landing page if they are not signed in // check middleware.tsx to see how this routing rule is set export default async function RequiredSession() { - const supabase = createServerClient(); - const { data } = await supabase.from('posts').select('*'); + const supabase = createServerClient(); + const { data } = await supabase.from('posts').select('*'); - return
{JSON.stringify({ data }, null, 2)}
; + return
{JSON.stringify({ data }, null, 2)}
; } diff --git a/examples/nextjs-server-components/components/login.tsx b/examples/nextjs-server-components/components/login.tsx index a6729bfa..5dc3add2 100644 --- a/examples/nextjs-server-components/components/login.tsx +++ b/examples/nextjs-server-components/components/login.tsx @@ -4,46 +4,46 @@ import { useSupabase } from './supabase-provider'; // Supabase auth needs to be triggered client-side export default function Login() { - const { supabase, session } = useSupabase(); - - const handleEmailLogin = async () => { - const { error } = await supabase.auth.signInWithPassword({ - email: 'jon@supabase.com', - password: 'password' - }); - - if (error) { - console.log({ error }); - } - }; - - const handleGitHubLogin = async () => { - const { error } = await supabase.auth.signInWithOAuth({ - provider: 'github' - }); - - if (error) { - console.log({ error }); - } - }; - - const handleLogout = async () => { - const { error } = await supabase.auth.signOut(); - - if (error) { - console.log({ error }); - } - }; - - // this `session` is from the root loader - server-side - // therefore, it can safely be used to conditionally render - // SSR pages without issues with hydration - return session ? ( - - ) : ( - <> - - - - ); + const { supabase, session } = useSupabase(); + + const handleEmailLogin = async () => { + const { error } = await supabase.auth.signInWithPassword({ + email: 'jon@supabase.com', + password: 'password' + }); + + if (error) { + console.log({ error }); + } + }; + + const handleGitHubLogin = async () => { + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'github' + }); + + if (error) { + console.log({ error }); + } + }; + + const handleLogout = async () => { + const { error } = await supabase.auth.signOut(); + + if (error) { + console.log({ error }); + } + }; + + // this `session` is from the root loader - server-side + // therefore, it can safely be used to conditionally render + // SSR pages without issues with hydration + return session ? ( + + ) : ( + <> + + + + ); } diff --git a/examples/nextjs-server-components/components/supabase-listener.tsx b/examples/nextjs-server-components/components/supabase-listener.tsx index e2e0fae5..ae7b012b 100644 --- a/examples/nextjs-server-components/components/supabase-listener.tsx +++ b/examples/nextjs-server-components/components/supabase-listener.tsx @@ -8,30 +8,26 @@ import { useSupabase } from './supabase-provider'; // this method avoids the need to pass a session down to child components // in order to re-render when the user's session changes // #elegant! -export default function SupabaseListener({ - serverAccessToken -}: { - serverAccessToken?: string; -}) { - const { supabase } = useSupabase(); - const router = useRouter(); +export default function SupabaseListener({ serverAccessToken }: { serverAccessToken?: string }) { + const { supabase } = useSupabase(); + const router = useRouter(); - useEffect(() => { - const { - data: { subscription } - } = supabase.auth.onAuthStateChange((event, session) => { - if (session?.access_token !== serverAccessToken) { - // server and client are out of sync - // reload the page to fetch fresh server data - // https://beta.nextjs.org/docs/data-fetching/mutating - router.refresh(); - } - }); + useEffect(() => { + const { + data: { subscription } + } = supabase.auth.onAuthStateChange((event, session) => { + if (session?.access_token !== serverAccessToken) { + // server and client are out of sync + // reload the page to fetch fresh server data + // https://beta.nextjs.org/docs/data-fetching/mutating + router.refresh(); + } + }); - return () => { - subscription.unsubscribe(); - }; - }, [serverAccessToken, router, supabase]); + return () => { + subscription.unsubscribe(); + }; + }, [serverAccessToken, router, supabase]); - return null; + return null; } diff --git a/examples/nextjs-server-components/components/supabase-provider.tsx b/examples/nextjs-server-components/components/supabase-provider.tsx index ba068416..9c94f2f5 100644 --- a/examples/nextjs-server-components/components/supabase-provider.tsx +++ b/examples/nextjs-server-components/components/supabase-provider.tsx @@ -8,27 +8,27 @@ import { createBrowserClient } from '../utils/supabase-browser'; type MaybeSession = Session | null; type SupabaseContext = { - supabase: TypedSupabaseClient; - session: MaybeSession; + supabase: TypedSupabaseClient; + session: MaybeSession; }; // @ts-ignore const Context = createContext(); export default function SupabaseProvider({ - children, - session + children, + session }: { - children: React.ReactNode; - session: MaybeSession; + children: React.ReactNode; + session: MaybeSession; }) { - const [supabase] = useState(() => createBrowserClient()); + const [supabase] = useState(() => createBrowserClient()); - return ( - - <>{children} - - ); + return ( + + <>{children} + + ); } export const useSupabase = () => useContext(Context); diff --git a/examples/nextjs-server-components/db_types.ts b/examples/nextjs-server-components/db_types.ts index 3986413a..64835236 100644 --- a/examples/nextjs-server-components/db_types.ts +++ b/examples/nextjs-server-components/db_types.ts @@ -1,43 +1,37 @@ -export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json } - | Json[]; +export type Json = string | number | boolean | null | { [key: string]: Json } | Json[]; export interface Database { - public: { - Tables: { - posts: { - Row: { - id: string; - created_at: string; - content: string; - user_id: string; - }; - Insert: { - id?: string; - created_at?: string; - content: string; - user_id?: string; - }; - Update: { - id?: string; - created_at?: string; - content?: string; - user_id?: string; - }; - }; - }; - Views: { - [_ in never]: never; - }; - Functions: { - [_ in never]: never; - }; - Enums: { - [_ in never]: never; - }; - }; + public: { + Tables: { + posts: { + Row: { + id: string; + created_at: string; + content: string; + user_id: string; + }; + Insert: { + id?: string; + created_at?: string; + content: string; + user_id?: string; + }; + Update: { + id?: string; + created_at?: string; + content?: string; + user_id?: string; + }; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + [_ in never]: never; + }; + Enums: { + [_ in never]: never; + }; + }; } diff --git a/examples/nextjs-server-components/middleware.tsx b/examples/nextjs-server-components/middleware.tsx index cbddfe51..54785a1f 100644 --- a/examples/nextjs-server-components/middleware.tsx +++ b/examples/nextjs-server-components/middleware.tsx @@ -5,21 +5,21 @@ import type { NextRequest } from 'next/server'; // this middleware refreshes the user's session and must be run // for any Server Component route that uses `createServerComponentSupabaseClient` export async function middleware(req: NextRequest) { - const res = NextResponse.next(); + const res = NextResponse.next(); - const supabase = createMiddlewareSupabaseClient({ req, res }); + const supabase = createMiddlewareSupabaseClient({ req, res }); - const { - data: { session } - } = await supabase.auth.getSession(); + const { + data: { session } + } = await supabase.auth.getSession(); - if (!session && req.nextUrl.pathname.startsWith('/required-session')) { - // Auth condition not met, redirect to home page. - const redirectUrl = req.nextUrl.clone(); - redirectUrl.pathname = '/'; - redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname); - return NextResponse.redirect(redirectUrl); - } + if (!session && req.nextUrl.pathname.startsWith('/required-session')) { + // Auth condition not met, redirect to home page. + const redirectUrl = req.nextUrl.clone(); + redirectUrl.pathname = '/'; + redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname); + return NextResponse.redirect(redirectUrl); + } - return res; + return res; } diff --git a/examples/nextjs-server-components/next.config.js b/examples/nextjs-server-components/next.config.js index dafb0c88..0f7a6ac0 100644 --- a/examples/nextjs-server-components/next.config.js +++ b/examples/nextjs-server-components/next.config.js @@ -1,8 +1,8 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - appDir: true, - }, -} + experimental: { + appDir: true + } +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/examples/nextjs-server-components/package.json b/examples/nextjs-server-components/package.json index 71249272..1a77f6a8 100644 --- a/examples/nextjs-server-components/package.json +++ b/examples/nextjs-server-components/package.json @@ -1,29 +1,29 @@ { - "name": "@example/nextjs-server-components", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "next dev", - "build:example": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@supabase/auth-helpers-nextjs": "workspace:*", - "@types/node": "18.11.9", - "@types/react": "18.0.25", - "@types/react-dom": "18.0.8", - "config": "workspace:*", - "eslint": "8.27.0", - "eslint-config-next": "13.0.2", - "next": "13.0.2", - "react": "18.2.0", - "react-dom": "18.2.0", - "server-only": "^0.0.1", - "tsconfig": "workspace:*", - "typescript": "4.8.4" - }, - "devDependencies": { - "json5": ">=1.0.2" - } + "name": "@example/nextjs-server-components", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build:example": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@supabase/auth-helpers-nextjs": "workspace:*", + "@types/node": "18.11.9", + "@types/react": "18.0.25", + "@types/react-dom": "18.0.8", + "config": "workspace:*", + "eslint": "8.27.0", + "eslint-config-next": "13.0.2", + "next": "13.0.2", + "react": "18.2.0", + "react-dom": "18.2.0", + "server-only": "^0.0.1", + "tsconfig": "workspace:*", + "typescript": "4.8.4" + }, + "devDependencies": { + "json5": ">=1.0.2" + } } diff --git a/examples/nextjs-server-components/pages/api/hello.ts b/examples/nextjs-server-components/pages/api/hello.ts index 89e4d6bd..fd02e058 100644 --- a/examples/nextjs-server-components/pages/api/hello.ts +++ b/examples/nextjs-server-components/pages/api/hello.ts @@ -2,12 +2,9 @@ import type { NextApiRequest, NextApiResponse } from 'next'; type Data = { - name: string; + name: string; }; -export default function handler( - req: NextApiRequest, - res: NextApiResponse -) { - res.status(200).json({ name: 'John Doe' }); +export default function handler(req: NextApiRequest, res: NextApiResponse) { + res.status(200).json({ name: 'John Doe' }); } diff --git a/examples/nextjs-server-components/tsconfig.json b/examples/nextjs-server-components/tsconfig.json index 6ef5cd57..7dc9d477 100644 --- a/examples/nextjs-server-components/tsconfig.json +++ b/examples/nextjs-server-components/tsconfig.json @@ -1,25 +1,25 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ] - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/examples/nextjs-server-components/utils/supabase-browser.ts b/examples/nextjs-server-components/utils/supabase-browser.ts index ce2eb459..5ae1ede8 100644 --- a/examples/nextjs-server-components/utils/supabase-browser.ts +++ b/examples/nextjs-server-components/utils/supabase-browser.ts @@ -1,5 +1,4 @@ import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'; import { Database } from '../db_types'; -export const createBrowserClient = () => - createBrowserSupabaseClient(); +export const createBrowserClient = () => createBrowserSupabaseClient(); diff --git a/examples/nextjs-server-components/utils/supabase-server.ts b/examples/nextjs-server-components/utils/supabase-server.ts index 76d14d2f..af895cf8 100644 --- a/examples/nextjs-server-components/utils/supabase-server.ts +++ b/examples/nextjs-server-components/utils/supabase-server.ts @@ -3,7 +3,7 @@ import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-next import { Database } from '../db_types'; export const createServerClient = () => - createServerComponentSupabaseClient({ - headers, - cookies - }); + createServerComponentSupabaseClient({ + headers, + cookies + }); diff --git a/examples/nextjs/.eslintrc.json b/examples/nextjs/.eslintrc.json index bffb357a..72cc705c 100644 --- a/examples/nextjs/.eslintrc.json +++ b/examples/nextjs/.eslintrc.json @@ -1,3 +1,3 @@ { - "extends": "next/core-web-vitals" + "extends": "next/core-web-vitals" } diff --git a/examples/nextjs/db_types.ts b/examples/nextjs/db_types.ts index 65bc3356..aedb3534 100644 --- a/examples/nextjs/db_types.ts +++ b/examples/nextjs/db_types.ts @@ -1,66 +1,60 @@ -export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json } - | Json[]; +export type Json = string | number | boolean | null | { [key: string]: Json } | Json[]; export interface Database { - public: { - Tables: { - test: { - Row: { - created_at: string | null; - id: number; - }; - Insert: { - created_at?: string | null; - id?: number; - }; - Update: { - created_at?: string | null; - id?: number; - }; - }; - users: { - Row: { - city: string | null; - country: string | null; - created_at: string; - full_name: string | null; - id: string; - username: string | null; - }; - Insert: { - city?: string | null; - country?: string | null; - created_at?: string; - full_name?: string | null; - id: string; - username?: string | null; - }; - Update: { - city?: string | null; - country?: string | null; - created_at?: string; - full_name?: string | null; - id?: string; - username?: string | null; - }; - }; - }; - Views: { - [_ in never]: never; - }; - Functions: { - [_ in never]: never; - }; - Enums: { - [_ in never]: never; - }; - CompositeTypes: { - [_ in never]: never; - }; - }; + public: { + Tables: { + test: { + Row: { + created_at: string | null; + id: number; + }; + Insert: { + created_at?: string | null; + id?: number; + }; + Update: { + created_at?: string | null; + id?: number; + }; + }; + users: { + Row: { + city: string | null; + country: string | null; + created_at: string; + full_name: string | null; + id: string; + username: string | null; + }; + Insert: { + city?: string | null; + country?: string | null; + created_at?: string; + full_name?: string | null; + id: string; + username?: string | null; + }; + Update: { + city?: string | null; + country?: string | null; + created_at?: string; + full_name?: string | null; + id?: string; + username?: string | null; + }; + }; + }; + Views: { + [_ in never]: never; + }; + Functions: { + [_ in never]: never; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; } diff --git a/examples/nextjs/middleware.ts b/examples/nextjs/middleware.ts index 9433cfce..be0491ef 100644 --- a/examples/nextjs/middleware.ts +++ b/examples/nextjs/middleware.ts @@ -3,28 +3,28 @@ import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function middleware(req: NextRequest) { - // We need to create a response and hand it to the supabase client to be able to modify the response headers. - const res = NextResponse.next(); - // Create authenticated Supabase Client - const supabase = createMiddlewareSupabaseClient({ req, res }); - // Check if we have a session - const { - data: { session } - } = await supabase.auth.getSession(); + // We need to create a response and hand it to the supabase client to be able to modify the response headers. + const res = NextResponse.next(); + // Create authenticated Supabase Client + const supabase = createMiddlewareSupabaseClient({ req, res }); + // Check if we have a session + const { + data: { session } + } = await supabase.auth.getSession(); - // Check auth condition - if (session?.user.email?.endsWith('@gmail.com')) { - // Authentication successful, forward request to protected route. - return res; - } + // Check auth condition + if (session?.user.email?.endsWith('@gmail.com')) { + // Authentication successful, forward request to protected route. + return res; + } - // Auth condition not met, redirect to home page. - const redirectUrl = req.nextUrl.clone(); - redirectUrl.pathname = '/'; - redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname); - return NextResponse.redirect(redirectUrl); + // Auth condition not met, redirect to home page. + const redirectUrl = req.nextUrl.clone(); + redirectUrl.pathname = '/'; + redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname); + return NextResponse.redirect(redirectUrl); } export const config = { - matcher: '/middleware-protected' + matcher: '/middleware-protected' }; diff --git a/examples/nextjs/next.config.js b/examples/nextjs/next.config.js index a843cbee..b8e3b7ba 100644 --- a/examples/nextjs/next.config.js +++ b/examples/nextjs/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true, -} + reactStrictMode: true +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index ccb1931e..9f06e9c8 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -1,36 +1,36 @@ { - "name": "@example/nextjs", - "version": "0.0.0", - "private": true, - "scripts": { - "dev": "next dev", - "build:example": "next build", - "start": "next start", - "lint": "next lint", - "clean:all": "rimraf .next node_modules" - }, - "dependencies": { - "@supabase/auth-helpers-nextjs": "workspace:*", - "@supabase/auth-helpers-react": "workspace:*", - "@supabase/auth-ui-react": "^0.2.6", - "@supabase/supabase-js": "^2.19.0", - "next": "^12.2.5", - "react": "17.0.2", - "react-dom": "17.0.2" - }, - "devDependencies": { - "@babel/core": ">=7.0.0 <8.0.0", - "@types/node": "^17.0.12", - "@types/react": "17.0.37", - "config": "workspace:*", - "encoding": "^0.1.13", - "eslint": "7.32.0", - "next-transpile-modules": "9.0.0", - "rimraf": "^3.0.2", - "tsconfig": "workspace:*", - "typescript": "^4.5.3" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0 <8.0.0" - } + "name": "@example/nextjs", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "next dev", + "build:example": "next build", + "start": "next start", + "lint": "next lint", + "clean:all": "rimraf .next node_modules" + }, + "dependencies": { + "@supabase/auth-helpers-nextjs": "workspace:*", + "@supabase/auth-helpers-react": "workspace:*", + "@supabase/auth-ui-react": "^0.2.6", + "@supabase/supabase-js": "^2.19.0", + "next": "^12.2.5", + "react": "17.0.2", + "react-dom": "17.0.2" + }, + "devDependencies": { + "@babel/core": ">=7.0.0 <8.0.0", + "@types/node": "^17.0.12", + "@types/react": "17.0.37", + "config": "workspace:*", + "encoding": "^0.1.13", + "eslint": "7.32.0", + "next-transpile-modules": "9.0.0", + "rimraf": "^3.0.2", + "tsconfig": "workspace:*", + "typescript": "^4.5.3" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0 <8.0.0" + } } diff --git a/examples/nextjs/pages/_app.tsx b/examples/nextjs/pages/_app.tsx index 5f0d6567..9eaf4a2c 100644 --- a/examples/nextjs/pages/_app.tsx +++ b/examples/nextjs/pages/_app.tsx @@ -1,40 +1,32 @@ import { useRouter } from 'next/router'; -import { - createBrowserSupabaseClient, - Session -} from '@supabase/auth-helpers-nextjs'; +import { createBrowserSupabaseClient, Session } from '@supabase/auth-helpers-nextjs'; import { SessionContextProvider } from '@supabase/auth-helpers-react'; import type { AppProps } from 'next/app'; import { useState } from 'react'; import { Database } from '../db_types'; import '../styles/globals.css'; -function MyApp({ - Component, - pageProps -}: AppProps<{ initialSession: Session }>) { - const router = useRouter(); - const [supabaseClient] = useState(() => - createBrowserSupabaseClient() - ); +function MyApp({ Component, pageProps }: AppProps<{ initialSession: Session }>) { + const router = useRouter(); + const [supabaseClient] = useState(() => createBrowserSupabaseClient()); - return ( - - + return ( + + - - - ); + + + ); } export default MyApp; diff --git a/examples/nextjs/pages/api/callback.ts b/examples/nextjs/pages/api/callback.ts index 3b19c528..048c6b9d 100644 --- a/examples/nextjs/pages/api/callback.ts +++ b/examples/nextjs/pages/api/callback.ts @@ -3,16 +3,16 @@ import { NextApiHandler } from 'next'; import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'; const callback: NextApiHandler = async (req, res) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient({ req, res }); + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient({ req, res }); - const code = req.query.code; + const code = req.query.code; - if (typeof code === 'string') { - await supabase.auth.exchangeCodeForSession(code); - } + if (typeof code === 'string') { + await supabase.auth.exchangeCodeForSession(code); + } - res.redirect('/'); + res.redirect('/'); }; export default callback; diff --git a/examples/nextjs/pages/api/protected-route.ts b/examples/nextjs/pages/api/protected-route.ts index 7e97a2f9..4bf899c1 100644 --- a/examples/nextjs/pages/api/protected-route.ts +++ b/examples/nextjs/pages/api/protected-route.ts @@ -3,23 +3,22 @@ import { NextApiHandler } from 'next'; import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'; const ProtectedRoute: NextApiHandler = async (req, res) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient({ req, res }); - // Check if we have a session - const { - data: { session } - } = await supabase.auth.getSession(); + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient({ req, res }); + // Check if we have a session + const { + data: { session } + } = await supabase.auth.getSession(); - if (!session) - return res.status(401).json({ - error: 'not_authenticated', - description: - 'The user does not have an active session or is not authenticated' - }); + if (!session) + return res.status(401).json({ + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated' + }); - // Run queries with RLS on the server - const { data } = await supabase.from('users').select('*'); - res.json(data); + // Run queries with RLS on the server + const { data } = await supabase.from('users').select('*'); + res.json(data); }; export default ProtectedRoute; diff --git a/examples/nextjs/pages/github-provider-token.tsx b/examples/nextjs/pages/github-provider-token.tsx index 0a531c31..4814066d 100644 --- a/examples/nextjs/pages/github-provider-token.tsx +++ b/examples/nextjs/pages/github-provider-token.tsx @@ -1,61 +1,51 @@ // pages/protected-page.js -import { - User, - createServerSupabaseClient -} from '@supabase/auth-helpers-nextjs'; +import { User, createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'; import { GetServerSidePropsContext } from 'next'; import Link from 'next/link'; -export default function ProtectedPage({ - user, - allRepos -}: { - user: User; - allRepos: any; -}) { - return ( - <> -

- [Home] | [ - withPageAuth] -

-
Protected content for {user.email}
-

Data fetched with provider token:

-
{JSON.stringify(allRepos, null, 2)}
-

user:

-
{JSON.stringify(user, null, 2)}
- - ); +export default function ProtectedPage({ user, allRepos }: { user: User; allRepos: any }) { + return ( + <> +

+ [Home] | [withPageAuth] +

+
Protected content for {user.email}
+

Data fetched with provider token:

+
{JSON.stringify(allRepos, null, 2)}
+

user:

+
{JSON.stringify(user, null, 2)}
+ + ); } export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx); - // Check if we have a session - const { - data: { session } - } = await supabase.auth.getSession(); + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx); + // Check if we have a session + const { + data: { session } + } = await supabase.auth.getSession(); - if (!session) - return { - redirect: { - destination: '/', - permanent: false - } - }; + if (!session) + return { + redirect: { + destination: '/', + permanent: false + } + }; - // Retrieve provider_token & logged in user's third-party id from metadata - const { provider_token, user } = session; - const userId = user.user_metadata.user_name; + // Retrieve provider_token & logged in user's third-party id from metadata + const { provider_token, user } = session; + const userId = user.user_metadata.user_name; - const allRepos = await ( - await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, { - method: 'GET', - headers: { - Authorization: `token ${provider_token}` - } - }) - ).json(); + const allRepos = await ( + await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, { + method: 'GET', + headers: { + Authorization: `token ${provider_token}` + } + }) + ).json(); - return { props: { user, allRepos } }; + return { props: { user, allRepos } }; }; diff --git a/examples/nextjs/pages/index.tsx b/examples/nextjs/pages/index.tsx index a2ab493f..69504667 100644 --- a/examples/nextjs/pages/index.tsx +++ b/examples/nextjs/pages/index.tsx @@ -1,8 +1,4 @@ -import { - useSessionContext, - useSupabaseClient, - useUser -} from '@supabase/auth-helpers-react'; +import { useSessionContext, useSupabaseClient, useUser } from '@supabase/auth-helpers-react'; import { Auth, ThemeSupa } from '@supabase/auth-ui-react'; import type { NextPage } from 'next'; import Link from 'next/link'; @@ -10,74 +6,68 @@ import { useEffect, useState } from 'react'; import { Database } from '../db_types'; const LoginPage: NextPage = () => { - const { isLoading, session, error } = useSessionContext(); - const user = useUser(); - const supabaseClient = useSupabaseClient(); + const { isLoading, session, error } = useSessionContext(); + const user = useUser(); + const supabaseClient = useSupabaseClient(); - const [data, setData] = useState(null); + const [data, setData] = useState(null); - useEffect(() => { - async function loadData() { - const { data } = await supabaseClient.from('users').select('*').single(); - setData(data); - } + useEffect(() => { + async function loadData() { + const { data } = await supabaseClient.from('users').select('*').single(); + setData(data); + } - if (user) loadData(); - }, [user, supabaseClient]); + if (user) loadData(); + }, [user, supabaseClient]); - if (!session) - return ( - <> - {error &&

{error.message}

} - {isLoading ?

Loading...

:

Loaded!

} - - - - ); + if (!session) + return ( + <> + {error &&

{error.message}

} + {isLoading ?

Loading...

:

Loaded!

} + + + + ); - return ( - <> -

- [getServerSideProps] | [ - server-side RLS] |{' '} - - -

- {isLoading ?

Loading...

:

Loaded!

} -

user:

-
{JSON.stringify(session, null, 2)}
-

client-side data fetching with RLS

-
{JSON.stringify(data, null, 2)}
- - ); + return ( + <> +

+ [getServerSideProps] | [ + server-side RLS] |{' '} + + +

+ {isLoading ?

Loading...

:

Loaded!

} +

user:

+
{JSON.stringify(session, null, 2)}
+

client-side data fetching with RLS

+
{JSON.stringify(data, null, 2)}
+ + ); }; export default LoginPage; diff --git a/examples/nextjs/pages/profile.tsx b/examples/nextjs/pages/profile.tsx index a7ebd23d..dc0926eb 100644 --- a/examples/nextjs/pages/profile.tsx +++ b/examples/nextjs/pages/profile.tsx @@ -1,44 +1,40 @@ // pages/profile.js -import { - createServerSupabaseClient, - User -} from '@supabase/auth-helpers-nextjs'; +import { createServerSupabaseClient, User } from '@supabase/auth-helpers-nextjs'; import { GetServerSidePropsContext } from 'next'; import Link from 'next/link'; export default function Profile({ user }: { user: User }) { - return ( - <> -

- [Home] | [ - server-side RLS] -

-
Hello {user.email}
-
{JSON.stringify(user, null, 2)}
- - ); + return ( + <> +

+ [Home] | [server-side RLS] +

+
Hello {user.email}
+
{JSON.stringify(user, null, 2)}
+ + ); } export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx); - // Check if we have a session - const { - data: { session } - } = await supabase.auth.getSession(); + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx); + // Check if we have a session + const { + data: { session } + } = await supabase.auth.getSession(); - if (!session) - return { - redirect: { - destination: '/', - permanent: false - } - }; + if (!session) + return { + redirect: { + destination: '/', + permanent: false + } + }; - return { - props: { - initialSession: session, - user: session.user - } - }; + return { + props: { + initialSession: session, + user: session.user + } + }; }; diff --git a/examples/nextjs/pages/protected-page.tsx b/examples/nextjs/pages/protected-page.tsx index f1f80c9c..1b8952c4 100644 --- a/examples/nextjs/pages/protected-page.tsx +++ b/examples/nextjs/pages/protected-page.tsx @@ -1,57 +1,47 @@ // pages/protected-page.js -import { - createServerSupabaseClient, - User -} from '@supabase/auth-helpers-nextjs'; +import { createServerSupabaseClient, User } from '@supabase/auth-helpers-nextjs'; import { GetServerSidePropsContext } from 'next'; import Link from 'next/link'; -export default function ProtectedPage({ - user, - data -}: { - user: User; - data: any; -}) { - return ( - <> -

- [Home] | [ - getServerSideProps] -

-
Protected content for {user?.email}
-

server-side fetched data with RLS:

-
{JSON.stringify(data, null, 2)}
-

user:

-
{JSON.stringify(user, null, 2)}
- - ); +export default function ProtectedPage({ user, data }: { user: User; data: any }) { + return ( + <> +

+ [Home] | [getServerSideProps] +

+
Protected content for {user?.email}
+

server-side fetched data with RLS:

+
{JSON.stringify(data, null, 2)}
+

user:

+
{JSON.stringify(user, null, 2)}
+ + ); } export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx); - // Check if we have a session - const { - data: { session } - } = await supabase.auth.getSession(); + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx); + // Check if we have a session + const { + data: { session } + } = await supabase.auth.getSession(); - if (!session) - return { - redirect: { - destination: '/', - permanent: false - } - }; + if (!session) + return { + redirect: { + destination: '/', + permanent: false + } + }; - // Run queries with RLS on the server - const { data } = await supabase.from('users').select('*'); + // Run queries with RLS on the server + const { data } = await supabase.from('users').select('*'); - return { - props: { - initialSession: session, - user: session.user, - data: data ?? [] - } - }; + return { + props: { + initialSession: session, + user: session.user, + data: data ?? [] + } + }; }; diff --git a/examples/nextjs/styles/Home.module.css b/examples/nextjs/styles/Home.module.css index 32a57d52..e3800260 100644 --- a/examples/nextjs/styles/Home.module.css +++ b/examples/nextjs/styles/Home.module.css @@ -1,116 +1,116 @@ .container { - padding: 0 2rem; + padding: 0 2rem; } .main { - min-height: 100vh; - padding: 4rem 0; - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + min-height: 100vh; + padding: 4rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } .footer { - display: flex; - flex: 1; - padding: 2rem 0; - border-top: 1px solid #eaeaea; - justify-content: center; - align-items: center; + display: flex; + flex: 1; + padding: 2rem 0; + border-top: 1px solid #eaeaea; + justify-content: center; + align-items: center; } .footer a { - display: flex; - justify-content: center; - align-items: center; - flex-grow: 1; + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; } .title a { - color: #0070f3; - text-decoration: none; + color: #0070f3; + text-decoration: none; } .title a:hover, .title a:focus, .title a:active { - text-decoration: underline; + text-decoration: underline; } .title { - margin: 0; - line-height: 1.15; - font-size: 4rem; + margin: 0; + line-height: 1.15; + font-size: 4rem; } .title, .description { - text-align: center; + text-align: center; } .description { - margin: 4rem 0; - line-height: 1.5; - font-size: 1.5rem; + margin: 4rem 0; + line-height: 1.5; + font-size: 1.5rem; } .code { - background: #fafafa; - border-radius: 5px; - padding: 0.75rem; - font-size: 1.1rem; - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; } .grid { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - max-width: 800px; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + max-width: 800px; } .card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - max-width: 300px; + margin: 1rem; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; + max-width: 300px; } .card:hover, .card:focus, .card:active { - color: #0070f3; - border-color: #0070f3; + color: #0070f3; + border-color: #0070f3; } .card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; + margin: 0 0 1rem 0; + font-size: 1.5rem; } .card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; + margin: 0; + font-size: 1.25rem; + line-height: 1.5; } .logo { - height: 1em; - margin-left: 0.5rem; + height: 1em; + margin-left: 0.5rem; } @media (max-width: 600px) { - .grid { - width: 100%; - flex-direction: column; - } + .grid { + width: 100%; + flex-direction: column; + } } diff --git a/examples/nextjs/styles/globals.css b/examples/nextjs/styles/globals.css index e5e2dcc2..67f28bb4 100644 --- a/examples/nextjs/styles/globals.css +++ b/examples/nextjs/styles/globals.css @@ -1,16 +1,16 @@ html, body { - padding: 0; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, + Fira Sans, Droid Sans, Helvetica Neue, sans-serif; } a { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } * { - box-sizing: border-box; + box-sizing: border-box; } diff --git a/examples/nextjs/tsconfig.json b/examples/nextjs/tsconfig.json index 99710e85..d51865fa 100644 --- a/examples/nextjs/tsconfig.json +++ b/examples/nextjs/tsconfig.json @@ -1,20 +1,20 @@ { - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], - "exclude": ["node_modules"] + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] } diff --git a/examples/remix/.eslintrc b/examples/remix/.eslintrc index 71569754..60fd39a0 100644 --- a/examples/remix/.eslintrc +++ b/examples/remix/.eslintrc @@ -1,3 +1,3 @@ { - "extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node"] + "extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node"] } diff --git a/examples/remix/app/entry.client.tsx b/examples/remix/app/entry.client.tsx index ea516524..ee296542 100644 --- a/examples/remix/app/entry.client.tsx +++ b/examples/remix/app/entry.client.tsx @@ -3,20 +3,20 @@ import { startTransition, StrictMode } from 'react'; import { hydrateRoot } from 'react-dom/client'; function hydrate() { - startTransition(() => { - hydrateRoot( - document, - - - - ); - }); + startTransition(() => { + hydrateRoot( + document, + + + + ); + }); } if (window.requestIdleCallback) { - window.requestIdleCallback(hydrate); + window.requestIdleCallback(hydrate); } else { - // Safari doesn't support requestIdleCallback - // https://caniuse.com/requestidlecallback - window.setTimeout(hydrate, 1); + // Safari doesn't support requestIdleCallback + // https://caniuse.com/requestidlecallback + window.setTimeout(hydrate, 1); } diff --git a/examples/remix/app/entry.server.tsx b/examples/remix/app/entry.server.tsx index dc1f831f..9a800b60 100644 --- a/examples/remix/app/entry.server.tsx +++ b/examples/remix/app/entry.server.tsx @@ -8,104 +8,94 @@ import { renderToPipeableStream } from 'react-dom/server'; const ABORT_DELAY = 5000; export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext ) { - return isbot(request.headers.get('user-agent')) - ? handleBotRequest( - request, - responseStatusCode, - responseHeaders, - remixContext - ) - : handleBrowserRequest( - request, - responseStatusCode, - responseHeaders, - remixContext - ); + return isbot(request.headers.get('user-agent')) + ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext) + : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext); } function handleBotRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext ) { - return new Promise((resolve, reject) => { - let didError = false; - - const { pipe, abort } = renderToPipeableStream( - , - { - onAllReady() { - const body = new PassThrough(); - - responseHeaders.set('Content-Type', 'text/html'); - - resolve( - new Response(body, { - headers: responseHeaders, - status: didError ? 500 : responseStatusCode - }) - ); - - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - didError = true; - - console.error(error); - } - } - ); - - setTimeout(abort, ABORT_DELAY); - }); + return new Promise((resolve, reject) => { + let didError = false; + + const { pipe, abort } = renderToPipeableStream( + , + { + onAllReady() { + const body = new PassThrough(); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(body, { + headers: responseHeaders, + status: didError ? 500 : responseStatusCode + }) + ); + + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + didError = true; + + console.error(error); + } + } + ); + + setTimeout(abort, ABORT_DELAY); + }); } function handleBrowserRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext ) { - return new Promise((resolve, reject) => { - let didError = false; - - const { pipe, abort } = renderToPipeableStream( - , - { - onShellReady() { - const body = new PassThrough(); - - responseHeaders.set('Content-Type', 'text/html'); - - resolve( - new Response(body, { - headers: responseHeaders, - status: didError ? 500 : responseStatusCode - }) - ); - - pipe(body); - }, - onShellError(err: unknown) { - reject(err); - }, - onError(error: unknown) { - didError = true; - - console.error(error); - } - } - ); - - setTimeout(abort, ABORT_DELAY); - }); + return new Promise((resolve, reject) => { + let didError = false; + + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + const body = new PassThrough(); + + responseHeaders.set('Content-Type', 'text/html'); + + resolve( + new Response(body, { + headers: responseHeaders, + status: didError ? 500 : responseStatusCode + }) + ); + + pipe(body); + }, + onShellError(err: unknown) { + reject(err); + }, + onError(error: unknown) { + didError = true; + + console.error(error); + } + } + ); + + setTimeout(abort, ABORT_DELAY); + }); } diff --git a/examples/remix/app/root.tsx b/examples/remix/app/root.tsx index 66c0e2ff..1d44bd34 100644 --- a/examples/remix/app/root.tsx +++ b/examples/remix/app/root.tsx @@ -1,32 +1,25 @@ import { MetaFunction } from '@remix-run/node'; -import { - Links, - LiveReload, - Meta, - Outlet, - Scripts, - ScrollRestoration -} from '@remix-run/react'; +import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'; export const meta: MetaFunction = () => ({ - charset: 'utf-8', - title: 'New Remix App', - viewport: 'width=device-width,initial-scale=1' + charset: 'utf-8', + title: 'New Remix App', + viewport: 'width=device-width,initial-scale=1' }); export default function App() { - return ( - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + ); } diff --git a/examples/remix/app/routes/__supabase.tsx b/examples/remix/app/routes/__supabase.tsx index d26096b1..1311a9ad 100644 --- a/examples/remix/app/routes/__supabase.tsx +++ b/examples/remix/app/routes/__supabase.tsx @@ -14,8 +14,8 @@ export type TypedSupabaseClient = SupabaseClient; export type MaybeSession = Session | null; export type SupabaseContext = { - supabase: TypedSupabaseClient; - session: MaybeSession; + supabase: TypedSupabaseClient; + session: MaybeSession; }; // this uses Pathless Layout Routes [1] to wrap up all our Supabase logic @@ -23,76 +23,73 @@ export type SupabaseContext = { // [1] https://remix.run/docs/en/v1/guides/routing#pathless-layout-routes export const loader = async ({ request }: LoaderArgs) => { - // environment variables may be stored somewhere other than - // `process.env` in runtimes other than node - // we need to pipe these Supabase environment variables to the browser - const env = { - SUPABASE_URL: process.env.SUPABASE_URL!, - SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY! - }; - - // We can retrieve the session on the server and hand it to the client. - // This is used to make sure the session is available immediately upon rendering - const response = new Response(); - - const supabase = createServerClient({ request, response }); - - const { - data: { session } - } = await supabase.auth.getSession(); - - // in order for the set-cookie header to be set, - // headers must be returned as part of the loader response - return json( - { - env, - session - }, - { - headers: response.headers - } - ); + // environment variables may be stored somewhere other than + // `process.env` in runtimes other than node + // we need to pipe these Supabase environment variables to the browser + const env = { + SUPABASE_URL: process.env.SUPABASE_URL!, + SUPABASE_ANON_KEY: process.env.SUPABASE_ANON_KEY! + }; + + // We can retrieve the session on the server and hand it to the client. + // This is used to make sure the session is available immediately upon rendering + const response = new Response(); + + const supabase = createServerClient({ request, response }); + + const { + data: { session } + } = await supabase.auth.getSession(); + + // in order for the set-cookie header to be set, + // headers must be returned as part of the loader response + return json( + { + env, + session + }, + { + headers: response.headers + } + ); }; export default function Supabase() { - const { env, session } = useLoaderData(); - const fetcher = useFetcher(); - - // it is important to create a single instance of Supabase - // to use across client components - outlet context 👇 - const [supabase] = useState(() => - createBrowserClient(env.SUPABASE_URL, env.SUPABASE_ANON_KEY) - ); - - const serverAccessToken = session?.access_token; - - useEffect(() => { - const { - data: { subscription } - } = supabase.auth.onAuthStateChange((event, session) => { - if ( - session?.access_token !== serverAccessToken && - fetcher.state === 'idle' - ) { - // server and client are out of sync. - // Remix recalls active loaders after actions complete - fetcher.submit(null, { - method: 'post', - action: '/handle-supabase-auth' - }); - } - }); - - return () => { - subscription.unsubscribe(); - }; - }, [serverAccessToken, supabase, fetcher]); - - return ( - <> - -