From be854a79b87009c44dc6ae9da20bf80d92f30e3a Mon Sep 17 00:00:00 2001 From: Colin Kallemein <cakallemein@gmail.com> Date: Sun, 27 Oct 2024 21:31:50 +0100 Subject: [PATCH] Added reset password modal form --- src/components/gui/ResetPassword.vue | 50 ++++++++++++++++++++++++++++ src/components/utilities/Modal.vue | 4 +-- src/screens/Login.vue | 9 ++++- src/services/authentication.ts | 9 +++++ src/stores/gameStore.ts | 7 +++- 5 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/components/gui/ResetPassword.vue diff --git a/src/components/gui/ResetPassword.vue b/src/components/gui/ResetPassword.vue new file mode 100644 index 0000000..0133a37 --- /dev/null +++ b/src/components/gui/ResetPassword.vue @@ -0,0 +1,50 @@ +<template> + <Modal :is-modal-open="gameStore.uiSettings.isPasswordResetOpen" @modal:close="() => gameStore.togglePasswordReset()" :modal-width="400" :modal-height="300" :is-resizable="false"> + <template #modalHeader> + <h3 class="m-0 font-medium shrink-0 text-white">Reset Password</h3> + </template> + + <template #modalBody> + <div class="h-[calc(100%_-_32px)] p-4"> + <form class="h-full flex flex-col justify-between" @submit.prevent="resetPasswordFunc"> + <div class="flex flex-col relative"> + <p>Fill in your email to receive a password reset request.</p> + <input type="email" name="email" class="input-field" v-model="email" placeholder="E-mail" /> + <span v-if="resetPasswordError" class="text-red-200 text-xs absolute top-full mt-1">{{ resetPasswordError }}</span> + </div> + <div class="grid grid-flow-col justify-stretch gap-4"> + <button class="btn-empty py-1.5 px-4 min-w-24 inline-block" @click.stop="gameStore.togglePasswordReset">Cancel</button> + <button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" type="submit">Send mail</button> + </div> + </form> + </div> + </template> + </Modal> +</template> + +<script setup lang="ts"> +import { onMounted, ref } from 'vue' +import { resetPassword } from '@/services/authentication' +import { useGameStore } from '@/stores/gameStore' +import Modal from '@/components/utilities/Modal.vue' + +const gameStore = useGameStore() +const email = ref('') +const resetPasswordError = ref('') + +async function resetPasswordFunc() { + // check if email is valid + if (email.value === '') { + resetPasswordError.value = 'Please enter an email' + return + } + + // send reset password event to server + const response = await resetPassword(email.value) + + if (response.success === undefined) { + resetPasswordError.value = response.error + return + } +} +</script> \ No newline at end of file diff --git a/src/components/utilities/Modal.vue b/src/components/utilities/Modal.vue index 3ddf0ba..4ba47ca 100644 --- a/src/components/utilities/Modal.vue +++ b/src/components/utilities/Modal.vue @@ -2,7 +2,7 @@ <Teleport to="body"> <div v-if="isModalOpenRef" class="fixed border-solid border-2 border-gray-500 z-50 flex flex-col backdrop-blur-sm shadow-lg" :style="modalStyle"> <div @mousedown="startDrag" class="cursor-move p-2.5 flex justify-between items-center border-solid border-0 border-b border-gray-500 relative"> - <div class="rounded-t-md absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-center bg-cover opacity-90"></div> + <div class="rounded-t absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-center bg-cover opacity-90"></div> <div class="relative z-10"> <slot name="modalHeader" /> </div> @@ -16,7 +16,7 @@ </div> </div> <div class="overflow-hidden grow relative"> - <div class="rounded-b-md absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center opacity-90"></div> + <div class="rounded-b absolute w-full h-full top-0 left-0 bg-[url('/assets/ui-texture.png')] bg-no-repeat bg-cover bg-center opacity-90"></div> <div class="relative z-10 h-full"> <slot name="modalBody" /> </div> diff --git a/src/screens/Login.vue b/src/screens/Login.vue index 6c13e26..529be5d 100644 --- a/src/screens/Login.vue +++ b/src/screens/Login.vue @@ -1,5 +1,6 @@ <template> <div class="relative max-lg:h-dvh flex flex-row-reverse"> + <ResetPassword /> <div class="lg:bg-gradient-to-l bg-gradient-to-b from-gray-900 to-transparent w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute left-0 max-lg:bottom-0 lg:top-0 z-10"></div> <div class="bg-[url('/assets/login/login-bg.png')] w-full lg:w-1/2 h-[35dvh] lg:h-dvh absolute left-0 max-lg:bottom-0 lg:top-0 bg-no-repeat bg-cover bg-center"></div> <div class="bg-gray-900 z-20 w-full lg:w-1/2 h-[65dvh] lg:h-dvh relative"> @@ -20,7 +21,7 @@ </div> <span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span> </div> - <button class="inline-flex self-end p-0 text-cyan-300 text-base">Forgot password?</button> + <button @click.stop="gameStore.togglePasswordReset" type="button" class="inline-flex self-end p-0 text-cyan-300 text-base">Forgot password?</button> <button class="btn-cyan px-0 xs:w-full" type="submit">Play now</button> <!-- Divider shape --> @@ -71,6 +72,7 @@ import { onMounted, ref } from 'vue' import { login, register } from '@/services/authentication' import { useGameStore } from '@/stores/gameStore' import { useCookies } from '@vueuse/integrations/useCookies' +import ResetPassword from '@/components/gui/ResetPassword.vue' const gameStore = useGameStore() const username = ref('') @@ -115,6 +117,11 @@ async function registerFunc() { return } + if (email.value === '') { + loginError.value = 'Please enter an email' + return + } + // send register event to server const response = await register(username.value, email.value, password.value) diff --git a/src/services/authentication.ts b/src/services/authentication.ts index 5d952e2..4c9ab2a 100644 --- a/src/services/authentication.ts +++ b/src/services/authentication.ts @@ -25,3 +25,12 @@ export async function login(username: string, password: string) { return { error: error.response.data.message } } } + +export async function resetPassword(email: string) { + try { + const response = await axios.post(`${config.server_endpoint}/reset-password`, { email }) + return { success: true, token: response.data.token } + } catch (error: any) { + return { error: error.response.data.message } + } +} \ No newline at end of file diff --git a/src/stores/gameStore.ts b/src/stores/gameStore.ts index ce28efd..5c8c39e 100644 --- a/src/stores/gameStore.ts +++ b/src/stores/gameStore.ts @@ -27,7 +27,8 @@ export const useGameStore = defineStore('game', { uiSettings: { isChatOpen: false, isCharacterProfileOpen: false, - isGmPanelOpen: false + isGmPanelOpen: false, + isPasswordResetOpen: false } } }, @@ -71,6 +72,9 @@ export const useGameStore = defineStore('game', { toggleCharacterProfile() { this.uiSettings.isCharacterProfileOpen = !this.uiSettings.isCharacterProfileOpen }, + togglePasswordReset() { + this.uiSettings.isPasswordResetOpen = !this.uiSettings.isPasswordResetOpen + }, initConnection() { this.connection = io(config.server_endpoint, { secure: !config.development, @@ -116,6 +120,7 @@ export const useGameStore = defineStore('game', { this.gameSettings.isCameraFollowingCharacter = false this.uiSettings.isChatOpen = false this.uiSettings.isCharacterProfileOpen = false + this.uiSettings.isPasswordResetOpen = false this.world.date = new Date() this.world.isRainEnabled = false