SupplyAuth + Next.js设置使用pages目录的问题

7kqas0il  于 2023-10-18  发布在  其他
关注(0)|答案(1)|浏览(106)

我目前正在做一个项目,涉及使用Next.js设置SupplyAuth。我遵循Supplier(https://supabase.com/docs/guides/auth/auth-helpers/nextjs-pages)提供的文档将身份验证功能集成到我的项目中。
然而,在将源文件从app目录转换到pages目录时,我遇到了困难。我把我的pages目录和别人在app目录下的设置合并在一起,因为我更喜欢在pages目录下编码。
为了提供关于我的问题的更多上下文和细节,我在GitHub存储库中记录了步骤和代码更改:https://github.com/mashwishi/NextJS-Supabase-AuthHelper/。您可以在自述文件中找到具体问题。
我在这个项目中的主要目标是使用Next.js和React.js创建一个带有SupplyAuth和数据库集成的项目页面目录。这个设置应该允许用户使用他们的电子邮件和密码登录,或者通过OAuth/Social Auth使用“@ supplement/auth-ui-react”库登录。
此外,我正在寻找关于为受保护的页面实现中间件的指导,以确保只有经过身份验证的用户才能访问某些页面。
注意:没有_app.tsx文件,一切都可以工作,但我想有这个,因为我需要这个。
_app.tsx

import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
import { useState } from 'react'
import { AppProps } from 'next/app'

function MyApp({
  Component,
  pageProps,
}: AppProps<{
  initialSession: Session
}>) {
  // Create a new supabase browser client on every first render.
  const [supabaseClient] = useState(() => createPagesBrowserClient())

  return (
    <SessionContextProvider
      supabaseClient={supabaseClient}
      initialSession={pageProps.initialSession}
    >
      <Component {...pageProps} />
    </SessionContextProvider>
  )
}

middleware.ts

import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'

import type { NextRequest } from 'next/server'

export async function middleware(req: NextRequest) {
    const res = NextResponse.next()
    const supabase = createMiddlewareClient({ req, res })

    const {
        data: { user },
    } = await supabase.auth.getUser()

    // if user is signed in and the current path is / redirect the user to /account
    if (user && req.nextUrl.pathname === '/login') {
        return NextResponse.redirect(new URL('/account', req.url))
    }

    // if user is not signed in and the current path is not / redirect the user to /
    if (!user && req.nextUrl.pathname !== '/login') {
        return NextResponse.redirect(new URL('/login', req.url))
    }

    return res
}

export const config = {
    matcher: ['/', '/account'],
}

./pages/login/index.tsx

import AuthForm from '../../components/auth/auth-form'

export default function Login() {
    return (
        <div className="grid grid-cols-1 md:grid-cols-2 gap-4 h-screen">
            <div className="flex flex-col justify-center">
                <div className="text-center mx-auto md:max-w-2xl">
                    <h1 className="header">Supabase Auth + Storage</h1>
                    <p>
                        Experience our Auth and Storage through a simple profile management example. Create a user profile and
                        upload an avatar image. Fast, simple, secure.
                    </p>
                </div>
            </div>
            <div className="flex flex-col justify-center">
                <div className="mx-auto w-1/2">
                    <AuthForm />
                </div>
            </div>
        </div>
    )
}

./components/auth/auth-form.tsx

'use client'
import { Auth } from '@supabase/auth-ui-react'
import { ThemeSupa } from '@supabase/auth-ui-shared'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import { Database } from '../../types/database.types'

export default function AuthForm() {
    const supabase = createClientComponentClient<Database>()

    return (
        <Auth
            supabaseClient={supabase}
            view="magic_link"
            appearance={{ theme: ThemeSupa }}
            theme="dark"
            showLinks={false}
            providers={['discord']}
            redirectTo="http://localhost:3000/api/auth/callback"
        />
    )
}

./pages/API/auth/callback.ts

import { NextApiHandler } from 'next'
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'

const handler: NextApiHandler = async (req, res) => {
    const { code } = req.query

    if (code) {
        const supabase = createPagesServerClient({ req, res })
        await supabase.auth.exchangeCodeForSession(String(code))
    }

    res.redirect('/account')
}

export default handler

./pages/account/index.tsx

import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'

export default function Profile({ user }) {
    console.log(user)
    return <div>Hello {user.user_metadata.name}</div>
}

export const getServerSideProps = async (ctx) => {
    // Create authenticated Supabase Client
    const supabase = createPagesServerClient(ctx)
    // Check if we have a session
    const {
        data: { session },
    } = await supabase.auth.getSession()

    if (!session)
        return {
        redirect: {
            destination: '/',
            permanent: false,
        },
        }

    return {
        props: {
        initialSession: session,
        user: session.user,
        },
    }
}

./pages/account/account-form.tsx

'use client'
import { useCallback, useEffect, useState } from 'react'
import { Database } from '../../types/database.types'
import { Session, createClientComponentClient } from '@supabase/auth-helpers-nextjs'

export default function AccountForm({ session }: { session: Session | null }) {
    const supabase = createClientComponentClient<Database>()
    const [loading, setLoading] = useState(true)
    const [fullname, setFullname] = useState<string | null>(null)
    const [username, setUsername] = useState<string | null>(null)
    const [avatar_url, setAvatarUrl] = useState<string | null>(null)
    const user = session?.user

    const getProfile = useCallback(async () => {
        try {
            setLoading(true)

            let { data, error, status } = await supabase
                .from('profiles')
                .select(`full_name, username, avatar_url`)
                .eq('id', user?.id)
                .single()

            if (error && status !== 406) {
                throw error
            }

            if (data) {
                setFullname(data.full_name)
                setUsername(data.username)
                setAvatarUrl(data.avatar_url)
            }
        } catch (error) {
            alert('Error loading user data!')
        } finally {
            setLoading(false)
        }
    }, [user, supabase])

    useEffect(() => {
        getProfile()
    }, [user, getProfile])

    async function updateProfile({
            username,
            avatar_url,
        }: {
        username: string | null
        fullname: string | null
        avatar_url: string | null
    }) {
        try {
            setLoading(true)

            let { error } = await supabase.from('profiles').upsert({
                id: user?.id as string,
                full_name: fullname,
                username,
                avatar_url,
                updated_at: new Date().toISOString(),
            })
            if (error) throw error
            alert('Profile updated!')
        } catch (error) {
            alert('Error updating the data!')
        } finally {
            setLoading(false)
        }
    }

    return (
        <div className="form-widget p-4 rounded-md w-1/2 mx-auto">
            <div className="mb-4">
                <label htmlFor="email" className="text-sm font-medium text-gray-700">Email</label>
                <input
                    id="email"
                    type="text"
                    value={session?.user.email}
                    disabled
                    className="w-full px-3 py-2 mt-1 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 text-black"
                />
            </div>
            <div className="mb-4">
                <label htmlFor="fullName" className="text-sm font-medium text-gray-700">Full Name</label>
                <input
                    id="fullName"
                    type="text"
                    value={fullname || ''}
                    onChange={(e) => setFullname(e.target.value)}
                    className="w-full px-3 py-2 mt-1 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 text-black"
                />
            </div>
            <div className="mb-4">
                <label htmlFor="username" className="text-sm font-medium text-gray-700">Username</label>
                <input
                    id="username"
                    type="text"
                    value={username || ''}
                    onChange={(e) => setUsername(e.target.value)}
                    className="w-full px-3 py-2 mt-1 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 text-black"
                />
            </div>

            <div className="mb-4">
                <button
                    className="w-full px-4 py-2 text-white bg-blue-500 rounded-md hover:bg-blue-600"
                    onClick={() => updateProfile({ fullname, username, avatar_url })}
                    disabled={loading}
                >
                    {loading ? 'Loading ...' : 'Update'}
                </button>
            </div>

            <div>
                <form action="/auth/signout" method="post">
                    <button
                        className="w-full px-4 py-2 text-gray-700 bg-gray-300 rounded-md hover:bg-gray-400"
                        type="submit"
                    >
                        Sign out
                    </button>
                </form>
            </div>
        </div>
    );

}

./pages/index.tsx

const Home = () => {
    return (
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4 h-screen">
      <div className="flex flex-col justify-center">
          <div className="text-center mx-auto md:max-w-2xl">
              <h1 className="header">You are home</h1>
          </div>
      </div>
  </div>
    )
  }
  
  export default Home

rm5edbpk

rm5edbpk1#

解决了,这是我对_app.tsx的解决方案我使用了旧的_app.tsx,并将其与_app.tsx合并到支持页面目录_app.tsx

import "../styles/globals.css";
import App, { AppProps, AppContext } from 'next/app'

import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
import { useState } from 'react'

function MyApp({ Component, pageProps }: AppProps<{ initialSession: Session }>) {

    // Create a new supabase browser client on every first render.
    const [supabaseClient] = useState(() => createPagesBrowserClient())

    return (
        <SessionContextProvider
        supabaseClient={supabaseClient}
        initialSession={pageProps.initialSession}
        >
        <Component {...pageProps} />
        </SessionContextProvider>
    )

}

MyApp.getInitialProps = async (appContext: AppContext) => {
    const appProps = await App.getInitialProps(appContext)
    const { router } = appContext;
    const { query } = router;

    return {
        pageProps: {
        ...appProps.pageProps,
        },
    }
}

export default MyApp;

相关问题