Update sveltekit example to use 0.9 auth helpers

This commit is contained in:
Andrew Smith committed 2023-04-29 15:23:52 +00:00
1 parent 05989a36a8
commit 534b39befd
17 files changed
+938 -3222

No files matched your search

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"
}
}
@@ -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>
@@ -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>