本页
快速入门: SolidJS
GitHub
如果你在阅读指南时遇到困难,请参考此版本.
构建应用程序
让我们开始从头开始构建SolidJS应用程序。
初始化一个SolidJS应用程序
我们可以使用Degit来初始化一个名为supabase-solid
的应用程序。
npx degit solidjs/templates/ts supabase-solid
cd supabase-solid
然后让我们安装唯一的额外依赖:supabase-js
npm install @supabase/supabase-js
最后,我们要把环境变量保存在.env
中。
我们所需要的是API URL和你[早些时候]复制的anon
密钥(#get-theapi-keys)。
VITE_SUPABASE_URL=YOUR_SUPABASE_URL
VITE_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
现在我们已经有了API凭证,让我们创建一个辅助文件来初始化Supabase客户端。这些变量将被暴露在 在浏览器上,这完全没有问题,因为我们的数据库已经启用了行级安全。
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
还有一个可选的步骤是更新CSS文件src/index.css
以使应用程序看起来漂亮。
你可以找到这个文件的全部内容这里
设置一个登录组件
让我们设置一个 SolidJS 组件来管理登录和注册。我们将使用Magic Links,因此用户可以用他们的电子邮件登录,而无需使用密码。
import { createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
export default function Auth() {
const [loading, setLoading] = createSignal(false)
const [email, setEmail] = createSignal('')
const handleLogin = async (e: SubmitEvent) => {
e.preventDefault()
try {
setLoading(true)
const { error } = await supabase.auth.signInWithOtp({ email: email() })
if (error) throw error
alert('Check your email for the login link!')
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setLoading(false)
}
}
return (
<div class="row flex-center flex">
<div class="col-6 form-widget" aria-live="polite">
<h1 class="header">Supabase + SolidJS</h1>
<p class="description">Sign in via magic link with your email below</p>
<form class="form-widget" onSubmit={handleLogin}>
<div>
<label for="email">Email</label>
<input
id="email"
class="inputField"
type="email"
placeholder="Your email"
value={email()}
onChange={(e) => setEmail(e.currentTarget.value)}
/>
</div>
<div>
<button type="submit" class="button block" aria-live="polite">
{loading() ? <span>Loading</span> : <span>Send magic link</span>}
</button>
</div>
</form>
</div>
</div>
)
}
账号页面
在用户登录后,我们可以让他们编辑他们的个人资料细节和管理他们的账户。
让我们为它创建一个新的组件,叫做Account.tsx
。
import { AuthSession } from '@supabase/supabase-js'
import { Component, createEffect, createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
interface Props {
session: AuthSession;
}
const Account: Component<Props> = ({ session }) => {
const [loading, setLoading] = createSignal(true)
const [username, setUsername] = (createSignal < string) | (null > null)
const [website, setWebsite] = (createSignal < string) | (null > null)
const [avatarUrl, setAvatarUrl] = (createSignal < string) | (null > null)
createEffect(() => {
getProfile()
})
const getProfile = async () => {
try {
setLoading(true)
const { user } = session
let { data, error, status } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user.id)
.single()
if (error && status !== 406) {
throw error
}
if (data) {
setUsername(data.username)
setWebsite(data.website)
setAvatarUrl(data.avatar_url)
}
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setLoading(false)
}
}
const updateProfile = async (e: Event) => {
e.preventDefault()
try {
setLoading(true)
const { user } = session
const updates = {
id: user.id,
username: username(),
website: website(),
avatar_url: avatarUrl(),
updated_at: new Date().toISOString(),
}
let { error } = await supabase.from('profiles').upsert(updates)
if (error) {
throw error
}
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setLoading(false)
}
}
return (
<div aria-live="polite">
<form onSubmit={updateProfile} class="form-widget">
<div>Email: {session.user.email}</div>
<div>
<label for="username">Name</label>
<input
id="username"
type="text"
value={username() || ''}
onChange={(e) => setUsername(e.currentTarget.value)}
/>
</div>
<div>
<label for="website">Website</label>
<input
id="website"
type="text"
value={website() || ''}
onChange={(e) => setWebsite(e.currentTarget.value)}
/>
</div>
<div>
<button type="submit" class="button primary block" disabled={loading()}>
{loading() ? 'Saving ...' : 'Update profile'}
</button>
</div>
<button type="button" class="button block" onClick={() => supabase.auth.signOut()}>
Sign Out
</button>
</form>
</div>
)
}
export default Account
启动
现在我们有了所有的组件,让我们更新App.tsx
。
import { Component, createEffect, createSignal } from 'solid-js'
import { supabase } from './supabaseClient'
import { AuthSession } from '@supabase/supabase-js'
import Account from './Account'
import Auth from './Auth'
const App: Component = () => {
const [session, setSession] = createSignal<AuthSession | null>(null)
createEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
})
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session)
})
})
return (
<div class="container" style={{ padding: '50px 0 100px 0' }}>
{!session() ? <Auth /> : <Account session={session()!} />}
</div>
)
}
export default App
一旦完成,在终端窗口运行这个程序。
npm start
然后打开浏览器到localhost:3000,你应该看到完成的应用程序。
个人照片
每个Supabase项目都配置了存储,用于管理照片和视频等大文件。
创建一个上传小组件
让我们为用户创建一个头像,以便他们可以上传个人资料照片。我们可以从创建一个新的组件开始。
import { Component, createEffect, createSignal, JSX } from 'solid-js'
import { supabase } from './supabaseClient'
interface Props {
size: number
url: string | null
onUpload: (event: Event, filePath: string) => void
}
const Avatar: Component<Props> = (props) => {
const [avatarUrl, setAvatarUrl] = createSignal<string | null>(null)
const [uploading, setUploading] = createSignal(false)
createEffect(() => {
if (props.url) downloadImage(props.url)
})
const downloadImage = async (path: string) => {
try {
const { data, error } = await supabase.storage.from('avatars').download(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
if (error instanceof Error) {
console.log('Error downloading image: ', error.message)
}
}
}
const uploadAvatar: JSX.EventHandler<HTMLInputElement, Event> = async (event) => {
try {
setUploading(true)
const target = event.currentTarget
if (!target?.files || target.files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${fileName}`
let { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file)
if (uploadError) {
throw uploadError
}
props.onUpload(event, filePath)
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
setUploading(false)
}
}
return (
<div style={{ width: props.size }} aria-live="polite">
{avatarUrl() ? (
<img
src={avatarUrl()!}
alt={avatarUrl() ? 'Avatar' : 'No image'}
class="avatar image"
style={{ height: `${props.size}px`, width: `${props.size}px` }}
/>
) : (
<div
class="avatar no-image"
style={{ height: `${props.size}px`, width: `${props.size}px` }}
/>
)}
<div style={{ width: `${props.size}px` }}>
<label class="button primary block" for="single">
{uploading() ? 'Uploading ...' : 'Upload avatar'}
</label>
<span style="display:none">
<input
type="file"
id="single"
accept="image/*"
onChange={uploadAvatar}
disabled={uploading()}
/>
</span>
</div>
</div>
)
}
export default Avatar
添加新的小组件
然后我们就可以把这个小部件添加到账号页面:
// Import the new component
import Avatar from './Avatar'
// ...
return (
<form onSubmit={updateProfile} class="form-widget">
{/* Add to the body */}
<Avatar
url={avatarUrl()}
size={150}
onUpload={(e: Event, url: string) => {
setAvatarUrl(url)
updateProfile(e)
}}
/>
{/* ... */}
</div>
)
下一步
在这个阶段,你已经有了一个功能完备的应用程序!
- 有问题吗?在此提问.
- 请登录MemFire Cloud