Compare commits
2 commits
21c3d42d90
...
8ebb55e31a
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ebb55e31a | |||
| 93c60498d0 |
6 changed files with 137 additions and 33 deletions
BIN
app/assets/img/incorrect-password.gif
Normal file
BIN
app/assets/img/incorrect-password.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 334 KiB |
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
13
app/types/invitation-status.ts
Normal file
13
app/types/invitation-status.ts
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue