入门

使用 React 构建用户管理应用


本教程演示如何构建一个基本的用户管理应用。该应用对用户进行身份验证和识别,将他们的个人资料信息存储在数据库中,并允许用户登录、更新他们的个人资料详细信息以及上传个人资料照片。该应用使用

Supabase User Management example

项目设置#

在开始构建之前,您需要设置数据库和 API。您可以通过在 Supabase 中启动一个新项目,然后在数据库中创建一个“schema”来完成此操作。

创建项目#

  1. 在 Supabase 控制面板中创建一个新项目
  2. 输入您的项目详细信息。
  3. 等待新的数据库启动。

设置数据库 schema#

现在设置数据库 schema。您可以使用 SQL 编辑器中的“用户管理 Starter”快速入门,或者您可以复制/粘贴下面的 SQL 并运行它。

  1. 转到控制面板中的 SQL 编辑器 页面。
  2. 点击 社区 > 快速入门 选项卡下的 用户管理 Starter
  3. 点击 运行

获取 API 详细信息#

现在您已经创建了一些数据库表,您可以使用自动生成的 API 插入数据。

为此,你需要从 项目 Connect 对话框 获取项目 URL 和密钥。

阅读 API 密钥文档 以全面了解所有密钥类型及其用途。

构建应用#

开始从头构建 React 应用。

初始化一个 React 应用#

使用 Vite 初始化一个名为 supabase-react 的应用

1
npm create vite@latest supabase-react -- --template react
2
cd supabase-react

安装唯一的附加依赖项:supabase-js

1
npm install @supabase/supabase-js

最后,将环境变量保存到 .env.local 文件中。使用你之前复制的 项目 URL 和密钥

.env
1
VITE_SUPABASE_URL=
2
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=
查看源代码

API 凭据就绪后,创建一个辅助文件来初始化 Supabase 客户端。这些变量将在浏览器中公开,但这没关系,因为你已经在数据库上启用了 行级别安全

创建并编辑 src/supabaseClient.js

src/supabaseClient.js
1
/**
2
* lib/supabaseClient.js
3
* Helper to initialize the Supabase client.
4
*/
5
6
import { createClient } from '@supabase/supabase-js'
7
8
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
9
const supabasePublishableKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY
10
11
export const supabase = createClient(supabaseUrl, supabasePublishableKey)
查看源代码

应用样式 (可选)#

一个可选步骤是更新 CSS 文件 src/index.css 以使应用看起来更美观。你可以在 这里 找到此文件的完整内容。

设置登录组件#

创建一个 React 组件来管理登录和注册。它使用 Magic Links,因此用户无需使用密码即可通过电子邮件登录。

创建并编辑 src/Auth.jsx

src/Auth.jsx
1
import { useState } from 'react'
2
import { supabase } from './supabaseClient'
3
4
export default function Auth() {
5
const [loading, setLoading] = useState(false)
6
const [email, setEmail] = useState('')
7
8
const handleLogin = async (event) => {
9
event.preventDefault()
10
11
setLoading(true)
12
const { error } = await supabase.auth.signInWithOtp({ email })
13
14
if (error) {
15
alert(error.error_description || error.message)
16
} else {
17
alert('Check your email for the login link!')
18
}
19
setLoading(false)
20
}
21
22
return (
23
<div className="row flex flex-center">
24
<div className="col-6 form-widget">
25
<h1 className="header">Supabase + React</h1>
26
<p className="description">Sign in via magic link with your email below</p>
27
<form className="form-widget" onSubmit={handleLogin}>
28
<div>
29
<input
30
className="inputField"
31
type="email"
32
placeholder="Your email"
33
value={email}
34
required={true}
35
onChange={(e) => setEmail(e.target.value)}
36
/>
37
</div>
38
<div>
39
<button className={'button block'} disabled={loading}>
40
{loading ? <span>Loading</span> : <span>Send magic link</span>}
41
</button>
42
</div>
43
</form>
44
</div>
45
</div>
46
)
47
}
查看源代码

账户页面#

用户还需要一种编辑他们的个人资料详细信息并在登录后管理他们的帐户的方式。

创建一个上传小部件#

每个 Supabase 项目都配置了 存储,用于管理大型文件,如照片和视频。

为用户创建一个头像,以便他们可以上传个人资料照片。首先创建一个新组件

创建并编辑 src/Avatar.jsx

src/Avatar.jsx
1
import { useEffect, useState } from 'react'
2
import { supabase } from './supabaseClient'
3
4
export default function Avatar({ url, size, onUpload }) {
5
const [avatarUrl, setAvatarUrl] = useState(null)
6
const [uploading, setUploading] = useState(false)
7
8
useEffect(() => {
9
if (url) downloadImage(url)
10
}, [url])
11
12
async function downloadImage(path) {
13
try {
14
const { data, error } = await supabase.storage.from('avatars').download(path)
15
if (error) {
16
throw error
17
}
18
const url = URL.createObjectURL(data)
19
setAvatarUrl(url)
20
} catch (error) {
21
console.log('Error downloading image: ', error.message)
22
}
23
}
24
25
async function uploadAvatar(event) {
26
try {
27
setUploading(true)
28
29
if (!event.target.files || event.target.files.length === 0) {
30
throw new Error('You must select an image to upload.')
31
}
32
33
const file = event.target.files[0]
34
const fileExt = file.name.split('.').pop()
35
const fileName = `${Math.random()}.${fileExt}`
36
const filePath = `${fileName}`
37
38
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
39
40
if (uploadError) {
41
throw uploadError
42
}
43
44
onUpload(event, filePath)
45
} catch (error) {
46
alert(error.message)
47
} finally {
48
setUploading(false)
49
}
50
}
51
52
return (
53
<div>
54
{avatarUrl ? (
55
<img
56
src={avatarUrl}
57
alt="Avatar"
58
className="avatar image"
59
style={{ height: size, width: size }}
60
/>
61
) : (
62
<div className="avatar no-image" style={{ height: size, width: size }} />
63
)}
64
<div style={{ width: size }}>
65
<label className="button primary block" htmlFor="single">
66
{uploading ? 'Uploading ...' : 'Upload'}
67
</label>
68
<input
69
style={{
70
visibility: 'hidden',
71
position: 'absolute',
72
}}
73
type="file"
74
id="single"
75
accept="image/*"
76
onChange={uploadAvatar}
77
disabled={uploading}
78
/>
79
</div>
80
</div>
81
)
82
}
查看源代码

用户登录后,允许他们编辑个人资料详细信息并管理帐户。

为此创建一个名为 src/Account.jsx 的新组件,并添加之前创建的 Avatar 组件。

src/Account.jsx
1
import { useState, useEffect } from 'react'
2
import { supabase } from './supabaseClient'
3
import Avatar from './Avatar'
4
5
export default function Account({ user }) {
6
const [loading, setLoading] = useState(true)
7
const [username, setUsername] = useState(null)
8
const [website, setWebsite] = useState(null)
9
const [avatar_url, setAvatarUrl] = useState(null)
10
11
useEffect(() => {
12
let ignore = false
13
async function getProfile() {
14
setLoading(true)
15
16
const { data, error } = await supabase
17
.from('profiles')
18
.select(`username, website, avatar_url`)
19
.eq('id', user.id)
20
.single()
21
22
if (!ignore) {
23
if (error) {
24
console.warn(error)
25
} else if (data) {
26
setUsername(data.username)
27
setWebsite(data.website)
28
setAvatarUrl(data.avatar_url)
29
}
30
}
31
32
setLoading(false)
33
}
34
35
getProfile()
36
37
return () => {
38
ignore = true
39
}
40
}, [user])
41
42
async function updateProfile(event, avatarUrl) {
43
event.preventDefault()
44
45
setLoading(true)
46
47
const updates = {
48
id: user.id,
49
username,
50
website,
51
avatar_url: avatarUrl,
52
updated_at: new Date(),
53
}
54
55
const { error } = await supabase.from('profiles').upsert(updates)
56
57
if (error) {
58
alert(error.message)
59
} else {
60
setAvatarUrl(avatarUrl)
61
}
62
setLoading(false)
63
}
64
65
return (
66
<form onSubmit={updateProfile} className="form-widget">
67
<Avatar
68
url={avatar_url}
69
size={150}
70
onUpload={(event, url) => {
71
updateProfile(event, url)
72
}}
73
/>
74
<div>
75
<label htmlFor="email">Email</label>
76
<input id="email" type="text" value={user.email} disabled />
77
</div>
78
<div>
79
<label htmlFor="username">Name</label>
80
<input
81
id="username"
82
type="text"
83
required
84
value={username || ''}
85
onChange={(e) => setUsername(e.target.value)}
86
/>
87
</div>
88
<div>
89
<label htmlFor="website">Website</label>
90
<input
91
id="website"
92
type="url"
93
value={website || ''}
94
onChange={(e) => setWebsite(e.target.value)}
95
/>
96
</div>
97
98
<div>
99
<button className="button block primary" type="submit" disabled={loading}>
100
{loading ? 'Loading ...' : 'Update'}
101
</button>
102
</div>
103
104
<div>
105
<button className="button block" type="button" onClick={() => supabase.auth.signOut()}>
106
Sign Out
107
</button>
108
</div>
109
</form>
110
)
111
}
查看源代码

启动!#

现在你已经准备好所有组件,请更新 src/App.jsx,它通过 getUser 方法获取当前用户,如果存在会话的话。此方法向 Supabase Auth 服务器执行网络请求。

src/App.jsx
1
import { useState, useEffect } from 'react'
2
import './App.css'
3
import { supabase } from './supabaseClient'
4
import Auth from './Auth'
5
import Account from './Account'
6
7
function App() {
8
const [user, setUser] = useState(null)
9
10
useEffect(() => {
11
supabase.auth.getUser().then(({ data: { user } }) => {
12
setUser(user)
13
})
14
15
supabase.auth.onAuthStateChange(async () => {
16
const { data: { user } } = await supabase.auth.getUser()
17
setUser(user)
18
})
19
}, [])
20
21
return (
22
<div className="container" style={{ padding: '50px 0 100px 0' }}>
23
{!user ? <Auth /> : <Account key={user.id} user={user} />}
24
</div>
25
)
26
}
27
28
export default App
查看源代码

完成此操作后,在终端窗口中运行以下命令

1
npm run dev

然后打开浏览器到 localhost:5173,您应该会看到完成的应用。

Supabase React

此时,您已经拥有一个功能齐全的应用!