added error handling and cleaned up styles #9

Merged
tiradoe merged 1 commit from auth-page-cleanup into main 2026-04-10 23:10:50 +00:00
6 changed files with 137 additions and 33 deletions
Showing only changes of commit 93c60498d0 - Show all commits

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

View file

@ -1,9 +1,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import incorrectPasswordGif from '~/assets/img/incorrect-password.gif';
const email = ref(""); const email = ref("");
const password = ref(""); const password = ref("");
const {login} = useAuth(); const {login} = useAuth();
const handleLogin = () => login(email.value, password.value) const errorMessage = ref("");
const handleLogin = async () => {
try {
await login(email.value, password.value)
} catch (error) {
errorMessage.value = "Invalid email or password";
}
}
</script> </script>
<template> <template>
@ -29,10 +39,22 @@ const handleLogin = () => login(email.value, password.value)
</div> </div>
<button type="submit">Submit</button> <button type="submit">Submit</button>
<div class="error-container">
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
<img v-if="errorMessage" :src="incorrectPasswordGif" alt="You didn't say the magic word." class="error-image"
height="200" width="300"/>
</div>
</form> </form>
</template> </template>
<style scoped> <style scoped>
.error-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.password-form { .password-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -46,4 +68,9 @@ const handleLogin = () => login(email.value, password.value)
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
} }
.error-message {
color: red;
text-align: center;
}
</style> </style>

View file

@ -5,27 +5,36 @@ const {register} = useAuth();
const username = ref(""); const username = ref("");
const email = ref(""); const email = ref("");
const errorMessage = ref("");
const handleRegistration = () => { const handleRegistration = async () => {
register(email.value, username.value); try {
await register(email.value, username.value);
} catch (error: any) {
console.error(error);
errorMessage.value = error.message;
}
} }
</script> </script>
<template> <template>
<form class="password-form" @submit.prevent="handleRegistration"> <form class="password-form" @submit.prevent="handleRegistration">
<div class="form-group">
<label for="username">Username</label>
<input id="username" v-model="username" type="text"/>
</div>
<div class="form-group"> <div class="form-group">
<label for="email">Email</label> <label for="email">Email</label>
<input id="email" v-model="email" type="email"/> <input id="email" v-model="email" type="email"/>
</div> </div>
<div class="form-group">
<label for="username">Username</label>
<input id="username" v-model="username" type="text"/>
</div>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</form> </form>
<div class="error-container">
<p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
</div>
</template> </template>
<style scoped> <style scoped>
@ -42,4 +51,17 @@ const handleRegistration = () => {
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
} }
.error-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.error-message {
color: red;
text-align: center;
}
</style> </style>

View file

@ -6,7 +6,16 @@ export const useAuth = () => {
baseURL: config.public.apiBase, baseURL: config.public.apiBase,
credentials: 'include', credentials: 'include',
}) })
await $api('/api/login', {method: 'POST', body: {email, password}}) await $api('/api/login', {
method: 'POST',
onResponseError({response}) {
if (response.status === 401) {
throw new Error('INVALID_CREDENTIALS')
}
},
body: {email, password}
}
)
window.location.href = '/lists' window.location.href = '/lists'
} }
@ -15,7 +24,16 @@ export const useAuth = () => {
baseURL: config.public.apiBase, baseURL: config.public.apiBase,
credentials: 'include', credentials: 'include',
}) })
await $api('/api/register', {method: 'POST', body: {email, username}}) await $api('/api/register', {
method: 'POST',
onResponseError({response}) {
console.log("wat", response)
if (response.status === 422) {
throw new Error(response._data.message)
}
},
body: {email, username}
})
await navigateTo('/auth/login') await navigateTo('/auth/login')
} }

View file

@ -1,26 +1,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import type {InviteStatus} from "~/types/invitation-status";
definePageMeta({
layout: 'auth'
})
const route = useRoute(); const route = useRoute();
const token = route.params.token as string; const token = route.params.token as string;
let isAuthorized = ref(false); let isAuthorized = ref(false);
let isProcessed = ref(false); let isProcessed = ref(false);
let failed = ref(false); let failed = ref(false);
let errorMessage = ref("An error occurred while accepting the request.");
enum InviteStatusEnum {
PENDING = "pending",
ACCEPTED = "accepted",
DECLINED = "declined",
NOT_FOUND = "not_found",
FAILED = "failed",
}
type InviteStatus = {
message: string
status: InviteStatusEnum
}
// check if the email from the invite has an existing user
const acceptInvitation = () => { const acceptInvitation = () => {
$api<InviteStatus>(`/api/invitations/${token}/accept`, { $api<InviteStatus>(`/api/invitations/${token}/accept`, {
method: "GET", method: "GET",
@ -29,6 +22,9 @@ const acceptInvitation = () => {
isAuthorized.value = false isAuthorized.value = false
isProcessed.value = true isProcessed.value = true
return; return;
} else if (response.status === 404) {
errorMessage.value = "Invitation not found."
isProcessed.value = true
} }
failed.value = true; failed.value = true;
@ -43,20 +39,48 @@ acceptInvitation();
</script> </script>
<template> <template>
<div class="content">
<div v-if="!isAuthorized && !isProcessed && !failed">
<span class="status-message">Checking your invitation...</span>
</div>
<div v-else-if="!isAuthorized && isProcessed && !failed" class="auth-message">
<span>You'll need to <NuxtLink class="link" to="/auth/login">log in</NuxtLink> or
<NuxtLink class="link" to="/auth/register">create an account</NuxtLink> to view this list.</span>
<span>If you're creating a new account, be sure to use the email address where you received this invitation.</span>
</div>
<div v-if="!isAuthorized && !isProcessed && !failed"> <div v-show="failed">
<span>Processing...</span> <span>{{ errorMessage }}</span>
</div> </div>
<div v-else-if="!isAuthorized && isProcessed && !failed">
<span>You need to <NuxtLink to="/auth/login">log in</NuxtLink> or <NuxtLink
to="/auth/register">create an account</NuxtLink></span>
</div>
<div v-show="failed">
<span>An error occurred while accepting the request.</span>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.auth-message {
display: flex;
flex-direction: column;
gap: 1rem;
font-size: 1rem;
}
.content {
display: flex;
justify-content: center;
align-items: center;
height: 50vh;
font-weight: bold;
font-size: 1.5rem;
text-align: center;
}
.link {
text-decoration: underline;
color: #007bff;
cursor: pointer;
}
.link:hover {
color: #0056b3;
}
</style> </style>

View file

@ -0,0 +1,13 @@
export enum InviteStatusEnum {
PENDING = "pending",
ACCEPTED = "accepted",
DECLINED = "declined",
NOT_FOUND = "not_found",
FAILED = "failed",
}
export type InviteStatus = {
message: string
status: InviteStatusEnum
}