mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
Update sveltekit example to use 0.9 auth helpers
This commit is contained in:
1 parent
05989a36a8
commit
534b39befd
17 files changed
+938
-3222
No files matched your search
+633
-3013
File diff suppressed because it is too large.
Load diff
@@ -13,21 +13,23 @@
|
||||
"devDependencies": {
|
||||
"@fontsource/fira-mono": "^4.5.0",
|
||||
"@neoconfetti/svelte": "^1.0.0",
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/kit": "next",
|
||||
"@sveltejs/adapter-auto": "^2.0.1",
|
||||
"@sveltejs/kit": "^1.15.9",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier-plugin-svelte": "^2.7.0",
|
||||
"svelte": "^3.46.0",
|
||||
"svelte-check": "^2.7.1",
|
||||
"svelte-preprocess": "^4.10.6",
|
||||
"svelte": "^3.58.0",
|
||||
"svelte-check": "^3.2.0",
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.1.0"
|
||||
"vite": "^4.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@supabase/auth-helpers-sveltekit": "^0.8.3",
|
||||
"@supabase/supabase-js": "^2.0.4"
|
||||
"@supabase/auth-helpers-sveltekit": "^0.9.4",
|
||||
"@supabase/auth-ui-shared": "^0.1.3",
|
||||
"@supabase/auth-ui-svelte": "^0.1.5",
|
||||
"@supabase/supabase-js": "^2.21.0"
|
||||
}
|
||||
}
|
||||
+14
-10
@@ -1,13 +1,17 @@
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
// and what to do when importing types
|
||||
declare namespace App {
|
||||
interface Supabase {
|
||||
Database: import('./schema').Database;
|
||||
SchemaName: 'public';
|
||||
}
|
||||
// src/app.d.ts
|
||||
import { SupabaseClient, Session } from '@supabase/supabase-js'
|
||||
import { Database } from './DatabaseDefinitions'
|
||||
|
||||
interface PageData {
|
||||
session: import('@supabase/supabase-js').Session | null
|
||||
declare global {
|
||||
namespace App {
|
||||
interface Locals {
|
||||
supabase: SupabaseClient<Database>
|
||||
getSession(): Promise<Session | null>
|
||||
}
|
||||
interface PageData {
|
||||
session: Session | null
|
||||
}
|
||||
// interface Error {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
import '$lib/supabaseClient';
|
||||
@@ -1 +1,35 @@
|
||||
import '$lib/supabaseClient'
|
||||
// src/hooks.server.ts
|
||||
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
|
||||
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
||||
import type { Handle } from '@sveltejs/kit'
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.supabase = createSupabaseServerClient({
|
||||
supabaseUrl: PUBLIC_SUPABASE_URL,
|
||||
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
|
||||
event
|
||||
})
|
||||
|
||||
/**
|
||||
* a little helper that is written for convenience so that instead
|
||||
* of calling `const { data: { session } } = await supabase.auth.getSession()`
|
||||
* you just call this `await getSession()`
|
||||
*/
|
||||
event.locals.getSession = async () => {
|
||||
const {
|
||||
data: { session }
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
return session
|
||||
}
|
||||
|
||||
return resolve(event, {
|
||||
/**
|
||||
* There´s an issue with `filterSerializedResponseHeaders` not working when using `sequence`
|
||||
*
|
||||
* https://github.com/sveltejs/kit/issues/8061
|
||||
*/
|
||||
filterSerializedResponseHeaders(name) {
|
||||
return name === 'content-range'
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
import { getServerSession } from '@supabase/auth-helpers-sveltekit';
|
||||
|
||||
export const load: LayoutServerLoad = async (event) => {
|
||||
// src/routes/+layout.server.ts
|
||||
export const load = async ({ locals: { getSession } }) => {
|
||||
return {
|
||||
session: await getServerSession(event)
|
||||
};
|
||||
};
|
||||
session: await getSession()
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,28 @@
|
||||
<!-- src/routes/+layout.svelte -->
|
||||
<script lang="ts">
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import '../styles.css'
|
||||
import { invalidate } from '$app/navigation'
|
||||
import { onMount } from 'svelte'
|
||||
import './styles.css'
|
||||
|
||||
export let data
|
||||
|
||||
$: ({ supabase, session } = data)
|
||||
|
||||
onMount(() => {
|
||||
const {
|
||||
data: { subscription }
|
||||
} = supabaseClient.auth.onAuthStateChange(() => {
|
||||
invalidate('supabase:auth')
|
||||
const { data } = supabase.auth.onAuthStateChange((event, _session) => {
|
||||
if (_session?.expires_at !== session?.expires_at) {
|
||||
invalidate('supabase:auth')
|
||||
}
|
||||
})
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
return () => data.subscription.unsubscribe()
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>User Management</title>
|
||||
</svelte:head>
|
||||
|
||||
<div class="container" style="padding: 50px 0 100px 0">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -1,7 +1,21 @@
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { getSupabase } from '@supabase/auth-helpers-sveltekit';
|
||||
// src/routes/+layout.ts
|
||||
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
||||
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
|
||||
import type { Database } from '../schema'
|
||||
|
||||
export const load: LayoutLoad = async (event) => {
|
||||
const { session } = await getSupabase(event);
|
||||
return { session };
|
||||
};
|
||||
export const load = async ({ fetch, data, depends }) => {
|
||||
depends('supabase:auth')
|
||||
|
||||
const supabase = createSupabaseLoadClient<Database>({
|
||||
supabaseUrl: PUBLIC_SUPABASE_URL,
|
||||
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
|
||||
event: { fetch },
|
||||
serverSession: data.session
|
||||
})
|
||||
|
||||
const {
|
||||
data: { session }
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return { supabase, session }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// src/routes/+page.server.ts
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export const load = async ({ url, locals: { getSession } }) => {
|
||||
const session = await getSession()
|
||||
|
||||
// if the user is already logged in return them to the account page
|
||||
if (session) {
|
||||
throw redirect(303, '/account')
|
||||
}
|
||||
|
||||
return { url: url.origin }
|
||||
}
|
||||
@@ -1,16 +1,23 @@
|
||||
<script>
|
||||
import { page } from '$app/stores'
|
||||
import Account from './Account.svelte'
|
||||
import Auth from './Auth.svelte'
|
||||
<!-- src/routes/+page.svelte -->
|
||||
<script lang="ts">
|
||||
import { Auth } from '@supabase/auth-ui-svelte'
|
||||
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||
|
||||
export let data
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Supabase + SvelteKit</title>
|
||||
<meta name="description" content="SvelteKit using supabase-js v2" />
|
||||
<title>User Management</title>
|
||||
</svelte:head>
|
||||
|
||||
{#if !$page.data.session}
|
||||
<Auth />
|
||||
{:else}
|
||||
<Account session={$page.data.session} />
|
||||
{/if}
|
||||
<div class="row flex-center flex">
|
||||
<div class="col-6 form-widget">
|
||||
<Auth
|
||||
supabaseClient={data.supabase}
|
||||
view="magic_link"
|
||||
redirectTo={`${data.url}/logging-in?redirect=/`}
|
||||
showLinks={false}
|
||||
appearance={{ theme: ThemeSupa, style: { input: 'color: #fff' } }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,112 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import type { AuthSession } from '@supabase/supabase-js'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
import Avatar from './Avatar.svelte'
|
||||
|
||||
export let session: AuthSession
|
||||
|
||||
let loading = false
|
||||
let username: string | null = null
|
||||
let website: string | null = null
|
||||
let avatarUrl: string | null = null
|
||||
|
||||
onMount(() => {
|
||||
getProfile()
|
||||
})
|
||||
|
||||
const getProfile = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
|
||||
const { data, error, status } = await supabaseClient
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (data) {
|
||||
username = data.username
|
||||
website = data.website
|
||||
avatarUrl = data.avatar_url
|
||||
}
|
||||
|
||||
if (error && status !== 406) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile() {
|
||||
try {
|
||||
loading = true
|
||||
const { user } = session
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url: avatarUrl,
|
||||
updated_at: new Date()
|
||||
}
|
||||
|
||||
let { error } = await supabaseClient.from('profiles').upsert(updates)
|
||||
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
async function signOut() {
|
||||
try {
|
||||
loading = true
|
||||
let { error } = await supabaseClient.auth.signOut()
|
||||
if (error) throw error
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="form-widget" on:submit|preventDefault={updateProfile}>
|
||||
<Avatar bind:url={avatarUrl} size={10} on:upload={updateProfile} />
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label for="username">Name</label>
|
||||
<input id="username" type="text" bind:value={username} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input id="website" type="url" bind:value={website} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block primary"
|
||||
value={loading ? 'Loading...' : 'Update'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button class="button block" on:click={signOut} disabled={loading}> Sign Out </button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,39 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
let loading = false
|
||||
let email: string
|
||||
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
loading = true
|
||||
const { error } = await supabaseClient.auth.signInWithOtp({ email })
|
||||
if (error) throw error
|
||||
alert('Check your email for the login link!')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(error.message)
|
||||
}
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="row flex-center flex" on:submit|preventDefault={handleLogin}>
|
||||
<div class="col-6 form-widget">
|
||||
<h1 class="header">Supabase + SvelteKit</h1>
|
||||
<p class="description">Sign in via magic link with your email below</p>
|
||||
<div>
|
||||
<input class="inputField" type="email" placeholder="Your email" bind:value={email} />
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block"
|
||||
value={loading ? 'Loading' : 'Send magic link'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,61 @@
|
||||
import { fail, redirect } from '@sveltejs/kit'
|
||||
|
||||
export const load = async ({ locals: { supabase, getSession } }) => {
|
||||
const session = await getSession()
|
||||
|
||||
if (!session) {
|
||||
throw redirect(303, '/')
|
||||
}
|
||||
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, full_name, website, avatar_url`)
|
||||
.eq('id', session.user.id)
|
||||
.single()
|
||||
|
||||
return { profile }
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
update: async ({ request, locals: { supabase, getSession } }) => {
|
||||
const formData = await request.formData()
|
||||
const fullName = formData.get('fullName') as string
|
||||
const username = formData.get('username') as string
|
||||
const website = formData.get('website') as string
|
||||
const avatarUrl = formData.get('avatarUrl') as string
|
||||
|
||||
const session = await getSession()
|
||||
|
||||
const { error } = await supabase.from('profiles').upsert({
|
||||
id: session?.user.id,
|
||||
full_name: fullName,
|
||||
username,
|
||||
website,
|
||||
avatar_url: avatarUrl,
|
||||
updated_at: new Date()
|
||||
})
|
||||
|
||||
if (error) {
|
||||
return fail(500, {
|
||||
fullName,
|
||||
username,
|
||||
website,
|
||||
avatarUrl
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
fullName,
|
||||
username,
|
||||
website,
|
||||
avatarUrl
|
||||
}
|
||||
},
|
||||
signout: async ({ locals: { supabase, getSession } }) => {
|
||||
const session = await getSession()
|
||||
if (session) {
|
||||
await supabase.auth.signOut()
|
||||
throw redirect(303, '/')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<!-- src/routes/account/+page.svelte -->
|
||||
<script lang="ts">
|
||||
import { enhance, type SubmitFunction } from '$app/forms'
|
||||
import Avatar from './Avatar.svelte'
|
||||
|
||||
export let data
|
||||
export let form
|
||||
|
||||
let { session, supabase, profile } = data
|
||||
|
||||
let profileForm: any
|
||||
let loading = false
|
||||
let fullName: string = profile?.full_name ?? ''
|
||||
let username: string = profile?.username ?? ''
|
||||
let website: string = profile?.website ?? ''
|
||||
let avatarUrl: string = profile?.avatar_url ?? ''
|
||||
|
||||
const handleSubmit: SubmitFunction = () => {
|
||||
loading = true
|
||||
return async () => {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSignOut: SubmitFunction = () => {
|
||||
loading = true
|
||||
return async ({ update }) => {
|
||||
loading = false
|
||||
update()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="form-widget">
|
||||
<form
|
||||
class="form-widget"
|
||||
method="post"
|
||||
action="?/update"
|
||||
use:enhance={handleSubmit}
|
||||
bind:this={profileForm}
|
||||
>
|
||||
<Avatar
|
||||
{supabase}
|
||||
bind:url={avatarUrl}
|
||||
size={10}
|
||||
on:upload={() => {
|
||||
profileForm.requestSubmit()
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="text" value={session?.user.email} disabled />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="fullName">Full Name</label>
|
||||
<input id="fullName" name="fullName" type="text" value={form?.fullName ?? fullName} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="username" type="text" value={form?.username ?? username} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="website">Website</label>
|
||||
<input id="website" name="website" type="url" value={form?.website ?? website} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
type="submit"
|
||||
class="button block primary"
|
||||
value={loading ? 'Loading...' : 'Update'}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method="post" action="?/signout" use:enhance={handleSignOut}>
|
||||
<div>
|
||||
<button class="button block" disabled={loading}>Sign Out</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
+7
-5
@@ -1,9 +1,11 @@
|
||||
<!-- src/routes/account/Avatar.svelte -->
|
||||
<script lang="ts">
|
||||
import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { supabaseClient } from '$lib/supabaseClient'
|
||||
|
||||
export let size = 10
|
||||
export let url: string
|
||||
export let supabase: SupabaseClient
|
||||
|
||||
let avatarUrl: string | null = null
|
||||
let uploading = false
|
||||
@@ -13,7 +15,7 @@
|
||||
|
||||
const downloadImage = async (path: string) => {
|
||||
try {
|
||||
const { data, error } = await supabaseClient.storage.from('avatars').download(path)
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
@@ -38,15 +40,14 @@
|
||||
|
||||
const file = files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const filePath = `${Math.random()}.${fileExt}`
|
||||
url = `${Math.random()}.${fileExt}`
|
||||
|
||||
let { error } = await supabaseClient.storage.from('avatars').upload(filePath, file)
|
||||
let { error } = await supabase.storage.from('avatars').upload(url, file)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
url = filePath
|
||||
dispatch('upload')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
@@ -71,6 +72,7 @@
|
||||
{:else}
|
||||
<div class="avatar no-image" style="height: {size}em; width: {size}em;" />
|
||||
{/if}
|
||||
<input type="hidden" name="avatarUrl" value={url} />
|
||||
|
||||
<div style="width: {size}em;">
|
||||
<label class="button primary block" for="single">
|
||||
@@ -0,0 +1,22 @@
|
||||
<!-- src/routes/logging-in/+page.svelte -->
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment'
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
|
||||
export let data
|
||||
$: {
|
||||
const redirectTo = $page.url.searchParams.get('redirect')
|
||||
|
||||
// check if user has been set in session store then redirect
|
||||
if (browser && data.session) {
|
||||
goto(redirectTo ?? '/account')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
"Because as we know, there are known knowns; there are things we know we know. We also know there
|
||||
are known unknowns; that is to say we know there are some things we do not know. But there are
|
||||
also unknown unknowns—the ones we don't know we don't know" - Donald Rumsfeld
|
||||
</section>
|
||||
Reference in new issue
Block a user