我目前正在做一个项目,涉及使用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
1条答案
按热度按时间rm5edbpk1#
解决了,这是我对_app.tsx的解决方案我使用了旧的_app.tsx,并将其与_app.tsx合并到支持页面目录_app.tsx