116 lines
2.9 KiB
Vue
116 lines
2.9 KiB
Vue
<template>
|
|
<IonPage>
|
|
<IonHeader>
|
|
<IonToolbar color="primary">
|
|
<IonButtons slot="start">
|
|
<IonBackButton default-href="/" />
|
|
</IonButtons>
|
|
<IonTitle>Sign In</IonTitle>
|
|
</IonToolbar>
|
|
</IonHeader>
|
|
<IonContent class="ion-padding">
|
|
<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,
|
|
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>
|