feat: implement basic auth workflow

This commit is contained in:
2026-04-11 21:36:50 -04:00
parent 652ac1e2af
commit 355f3c5dfa
4 changed files with 158 additions and 18 deletions

View File

@@ -1,10 +1,10 @@
export default defineNuxtRouteMiddleware((to) => {
const user = useSupabaseUser()
const publicRoutes = ['/login', '/signup', '/auth/callback']
const publicRoutes = ['/', '/login', '/signup', '/auth/callback']
if (publicRoutes.includes(to.path)) return
if (!user.value) {
return navigateTo('/login')
return navigateTo('/')
}
})

View File

@@ -1,22 +1,65 @@
<template>
<IonPage>
<IonHeader>
<IonToolbar color="primary">
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>Home</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
<h2>Welcome to OYS Borrow a Boat</h2>
</IonContent>
<!-- Splash: unauthenticated -->
<template v-if="!user">
<IonContent class="splash-content">
<div class="splash-center">
<img src="/oysqn_logo.png" alt="OYS Borrow a Boat" class="splash-logo" />
<IonButton expand="block" router-link="/login" class="splash-btn">Log In</IonButton>
</div>
</IonContent>
</template>
<!-- Home: authenticated -->
<template v-else>
<IonHeader>
<IonToolbar color="primary">
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>Home</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
<h2>Welcome to OYS Borrow a Boat</h2>
</IonContent>
</template>
</IonPage>
</template>
<script setup lang="ts">
import {
IonPage, IonHeader, IonToolbar, IonTitle, IonContent,
IonButtons, IonMenuButton,
IonButtons, IonMenuButton, IonButton,
} from '@ionic/vue'
const user = useSupabaseUser()
definePageMeta({ layout: false })
</script>
<style scoped>
.splash-content {
--background: var(--ion-color-light);
}
.splash-center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 2rem;
gap: 2rem;
}
.splash-logo {
max-width: 280px;
width: 100%;
}
.splash-btn {
width: 100%;
max-width: 280px;
}
</style>

View File

@@ -2,17 +2,114 @@
<IonPage>
<IonHeader>
<IonToolbar color="primary">
<IonButtons slot="start">
<IonBackButton default-href="/" />
</IonButtons>
<IonTitle>Sign In</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent class="ion-padding">
<!-- TODO: Auth form -->
<div class="login-form">
<template v-if="!sent">
<p class="ion-text-center">Enter your email address and we'll send you a sign-in link.</p>
<IonList>
<IonItem>
<IonLabel position="stacked">Email address</IonLabel>
<IonInput
v-model="email"
type="email"
placeholder="you@example.com"
autocomplete="email"
inputmode="email"
@keyup.enter="send"
/>
</IonItem>
</IonList>
<IonButton
expand="block"
class="ion-margin-top"
:disabled="!email || loading"
@click="send"
>
<IonSpinner v-if="loading" name="crescent" slot="start" />
Send Sign-In Link
</IonButton>
<p v-if="error" class="error-text ion-text-center">{{ error }}</p>
</template>
<template v-else>
<div class="sent-state ion-text-center">
<IonIcon :icon="mailOutline" class="sent-icon" />
<h2>Check your email</h2>
<p>A sign-in link was sent to <strong>{{ email }}</strong>. Open it on this device to sign in.</p>
<IonButton fill="outline" expand="block" class="ion-margin-top" @click="reset">
Use a different email
</IonButton>
</div>
</template>
</div>
</IonContent>
</IonPage>
</template>
<script setup lang="ts">
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/vue'
import {
IonPage, IonHeader, IonToolbar, IonTitle, IonContent,
IonButtons, IonBackButton, IonButton, IonList, IonItem,
IonLabel, IonInput, IonSpinner, IonIcon,
} from '@ionic/vue'
import { mailOutline } from 'ionicons/icons'
import { useAuthStore } from '~/stores/auth'
definePageMeta({ layout: false })
const auth = useAuthStore()
const email = ref('')
const loading = ref(false)
const sent = ref(false)
const error = ref('')
async function send() {
if (!email.value || loading.value) return
loading.value = true
error.value = ''
try {
await auth.sendMagicLink(email.value.trim())
sent.value = true
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Failed to send link. Please try again.'
} finally {
loading.value = false
}
}
function reset() {
sent.value = false
email.value = ''
error.value = ''
}
</script>
<style scoped>
.login-form {
max-width: 400px;
margin: 2rem auto;
}
.sent-state {
margin-top: 4rem;
}
.sent-icon {
font-size: 4rem;
color: var(--ion-color-primary);
margin-bottom: 1rem;
}
.error-text {
color: var(--ion-color-danger);
font-size: 0.875rem;
margin-top: 0.5rem;
}
</style>