fix(docs): update SvelteKit tutorial to use Svelte 5 syntax (#31229)

* docs: Update SvelteKit auth tutorial project creation syntax (#31114)

* docs: Update link to SvelteKit docs (#31114)

* Update +layout.svelte to Svelte 5 syntax (#31114)

* Update +page.svelte to use Svelte 5 syntax (#31114)

* Update account +page.svelte to use Svelte 5 syntax (#31114)

* Update account Avatar.svelte to use Svelte 5 syntax (#31114)

* Update SvelteKit example to Svelte 5 syntax (#31114)

* docs: Update npm dependencies (#31114)

* Start review

* Finish review

* Update code for SvelteKit 5

* Convert to codesamples

---------

Co-authored-by: Chris Chinchilla <chris@chrischinchilla.com>
Co-authored-by: Chris Chinchilla <chris.ward@supabase.io>
This commit is contained in:
authored and GitHub committed 2025-08-12 16:38:06 +02:00
1 parent c32c74e34c
commit 4b2705f0ba
9 files changed
+917 -1593

No files matched your search

@@ -17,14 +17,14 @@ If you get stuck while working through this guide, refer to the [full example on
## Building the app
Let's start building the Svelte app from scratch.
Start building the Svelte app from scratch.
### Initialize a Svelte app
We can use the [SvelteKit Skeleton Project](https://kit.svelte.dev/docs) to initialize an app called `supabase-sveltekit` (for this tutorial we will be using TypeScript):
Use the [SvelteKit Skeleton Project](https://svelte.dev/docs/kit) to initialize an app called `supabase-sveltekit` (for this tutorial, select "SvelteKit minimal" and use TypeScript):
```bash
npm create svelte@latest supabase-sveltekit
npx sv create supabase-sveltekit
cd supabase-sveltekit
npm install
```
@@ -35,8 +35,8 @@ Then install the Supabase client library: [supabase-js](https://github.com/supab
npm install @supabase/supabase-js
```
And finally we want to save the environment variables in a `.env`.
All we need are the `SUPABASE_URL` and the `SUPABASE_KEY` key that you copied [earlier](#get-the-api-keys).
And finally, save the environment variables in a `.env` file.
All you need are the `PUBLIC_SUPABASE_URL` and the `PUBLIC_SUPABASE_ANON_KEY` key that you copied [earlier](#get-the-api-keys).
<$CodeTabs>
@@ -47,108 +47,46 @@ PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY"
</$CodeTabs>
Optionally, add `src/styles.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/sveltekit-user-management/src/styles.css).
### App styling (optional)
An optional step is to update the CSS file `src/styles.css` to make the app look nice.
You can find the full contents of this file [in the example repository](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/sveltekit-user-management/src/styles.css).
### Creating a Supabase client for SSR
The `ssr` package configures Supabase to use Cookies, which is required for server-side languages and frameworks.
The `ssr` package configures Supabase to use Cookies, which are required for server-side languages and frameworks.
Install the Supabase packages:
Install the SSR package:
```bash
npm install @supabase/ssr @supabase/supabase-js
npm install @supabase/ssr
```
Creating a Supabase client with the `ssr` package automatically configures it to use Cookies. This means your user's session is available throughout the entire SvelteKit stack - page, layout, server, hooks.
Creating a Supabase client with the `ssr` package automatically configures it to use Cookies. This means the user's session is available throughout the entire SvelteKit stack - page, layout, server, and hooks.
Add the code below to your `src/hooks.server.ts` to initialize the client on the server:
Add the code below to a `src/hooks.server.ts` file to initialize the client on the server:
<$CodeTabs>
```ts name=src/hooks.server.ts
// src/hooks.server.ts
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createServerClient } from '@supabase/ssr'
import type { Handle } from '@sveltejs/kit'
export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
cookies: {
getAll: () => event.cookies.getAll(),
/**
* SvelteKit's cookies API requires `path` to be explicitly set in
* the cookie options. Setting `path` to `/` replicates previous/
* standard behavior.
*/
setAll: (cookiesToSet) => {
cookiesToSet.forEach(({ name, value, options }) => {
event.cookies.set(name, value, { ...options, path: '/' })
})
},
},
})
/**
* Unlike `supabase.auth.getSession()`, which returns the session _without_
* validating the JWT, this function also calls `getUser()` to validate the
* JWT before returning the session.
*/
event.locals.safeGetSession = async () => {
const {
data: { session },
} = await event.locals.supabase.auth.getSession()
if (!session) {
return { session: null, user: null }
}
const {
data: { user },
error,
} = await event.locals.supabase.auth.getUser()
if (error) {
// JWT validation has failed
return { session: null, user: null }
}
return { session, user }
}
return resolve(event, {
filterSerializedResponseHeaders(name) {
return name === 'content-range' || name === 'x-supabase-api-version'
},
})
}
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/hooks.server.ts"
lines={[[1, -1]]}
meta="name=src/hooks.server.ts"
/>
</$CodeTabs>
<$Partial path="get_session_warning.mdx" />
If you are using TypeScript the compiler might complain about `event.locals.supabase` and `event.locals.safeGetSession`, this can be fixed by updating your `src/app.d.ts` with the content below:
{/* TODO: Change when adding JS autoconversion */}
As this tutorial uses TypeScript the compiler complains about `event.locals.supabase` and `event.locals.safeGetSession`, you can fix this by updating the `src/app.d.ts` with the content below:
<$CodeTabs>
```ts name=src/app.d.ts
// src/app.d.ts
import { SupabaseClient, Session } from '@supabase/supabase-js'
declare global {
namespace App {
interface Locals {
supabase: SupabaseClient
safeGetSession(): Promise<{ session: Session | null; user: User | null }>
}
interface PageData {
session: Session | null
user: User | null
}
// interface Error {}
// interface Platform {}
}
}
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/app.d.ts"
lines={[[1, -1]]}
meta="name=src/app.d.ts"
/>
</$CodeTabs>
@@ -156,26 +94,17 @@ Create a new `src/routes/+layout.server.ts` file to handle the session on the se
<$CodeTabs>
```ts name=src/routes/+layout.server.ts
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types'
export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
const { session, user } = await safeGetSession()
return {
session,
user,
cookies: cookies.getAll(),
}
}
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/+layout.server.ts"
lines={[[1, -1]]}
meta="name=src/routes/+layout.server.ts"
/>
</$CodeTabs>
<Admonition type="tip">
Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project.
Start the dev server (`npm run dev`) to generate the `./$types` files we are referencing in our project.
</Admonition>
@@ -183,202 +112,49 @@ Create a new `src/routes/+layout.ts` file to handle the session and the `supabas
<$CodeTabs>
```ts name=src/routes/+layout.ts
// src/routes/+layout.ts
import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr'
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import type { LayoutLoad } from './$types'
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends('supabase:auth')
const supabase = isBrowser()
? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
})
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
global: {
fetch,
},
cookies: {
getAll() {
return data.cookies
},
},
})
/**
* It's fine to use `getSession` here, because on the client, `getSession` is
* safe, and on the server, it reads `session` from the `LayoutData`, which
* safely checked the session using `safeGetSession`.
*/
const {
data: { session },
} = await supabase.auth.getSession()
return { supabase, session }
}
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/+layout.ts"
lines={[[1, -1]]}
meta="name=src/routes/+layout.ts"
/>
</$CodeTabs>
Update your `src/routes/+layout.svelte`:
Create `src/routes/+layout.svelte`:
<$CodeTabs>
```svelte name=src/routes/+layout.svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import '../styles.css'
import { invalidate } from '$app/navigation'
import { onMount } from 'svelte'
export let data
let { supabase, session } = data
$: ({ supabase, session } = data)
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((event, newSession) => {
if (newSession?.expires_at !== session?.expires_at) {
invalidate('supabase:auth')
}
})
return () => data.subscription.unsubscribe()
})
</script>
<svelte:head>
<title>User Management</title>
</svelte:head>
<div class="container" style="padding: 50px 0 100px 0">
<slot />
</div>
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/+layout.svelte"
lines={[[1, -1]]}
meta="name=src/routes/+layout.svelte"
/>
</$CodeTabs>
## Set up a login page
Create a magic link login/signup page for your application:
Create a magic link login/signup page for your application by updating the `routes/+page.svelte` file:
<$CodeTabs>
```svelte name=src/routes/+page.svelte
<!-- src/routes/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms'
import type { ActionData, SubmitFunction } from './$types.js'
export let form: ActionData;
let loading = false
const handleSubmit: SubmitFunction = () => {
loading = true
return async ({ update }) => {
update()
loading = false
}
}
</script>
<svelte:head>
<title>User Management</title>
</svelte:head>
<form class="row flex flex-center" method="POST" use:enhance={handleSubmit}>
<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>
{#if form?.message !== undefined}
<div class="success {form?.success ? '' : 'fail'}">
{form?.message}
</div>
{/if}
<div>
<label for="email">Email address</label>
<input
id="email"
name="email"
class="inputField"
type="email"
placeholder="Your email"
value={form?.email ?? ''}
/>
</div>
{#if form?.errors?.email}
<span class="flex items-center text-sm error">
{form?.errors?.email}
</span>
{/if}
<div>
<button class="button primary block">
{ loading ? 'Loading' : 'Send magic link' }
</button>
</div>
</div>
</form>
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/+page.svelte"
lines={[[1, -1]]}
meta="name=src/routes/+page.svelte"
/>
</$CodeTabs>
Create a `src/routes/+page.server.ts` file that will handle our magic link form when submitted.
Create a `src/routes/+page.server.ts` file that handles the magic link form when submitted.
<$CodeTabs>
```ts name=src/routes/+page.server.ts
// src/routes/+page.server.ts
import { fail, redirect } from '@sveltejs/kit'
import type { Actions, PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ url, locals: { safeGetSession } }) => {
const { session } = await safeGetSession()
// if the user is already logged in return them to the account page
if (session) {
redirect(303, '/account')
}
return { url: url.origin }
}
export const actions: Actions = {
default: async (event) => {
const {
url,
request,
locals: { supabase },
} = event
const formData = await request.formData()
const email = formData.get('email') as string
const validEmail = /^[\w-\.+]+@([\w-]+\.)+[\w-]{2,8}$/.test(email)
if (!validEmail) {
return fail(400, { errors: { email: 'Please enter a valid email address' }, email })
}
const { error } = await supabase.auth.signInWithOtp({ email })
if (error) {
return fail(400, {
success: false,
email,
message: `There was an issue, Please contact support.`,
})
}
return {
success: true,
message: 'Please check your email for a magic link to log into the website.',
}
},
}
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/+page.server.ts"
lines={[[1, -1]]}
meta="name=src/routes/+page.server.ts"
/>
</$CodeTabs>
@@ -388,239 +164,77 @@ Change the email template to support a server-side authentication flow.
Before we proceed, let's change the email template to support sending a token hash:
- Go to the [Auth templates](/dashboard/project/_/auth/templates) page in your dashboard.
- Select `Confirm signup` template.
- Go to the [**Auth** > **Emails**](/dashboard/project/_/auth/templates) page in the project dashboard.
- Select the **Confirm signup** template.
- Change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email`.
- Repeat the previous step for `Magic link` template.
- Repeat the previous step for **Magic link** template.
<Admonition type="tip">
Did you know? You could also customize emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates).
**Did you know?** You can also customize emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates).
</Admonition>
### Confirmation endpoint
As we are working in a server-side rendering (SSR) environment, it is necessary to create a server endpoint responsible for exchanging the `token_hash` for a session.
As this is a server-side rendering (SSR) environment, you need to create a server endpoint responsible for exchanging the `token_hash` for a session.
In the following code snippet, we perform the following steps:
The following code snippet performs the following steps:
- Retrieve the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter.
- Exchange this `token_hash` for a session, which we store in storage (in this case, cookies).
- Finally, the user is redirected to the `account` page or the `error` page.
- Retrieves the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter.
- Exchanges this `token_hash` for a session, which you store in storage (in this case, cookies).
- Finally, redirect the user to the `account` page or the `error` page.
<$CodeTabs>
```ts name=src/routes/auth/confirm/+server.ts
// src/routes/auth/confirm/+server.ts
import type { EmailOtpType } from '@supabase/supabase-js'
import { redirect } from '@sveltejs/kit'
import type { RequestHandler } from './$types'
export const GET: RequestHandler = async ({ url, locals: { supabase } }) => {
const token_hash = url.searchParams.get('token_hash')
const type = url.searchParams.get('type') as EmailOtpType | null
const next = url.searchParams.get('next') ?? '/account'
/**
* Clean up the redirect URL by deleting the Auth flow parameters.
*
* `next` is preserved for now, because it's needed in the error case.
*/
const redirectTo = new URL(url)
redirectTo.pathname = next
redirectTo.searchParams.delete('token_hash')
redirectTo.searchParams.delete('type')
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ type, token_hash })
if (!error) {
redirectTo.searchParams.delete('next')
redirect(303, redirectTo)
}
}
redirectTo.pathname = '/auth/error'
redirect(303, redirectTo)
}
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/auth/confirm/+server.ts"
lines={[[1, -1]]}
meta="name=src/routes/auth/confirm/+server.ts"
/>
</$CodeTabs>
### Authentication error page
If there is an error with confirming the token you will be redirect to this error page.
If there is an error with confirming the token, redirect the user to an error page.
<$CodeTabs>
```svelte name=src/routes/auth/error/+page.svelte
<p>Login error</p>
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/auth/error/+page.svelte"
lines={[[1, -1]]}
meta="name=src/routes/auth/error/+page.svelte"
/>
</$CodeTabs>
### Account page
After a user is signed in, they need to be able to edit their profile details and manage their account.
After a user signs in, they need to be able to edit their profile details page.
Create a new `src/routes/account/+page.svelte` file with the content below.
<$CodeTabs>
```svelte name=src/routes/account/+page.svelte
<!-- src/routes/account/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { SubmitFunction } from '@sveltejs/kit';
export let data
export let form
let { session, supabase, profile } = data
$: ({ session, supabase, profile } = data)
let profileForm: HTMLFormElement
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}
>
<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>
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/account/+page.svelte"
lines={[[1, 3],[6,12],[15,38],[47,-1]]}
meta="name=src/routes/account/+page.svelte"
/>
</$CodeTabs>
Now create the associated `src/routes/account/+page.server.ts` file that will handle loading our data from the server through the `load` function
and handle all our form actions through the `actions` object.
Now, create the associated `src/routes/account/+page.server.ts` file that handles loading data from the server through the `load` function
and handle all form actions through the `actions` object.
```ts name=src/routes/account/+page.server.ts
import { fail, redirect } from '@sveltejs/kit'
import type { Actions, PageServerLoad } from './$types'
export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => {
const { session } = await safeGetSession()
if (!session) {
redirect(303, '/')
}
const { data: profile } = await supabase
.from('profiles')
.select(`username, full_name, website, avatar_url`)
.eq('id', session.user.id)
.single()
return { session, profile }
}
export const actions: Actions = {
update: async ({ request, locals: { supabase, safeGetSession } }) => {
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 safeGetSession()
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, safeGetSession } }) => {
const { session } = await safeGetSession()
if (session) {
await supabase.auth.signOut()
redirect(303, '/')
}
},
}
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/+page.server.ts"
lines={[[1, -1]]}
meta="name=src/routes/+page.server.ts"
/>
### Launch!
Now that we have all the pages in place, run this in a terminal window:
With all the pages in place, run this command in a terminal:
```bash
npm run dev
@@ -636,144 +250,29 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma
### Create an upload widget
Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component called `Avatar.svelte` in the `src/routes/account` directory:
Create an avatar for the user so that they can upload a profile photo. Start by creating a new component called `Avatar.svelte` in the `src/routes/account` directory:
<$CodeTabs>
```svelte name=src/routes/account/Avatar.svelte
<!-- src/routes/account/Avatar.svelte -->
<script lang="ts">
import type { SupabaseClient } from '@supabase/supabase-js'
import { createEventDispatcher } from 'svelte'
export let size = 10
export let url: string
export let supabase: SupabaseClient
let avatarUrl: string | null = null
let uploading = false
let files: FileList
const dispatch = createEventDispatcher()
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)
avatarUrl = url
} catch (error) {
if (error instanceof Error) {
console.log('Error downloading image: ', error.message)
}
}
}
const uploadAvatar = async () => {
try {
uploading = true
if (!files || files.length === 0) {
throw new Error('You must select an image to upload.')
}
const file = files[0]
const fileExt = file.name.split('.').pop()
const filePath = `${Math.random()}.${fileExt}`
const { error } = await supabase.storage.from('avatars').upload(filePath, file)
if (error) {
throw error
}
url = filePath
setTimeout(() => {
dispatch('upload')
}, 100)
} catch (error) {
if (error instanceof Error) {
alert(error.message)
}
} finally {
uploading = false
}
}
$: if (url) downloadImage(url)
</script>
<div>
{#if avatarUrl}
<img
src={avatarUrl}
alt={avatarUrl ? 'Avatar' : 'No image'}
class="avatar image"
style="height: {size}em; width: {size}em;"
/>
{: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">
{uploading ? 'Uploading ...' : 'Upload'}
</label>
<input
style="visibility: hidden; position:absolute;"
type="file"
id="single"
accept="image/*"
bind:files
on:change={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/account/Avatar.svelte"
lines={[[1, -1]]}
meta="name=src/routes/account/Avatar.svelte"
/>
</$CodeTabs>
### Add the new widget
And then we can add the widget to the Account page:
Add the widget to the Account page:
<$CodeTabs>
```svelte name=src/routes/account/+page.svelte
<!-- src/routes/account/+page.svelte -->
<script lang="ts">
// Import the new component
import Avatar from './Avatar.svelte'
</script>
<div class="form-widget">
<form
class="form-widget"
method="post"
action="?/update"
use:enhance={handleSubmit}
bind:this={profileForm}
>
<!-- Add to body -->
<Avatar
{supabase}
bind:url={avatarUrl}
size={10}
on:upload={() => {
profileForm.requestSubmit();
}}
/>
<!-- Other form elements -->
</form>
</div>
```
<$CodeSample
path="/user-management/sveltekit-user-management/src/routes/account/+page.svelte"
lines={[[1, 1], [4,4], [31,31],[39,46],[82,82]]}
meta="name=src/routes/account/+page.svelte"
/>
</$CodeTabs>
File diff suppressed because it is too large. Load diff
@@ -10,20 +10,20 @@
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.2.2",
"@sveltejs/kit": "^2.5.18",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"svelte": "^4.2.18",
"svelte-check": "^3.8.4",
"@sveltejs/adapter-auto": "^6.0.1",
"@sveltejs/kit": "^2.5.27",
"@sveltejs/vite-plugin-svelte": "^6.1.0",
"svelte": "^5.14.4",
"svelte-check": "^4.1.1",
"tslib": "^2.6.3",
"typescript": "^5.5.3",
"vite": "^5.3.3"
"vite": "^7.0.5"
},
"type": "module",
"dependencies": {
"@supabase/auth-ui-shared": "^0.1.8",
"@supabase/auth-ui-svelte": "^0.2.9",
"@supabase/ssr": "^0.4.0",
"@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.44.2"
}
}
@@ -4,10 +4,8 @@
import { invalidate } from '$app/navigation'
import { onMount } from 'svelte'
export let data
let { supabase, session } = data
$: ({ supabase, session } = data)
let { data, children } = $props()
let { supabase, session } = $derived(data)
onMount(() => {
const { data } = supabase.auth.onAuthStateChange((event, _session) => {
@@ -25,5 +23,5 @@
</svelte:head>
<div class="container" style="padding: 50px 0 100px 0">
<slot />
{@render children()}
</div>
@@ -3,9 +3,12 @@
import { enhance } from '$app/forms'
import type { ActionData, SubmitFunction } from './$types.js'
export let form: ActionData;
let loading = false
interface Props {
form: ActionData
}
let { form }: Props = $props()
let loading = $state(false)
const handleSubmit: SubmitFunction = () => {
loading = true
@@ -1,21 +1,16 @@
<!-- src/routes/account/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { SubmitFunction } from '@sveltejs/kit';
import Avatar from './Avatar.svelte'
export let data
export let form
let { session, supabase, profile } = data
$: ({ session, supabase, profile } = data)
let { data, form } = $props()
let { session, supabase, profile } = $derived(data)
let profileForm: HTMLFormElement
let loading = false
let loading = $state(false)
let fullName: string = profile?.full_name ?? ''
let username: string = profile?.username ?? ''
let website: string = profile?.website ?? ''
let avatarUrl: string = profile?.avatar_url ?? ''
let avatarUrl: string = $state(profile?.avatar_url ?? '')
const handleSubmit: SubmitFunction = () => {
loading = true
@@ -45,7 +40,7 @@
{supabase}
bind:url={avatarUrl}
size={10}
on:upload={() => {
onupload={() => {
profileForm.requestSubmit();
}}
/>
@@ -1,17 +1,18 @@
<!-- src/routes/account/Avatar.svelte -->
<script lang="ts">
import type { SupabaseClient } from '@supabase/supabase-js'
import { createEventDispatcher } from 'svelte'
export let size = 10
export let url: string
export let supabase: SupabaseClient
interface Props {
size?: number
url?: string
supabase: SupabaseClient
onupload?: () => void
}
let { size = 10, url = $bindable(), supabase, onupload }: Props = $props()
let avatarUrl: string | null = null
let uploading = false
let files: FileList
const dispatch = createEventDispatcher()
let avatarUrl: string | null = $state(null)
let uploading = $state(false)
let files: FileList = $state()
const downloadImage = async (path: string) => {
try {
@@ -50,7 +51,7 @@
url = filePath
setTimeout(() => {
dispatch('upload')
onupload?.()
}, 100)
} catch (error) {
if (error instanceof Error) {
@@ -61,7 +62,9 @@
}
}
$: if (url) downloadImage(url)
$effect(() => {
if (url) downloadImage(url)
})
</script>
<div>
@@ -73,7 +76,7 @@
style="height: {size}em; width: {size}em;"
/>
{:else}
<div class="avatar no-image" style="height: {size}em; width: {size}em;" />
<div class="avatar no-image" style="height: {size}em; width: {size}em;"></div>
{/if}
<input type="hidden" name="avatarUrl" value={url} />
@@ -87,7 +90,7 @@
id="single"
accept="image/*"
bind:files
on:change={uploadAvatar}
onchange={uploadAvatar}
disabled={uploading}
/>
</div>
@@ -0,0 +1 @@
<p>Login error</p>
+12 -5
View File
@@ -29,7 +29,6 @@
"test:ui-patterns": "turbo run test --filter=ui-patterns",
"test:studio": "turbo run test --filter=studio",
"test:studio:watch": "turbo run test --filter=studio -- watch",
"e2e": "pnpm --prefix e2e/studio run e2e",
"e2e:dev-hosted": "pnpm --prefix e2e/studio run e2e:dev-hosted",
"e2e:dev-selfhosted": "pnpm --prefix e2e/studio run e2e:dev-selfhosted",
@@ -39,7 +38,6 @@
"e2e:ci": "pnpm --prefix e2e/studio run e2e:ci",
"e2e:supabase:start": "pnpm --prefix e2e/studio run supabase:start",
"e2e:supabase:stop": "pnpm --prefix e2e/studio run supabase:stop",
"perf:kong": "ab -t 5 -c 20 -T application/json http://localhost:8000/",
"perf:meta": "ab -t 5 -c 20 -T application/json http://localhost:5555/tables",
"setup:cli": "supabase start -x studio && supabase status --output json > keys.json && node scripts/generateLocalEnv.js",
@@ -72,12 +70,21 @@
"esbuild": "^0.25.2",
"@redocly/respect-core>form-data": "^4.0.4"
},
"neverBuiltDependencies": ["libpg-query"]
"neverBuiltDependencies": [
"libpg-query"
]
},
"engines": {
"pnpm": ">=9",
"node": ">=22"
},
"keywords": ["postgres", "firebase", "storage", "functions", "database", "auth"],
"keywords": [
"postgres",
"firebase",
"storage",
"functions",
"database",
"auth"
],
"packageManager": "pnpm@9.15.5"
}
}