mirror of
https://github.com/jcreek/SvelteKitSaasBoilerplate.git
synced 2026-07-15 03:53:48 +00:00
feat(#29): use magic link to sign in
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Auth } from '@supabase/auth-ui-svelte'
|
||||||
|
import { ThemeSupa } from '@supabase/auth-ui-shared'
|
||||||
|
|
||||||
|
export let supabase: any;
|
||||||
|
export let url: string;
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
<Auth
|
||||||
|
supabaseClient={supabase}
|
||||||
|
view="magic_link"
|
||||||
|
redirectTo={`${url}/auth/callback`}
|
||||||
|
showLinks={false}
|
||||||
|
appearance={{ theme: ThemeSupa, style: { input: 'color: #fff' } }}
|
||||||
|
additionalData={{}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
import { basket, type Basket, type Item } from '$lib/stores/basket.js';
|
import { basket, type Basket, type Item } from '$lib/stores/basket.js';
|
||||||
import { general } from '$lib/stores/generalStore.js';
|
import { general } from '$lib/stores/generalStore.js';
|
||||||
import { invalidate } from '$app/navigation';
|
import { invalidate } from '$app/navigation';
|
||||||
import SignIn from '$lib/components/SignIn.svelte';
|
|
||||||
import SignOut from '$lib/components/SignOut.svelte';
|
import SignOut from '$lib/components/SignOut.svelte';
|
||||||
import CookieConsent from '$lib/components/CookieConsent.svelte';
|
import CookieConsent from '$lib/components/CookieConsent.svelte';
|
||||||
import NavLinks from '$lib/components/NavLinks.svelte';
|
import NavLinks from '$lib/components/NavLinks.svelte';
|
||||||
|
import MagicLink from '$lib/components/MagicLink.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
let { supabase, session } = data;
|
let { supabase, session, url } = data;
|
||||||
$: ({ supabase, session } = data);
|
$: ({ supabase, session, url } = data);
|
||||||
|
|
||||||
let localBasket: Basket;
|
let localBasket: Basket;
|
||||||
const unsubscribeToBasket = basket.subscribe((value) => {
|
const unsubscribeToBasket = basket.subscribe((value) => {
|
||||||
@@ -113,8 +113,8 @@
|
|||||||
<span class="badge badge-sm indicator-item">{localBasket.items.length}</span>
|
<span class="badge badge-sm indicator-item">{localBasket.items.length}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown dropdown-end">
|
<details class="dropdown dropdown-end">
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost btn-circle avatar">
|
<summary tabindex="0" role="button" class="btn btn-ghost btn-circle avatar">
|
||||||
<div class="w-10 rounded-full">
|
<div class="w-10 rounded-full">
|
||||||
{#if session?.user}
|
{#if session?.user}
|
||||||
<div class="user-circle text-primary-content border-primary-content">
|
<div class="user-circle text-primary-content border-primary-content">
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</summary>
|
||||||
|
|
||||||
{#if session?.user}
|
{#if session?.user}
|
||||||
<ul
|
<ul
|
||||||
@@ -147,10 +147,10 @@
|
|||||||
<div
|
<div
|
||||||
class="mt-3 z-[1] p-2 shadow menu menu-sm dropdown-content bg-base-100 text-base-content rounded-box min-w-[13rem] w-auto"
|
class="mt-3 z-[1] p-2 shadow menu menu-sm dropdown-content bg-base-100 text-base-content rounded-box min-w-[13rem] w-auto"
|
||||||
>
|
>
|
||||||
<SignIn {supabase} />
|
<MagicLink {supabase} {url} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ss
|
|||||||
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public';
|
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public';
|
||||||
import type { LayoutLoad } from './$types';
|
import type { LayoutLoad } from './$types';
|
||||||
|
|
||||||
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
export const load: LayoutLoad = async ({ fetch, data, depends, url }) => {
|
||||||
depends('supabase:auth');
|
depends('supabase:auth');
|
||||||
|
|
||||||
const supabase = isBrowser()
|
const supabase = isBrowser()
|
||||||
@@ -31,5 +31,5 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
|||||||
data: { session }
|
data: { session }
|
||||||
} = await supabase.auth.getSession();
|
} = await supabase.auth.getSession();
|
||||||
|
|
||||||
return { supabase, session };
|
return { supabase, session, url: url.origin };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SignUp from '$lib/components/SignUp.svelte';
|
import MagicLink from '$lib/components/MagicLink.svelte';
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
let { supabase, session } = data;
|
let { supabase, session, url } = data;
|
||||||
$: ({ supabase, session } = data);
|
$: ({ supabase, session, url } = data);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if session !== null}
|
{#if session !== null}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div id="sign-up" class="card shrink-0 w-full max-w-sm shadow-2xl">
|
<div id="sign-up" class="card shrink-0 w-full max-w-sm shadow-2xl">
|
||||||
<SignUp {supabase} />
|
<MagicLink {supabase} {url} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,41 +8,39 @@ export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession
|
|||||||
redirect(303, '/');
|
redirect(303, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: profile } = await supabase
|
const { data: user } = await supabase
|
||||||
.from('profiles')
|
.from('users')
|
||||||
.select(`username, full_name`)
|
.select(`name`)
|
||||||
.eq('id', session.user.id)
|
.eq('id', session.user.id)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
return { session, profile };
|
return { session, user };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions: Actions = {
|
export const actions: Actions = {
|
||||||
update: async ({ request, locals: { supabase, safeGetSession } }) => {
|
update: async ({ request, locals: { supabase, safeGetSession } }) => {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const fullName = formData.get('fullName') as string;
|
const name = formData.get('name') as string;
|
||||||
|
|
||||||
const { session } = await safeGetSession();
|
const { session } = await safeGetSession();
|
||||||
|
|
||||||
const { error } = await supabase.from('profiles').upsert({
|
if (!session) {
|
||||||
id: session?.user.id,
|
return fail(401, { name });
|
||||||
full_name: fullName,
|
}
|
||||||
username: session?.user.email,
|
const { error } = await supabase.from('users').update({
|
||||||
updated_at: new Date()
|
name: name,
|
||||||
});
|
}).eq('id', session?.user.id);
|
||||||
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return fail(500, {
|
return fail(500, {
|
||||||
fullName,
|
name,
|
||||||
username: session?.user.email
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fullName,
|
name,
|
||||||
username: session?.user.email
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
signout: async ({ locals: { supabase, safeGetSession } }) => {
|
signout: async ({ locals: { supabase, safeGetSession } }) => {
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
export let data;
|
export let data;
|
||||||
export let form;
|
export let form;
|
||||||
|
|
||||||
let { session, supabase, profile } = data;
|
let { session, supabase, user } = data;
|
||||||
$: ({ session, supabase, profile } = data);
|
$: ({ session, supabase, user } = data);
|
||||||
|
|
||||||
let profileForm: HTMLFormElement;
|
let profileForm: HTMLFormElement;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let fullName: string = profile?.full_name ?? '';
|
let name: string = user?.name ?? '';
|
||||||
|
|
||||||
const handleSubmit: SubmitFunction = () => {
|
const handleSubmit: SubmitFunction = () => {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -37,13 +37,8 @@
|
|||||||
bind:this={profileForm}
|
bind:this={profileForm}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label for="email">Username</label>
|
<label for="name">Name</label>
|
||||||
<input id="email" type="text" value={session.user.email} disabled />
|
<input id="name" name="name" type="text" value={form?.name ?? name} />
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<label for="fullName">Full Name</label>
|
|
||||||
<input id="fullName" name="fullName" type="text" value={form?.fullName ?? fullName} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { redirect, type RequestHandler } from '@sveltejs/kit'
|
||||||
|
|
||||||
|
export const GET: RequestHandler = async (event) => {
|
||||||
|
const { url, locals: { supabase } } = event;
|
||||||
|
const code = url.searchParams.get('code')
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
await supabase.auth.exchangeCodeForSession(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Create stripe customer and sync with supabase user
|
||||||
|
|
||||||
|
throw redirect(303, '/account')
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user