Refactored screens & login forms, fixed pw reset modal, WIP new pw form
This commit is contained in:
parent
584262a59b
commit
bbcb84ed03
@ -11,10 +11,10 @@ import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
|||||||
import Notifications from '@/components/utilities/Notifications.vue'
|
import Notifications from '@/components/utilities/Notifications.vue'
|
||||||
import GmTools from '@/components/gameMaster/GmTools.vue'
|
import GmTools from '@/components/gameMaster/GmTools.vue'
|
||||||
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
||||||
import Login from '@/screens/Login.vue'
|
import Login from '@/components/screens/Login.vue'
|
||||||
import Characters from '@/screens/Characters.vue'
|
import Characters from '@/components/screens/Characters.vue'
|
||||||
import Game from '@/screens/Game.vue'
|
import Game from '@/components/screens/Game.vue'
|
||||||
import ZoneEditor from '@/screens/ZoneEditor.vue'
|
import ZoneEditor from '@/components/screens/ZoneEditor.vue'
|
||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
@ -168,8 +168,8 @@ function stopDrag() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function adjustPosition() {
|
function adjustPosition() {
|
||||||
x.value = Math.max(0, Math.min(x.value, window.innerWidth - width.value))
|
x.value = Math.min(x.value, window.innerWidth - width.value)
|
||||||
y.value = Math.max(0, Math.min(y.value, window.innerHeight - height.value))
|
y.value = Math.min(y.value, window.innerHeight - height.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializePosition() {
|
function initializePosition() {
|
||||||
@ -236,6 +236,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
removeEventListener('keydown', keyPress)
|
||||||
removeEventListener('mousemove', drag)
|
removeEventListener('mousemove', drag)
|
||||||
removeEventListener('mouseup', stopDrag)
|
removeEventListener('mouseup', stopDrag)
|
||||||
})
|
})
|
||||||
|
@ -1,159 +1,159 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-gray-900 relative">
|
<div class="bg-gray-900 relative">
|
||||||
<div class="absolute bg-[url('/assets/shapes/select-screen-bg-shape.svg')] bg-no-repeat bg-center w-full h-full"></div>
|
<div class="absolute bg-[url('/assets/shapes/select-screen-bg-shape.svg')] bg-no-repeat bg-center w-full h-full"></div>
|
||||||
<div class="ui-wrapper h-dvh flex flex-col justify-center items-center gap-20 px-10 sm:px-20">
|
<div class="ui-wrapper h-dvh flex flex-col justify-center items-center gap-20 px-10 sm:px-20">
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<div class="flex gap-14 w-full max-h-[650px] overflow-x-auto" v-if="!isLoading">
|
<div class="flex gap-14 w-full max-h-[650px] overflow-x-auto" v-if="!isLoading">
|
||||||
<!-- CHARACTER LIST -->
|
<!-- CHARACTER LIST -->
|
||||||
<div v-for="character in characters" :key="character.id" class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character" :class="{ active: selected_character == character.id }">
|
<div v-for="character in characters" :key="character.id" class="group first:ml-auto last:mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 relative shadow-character" :class="{ active: selected_character == character.id }">
|
||||||
<img src="/assets/ui-elements/ui-box-outer.svg" class="absolute w-full h-full" />
|
<img src="/assets/ui-elements/ui-box-outer.svg" class="absolute w-full h-full" />
|
||||||
<img src="/assets/ui-elements/ui-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)]" />
|
<img src="/assets/ui-elements/ui-box-inner.svg" class="absolute left-2 bottom-2 w-[calc(100%_-_16px)] h-[calc(100%_-_40px)]" />
|
||||||
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
|
<input class="opacity-0 h-full w-full absolute m-0 z-10" type="radio" :id="character.id" name="character" :value="character.id" v-model="selected_character" />
|
||||||
<label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
|
<label class="font-bold absolute left-1/2 top-4 max-w-32 -translate-x-1/2 -translate-y-1/2 text-center text-ellipsis overflow-hidden whitespace-nowrap drop-shadow-text" :for="character.id">{{ character.name }}</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="delete bg-red w-8 h-8 p-[3px] rounded-full absolute -right-4 top-0 -translate-y-1/2 z-10 border-2 border-solid border-white hover:bg-red-300"
|
class="delete bg-red w-8 h-8 p-[3px] rounded-full absolute -right-4 top-0 -translate-y-1/2 z-10 border-2 border-solid border-white hover:bg-red-300"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
deletingCharacter = character
|
deletingCharacter = character
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<img draggable="false" src="/assets/icons/trashcan.svg" />
|
<img draggable="false" src="/assets/icons/trashcan.svg" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="sprite-container flex flex-col items-center m-auto">
|
<div class="sprite-container flex flex-col items-center m-auto">
|
||||||
<img class="drop-shadow-20" draggable="false" src="/assets/avatar/default/0.png" />
|
<img class="drop-shadow-20" draggable="false" src="/assets/avatar/default/0.png" />
|
||||||
</div>
|
</div>
|
||||||
<span class="absolute bottom-6 w-full text-center translate-y-1/2 z-10">Lvl. {{ character.level }}</span>
|
<span class="absolute bottom-6 w-full text-center translate-y-1/2 z-10">Lvl. {{ character.level }}</span>
|
||||||
<div class="selected-character group-[.active]:max-w-[170px] absolute max-w-0 w-4/6 h-[3px] bg-gray-500 rounded-[3px] left-1/2 -bottom-4 -translate-x-1/2 transition-all ease-in-out duration-300"></div>
|
<div class="selected-character group-[.active]:max-w-[170px] absolute max-w-0 w-4/6 h-[3px] bg-gray-500 rounded-[3px] left-1/2 -bottom-4 -translate-x-1/2 transition-all ease-in-out duration-300"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="character new-character first:ml-auto mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 rounded-2xl relative bg-gray-500/50 bg-no-repeat shadow-character" v-if="characters.length < 4">
|
<div class="character new-character first:ml-auto mr-auto m-4 w-[170px] h-[275px] flex flex-col shrink-0 rounded-2xl relative bg-gray-500/50 bg-no-repeat shadow-character" v-if="characters.length < 4">
|
||||||
<button class="h-full w-full py-10 flex flex-col justify-between" @click="isModalOpen = true">
|
<button class="h-full w-full py-10 flex flex-col justify-between" @click="isModalOpen = true">
|
||||||
<div class="filler"></div>
|
<div class="filler"></div>
|
||||||
<img class="w-24 h-24 m-auto" draggable="false" src="/assets/icons/plus-icon.svg" />
|
<img class="w-24 h-24 m-auto" draggable="false" src="/assets/icons/plus-icon.svg" />
|
||||||
<span class="self-center text-base absolute bottom-5 w-full text-center translate-y-1/2 z-10">Create new</span>
|
<span class="self-center text-base absolute bottom-5 w-full text-center translate-y-1/2 z-10">Create new</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<img class="w-20 invert-80" src="/assets/icons/loading-icon1.svg" />
|
<img class="w-20 invert-80" src="/assets/icons/loading-icon1.svg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button-wrapper flex gap-8" v-if="!isLoading">
|
<div class="button-wrapper flex gap-8" v-if="!isLoading">
|
||||||
<button
|
<button
|
||||||
class="btn-red py-2 pr-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-red/50 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
class="btn-red py-2 pr-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-red/50 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
||||||
@click.stop="gameStore.disconnectSocket()"
|
@click.stop="gameStore.disconnectSocket()"
|
||||||
>
|
>
|
||||||
<img class="h-8 drop-shadow-20 rotate-180" draggable="false" src="/assets/icons/arrow.svg" alt="Logout icon" />
|
<img class="h-8 drop-shadow-20 rotate-180" draggable="false" src="/assets/icons/arrow.svg" alt="Logout icon" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn-cyan py-2 px-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-cyan-800 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
class="btn-cyan py-2 px-2.5 pl-8 min-w-24 relative rounded text-xl flex gap-4 items-center transition-all ease-in-out duration-200 hover:gap-5 disabled:bg-cyan-800 disabled:hover:bg-opacity-50 disabled:cursor-not-allowed disabled:hover:gap-[15px]"
|
||||||
:disabled="!selected_character"
|
:disabled="!selected_character"
|
||||||
@click="select_character()"
|
@click="select_character()"
|
||||||
>
|
>
|
||||||
PLAY
|
PLAY
|
||||||
<img class="h-8 drop-shadow-20" draggable="false" src="/assets/icons/arrow.svg" alt="Play icon" />
|
<img class="h-8 drop-shadow-20" draggable="false" src="/assets/icons/arrow.svg" alt="Play icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CREATE CHARACTER MODAL -->
|
<!-- CREATE CHARACTER MODAL -->
|
||||||
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false" :modal-width="430" :modal-height="275">
|
<Modal :isModalOpen="isModalOpen" @modal:close="isModalOpen = false" :modal-width="430" :modal-height="275">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium text-white">Create your character</h3>
|
<h3 class="m-0 font-medium text-white">Create your character</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="p-4 h-[calc(100%_-_32px)]">
|
<div class="p-4 h-[calc(100%_-_32px)]">
|
||||||
<form method="post" @submit.prevent="create" class="h-full flex flex-col justify-between">
|
<form method="post" @submit.prevent="create" class="h-full flex flex-col justify-between">
|
||||||
<div class="form-field-full">
|
<div class="form-field-full">
|
||||||
<label for="name" class="text-white">Nickname</label>
|
<label for="name" class="text-white">Nickname</label>
|
||||||
<input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname.." />
|
<input class="input-field" v-model="name" name="name" id="name" placeholder="Enter a nickname.." />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-col justify-stretch gap-4">
|
<div class="grid grid-flow-col justify-stretch gap-4">
|
||||||
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isModalOpen = false">Cancel</button>
|
<button type="button" class="btn-empty py-1.5 px-4 inline-block" @click.prevent="isModalOpen = false">Cancel</button>
|
||||||
<button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button>
|
<button class="btn-cyan py-1.5 px-4 inline-block" type="submit">Create</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<!-- DELETE CHARACTER MODAL -->
|
<!-- DELETE CHARACTER MODAL -->
|
||||||
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
|
<ConfirmationModal v-if="deletingCharacter != null" :confirm-function="delete_character.bind(this, deletingCharacter.id)" :cancel-function="(() => (deletingCharacter = null)).bind(this)" confirm-button-text="Delete">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium text-white">Delete character?</h3>
|
<h3 class="m-0 font-medium text-white">Delete character?</h3>
|
||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<p class="mt-0 mb-5 text-white text-lg">
|
<p class="mt-0 mb-5 text-white text-lg">
|
||||||
Do you want to permanently delete <span class="font-extrabold text-white">{{ deletingCharacter.name }}</span
|
Do you want to permanently delete <span class="font-extrabold text-white">{{ deletingCharacter.name }}</span
|
||||||
>?
|
>?
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
import { type Character as CharacterT } from '@/types'
|
import { type Character as CharacterT } from '@/types'
|
||||||
import ConfirmationModal from '@/components/utilities/ConfirmationModal.vue'
|
import ConfirmationModal from '@/components/utilities/ConfirmationModal.vue'
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const characters = ref([])
|
const characters = ref([])
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const deletingCharacter = ref(null)
|
const deletingCharacter = ref(null)
|
||||||
|
|
||||||
// Fetch characters
|
// Fetch characters
|
||||||
gameStore.connection?.on('character:list', (data: any) => {
|
gameStore.connection?.on('character:list', (data: any) => {
|
||||||
characters.value = data
|
characters.value = data
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const audio = new Audio('/assets/music/login.mp3')
|
const audio = new Audio('/assets/music/login.mp3')
|
||||||
await audio.play()
|
await audio.play()
|
||||||
|
|
||||||
// wait 0.75 sec
|
// wait 0.75 sec
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
gameStore.connection?.emit('character:list')
|
gameStore.connection?.emit('character:list')
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}, 750)
|
}, 750)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Select character logics
|
// Select character logics
|
||||||
const selected_character = ref(null)
|
const selected_character = ref(null)
|
||||||
function select_character() {
|
function select_character() {
|
||||||
if (!selected_character.value) return
|
if (!selected_character.value) return
|
||||||
deletingCharacter.value = null
|
deletingCharacter.value = null
|
||||||
gameStore.connection?.emit('character:connect', { character_id: selected_character.value })
|
gameStore.connection?.emit('character:connect', { character_id: selected_character.value })
|
||||||
gameStore.connection?.on('character:connect', (data: CharacterT) => gameStore.setCharacter(data))
|
gameStore.connection?.on('character:connect', (data: CharacterT) => gameStore.setCharacter(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete character logics
|
// Delete character logics
|
||||||
function delete_character(character_id: number) {
|
function delete_character(character_id: number) {
|
||||||
if (!character_id) return
|
if (!character_id) return
|
||||||
deletingCharacter.value = null
|
deletingCharacter.value = null
|
||||||
gameStore.connection?.emit('character:delete', { character_id: character_id })
|
gameStore.connection?.emit('character:delete', { character_id: character_id })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create character logics
|
// Create character logics
|
||||||
const isModalOpen = ref(false)
|
const isModalOpen = ref(false)
|
||||||
const name = ref('')
|
const name = ref('')
|
||||||
function create() {
|
function create() {
|
||||||
gameStore.connection?.on('character:create:success', (data: CharacterT) => {
|
gameStore.connection?.on('character:create:success', (data: CharacterT) => {
|
||||||
gameStore.setCharacter(data)
|
gameStore.setCharacter(data)
|
||||||
isModalOpen.value = false
|
isModalOpen.value = false
|
||||||
})
|
})
|
||||||
gameStore.connection?.emit('character:create', { name: name.value })
|
gameStore.connection?.emit('character:create', { name: name.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
gameStore.connection?.off('character:list')
|
gameStore.connection?.off('character:list')
|
||||||
gameStore.connection?.off('character:connect')
|
gameStore.connection?.off('character:connect')
|
||||||
gameStore.connection?.off('character:create:success')
|
gameStore.connection?.off('character:create:success')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
@ -1,82 +1,82 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex justify-center items-center h-dvh relative">
|
<div class="flex justify-center items-center h-dvh relative">
|
||||||
<Game :config="gameConfig" @create="createGame">
|
<Game :config="gameConfig" @create="createGame">
|
||||||
<Scene name="main" @preload="preloadScene" @create="createScene">
|
<Scene name="main" @preload="preloadScene" @create="createScene">
|
||||||
<Menu />
|
<Menu />
|
||||||
<Hud />
|
<Hud />
|
||||||
<Hotkeys />
|
<Hotkeys />
|
||||||
<Minimap />
|
<Minimap />
|
||||||
<Zone />
|
<Zone />
|
||||||
<Chat />
|
<Chat />
|
||||||
<ExpBar />
|
<ExpBar />
|
||||||
|
|
||||||
<CharacterProfile />
|
<CharacterProfile />
|
||||||
<Effects />
|
<Effects />
|
||||||
</Scene>
|
</Scene>
|
||||||
</Game>
|
</Game>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
import 'phaser'
|
import 'phaser'
|
||||||
import { Game, Scene } from 'phavuer'
|
import { Game, Scene } from 'phavuer'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import Menu from '@/components/gui/Menu.vue'
|
import Menu from '@/components/gui/Menu.vue'
|
||||||
import ExpBar from '@/components/gui/ExpBar.vue'
|
import ExpBar from '@/components/gui/ExpBar.vue'
|
||||||
import Hud from '@/components/gui/Hud.vue'
|
import Hud from '@/components/gui/Hud.vue'
|
||||||
import Zone from '@/components/zone/Zone.vue'
|
import Zone from '@/components/zone/Zone.vue'
|
||||||
import Hotkeys from '@/components/gui/Hotkeys.vue'
|
import Hotkeys from '@/components/gui/Hotkeys.vue'
|
||||||
import Chat from '@/components/gui/Chat.vue'
|
import Chat from '@/components/gui/Chat.vue'
|
||||||
import CharacterProfile from '@/components/gui/CharacterProfile.vue'
|
import CharacterProfile from '@/components/gui/CharacterProfile.vue'
|
||||||
import Effects from '@/components/Effects.vue'
|
import Effects from '@/components/Effects.vue'
|
||||||
import Minimap from '@/components/gui/Minimap.vue'
|
import Minimap from '@/components/gui/Minimap.vue'
|
||||||
import AwaitLoaderPlugin from 'phaser3-rex-plugins/plugins/awaitloader-plugin'
|
import AwaitLoaderPlugin from 'phaser3-rex-plugins/plugins/awaitloader-plugin'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
const gameConfig = {
|
const gameConfig = {
|
||||||
name: config.name,
|
name: config.name,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
||||||
resolution: 5,
|
resolution: 5,
|
||||||
plugins: {
|
plugins: {
|
||||||
global: [
|
global: [
|
||||||
{
|
{
|
||||||
key: 'rexAwaitLoader',
|
key: 'rexAwaitLoader',
|
||||||
plugin: AwaitLoaderPlugin,
|
plugin: AwaitLoaderPlugin,
|
||||||
start: true
|
start: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createGame = (game: Phaser.Game) => {
|
const createGame = (game: Phaser.Game) => {
|
||||||
/**
|
/**
|
||||||
* Resize the game when the window is resized
|
* Resize the game when the window is resized
|
||||||
*/
|
*/
|
||||||
addEventListener('resize', () => {
|
addEventListener('resize', () => {
|
||||||
game.scale.resize(window.innerWidth, window.innerHeight)
|
game.scale.resize(window.innerWidth, window.innerHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
// We don't support canvas mode, only WebGL
|
// We don't support canvas mode, only WebGL
|
||||||
if (game.renderer.type === Phaser.CANVAS) {
|
if (game.renderer.type === Phaser.CANVAS) {
|
||||||
gameStore.addNotification({
|
gameStore.addNotification({
|
||||||
title: 'Warning',
|
title: 'Warning',
|
||||||
message: 'Your browser does not support WebGL. Please use a modern browser like Chrome, Firefox, or Edge.'
|
message: 'Your browser does not support WebGL. Please use a modern browser like Chrome, Firefox, or Edge.'
|
||||||
})
|
})
|
||||||
gameStore.disconnectSocket()
|
gameStore.disconnectSocket()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function preloadScene(scene: Phaser.Scene) {
|
function preloadScene(scene: Phaser.Scene) {
|
||||||
/**
|
/**
|
||||||
* Load the base assets into the Phaser scene
|
* Load the base assets into the Phaser scene
|
||||||
*/
|
*/
|
||||||
scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
|
scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
|
||||||
scene.load.image('waypoint', '/assets/waypoint.png')
|
scene.load.image('waypoint', '/assets/waypoint.png')
|
||||||
}
|
}
|
||||||
|
|
||||||
function createScene(scene: Phaser.Scene) {}
|
function createScene(scene: Phaser.Scene) {}
|
||||||
</script>
|
</script>
|
@ -1,25 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col justify-center items-center h-dvh relative">
|
<div class="flex flex-col justify-center items-center h-dvh relative">
|
||||||
<button @click="continueBtnClick" class="w-32 h-12 rounded-full bg-gray-500 flex items-center justify-between px-4 hover:bg-gray-600 transition-colors">
|
<button @click="continueBtnClick" class="w-32 h-12 rounded-full bg-gray-500 flex items-center justify-between px-4 hover:bg-gray-600 transition-colors">
|
||||||
<span class="text-white text-lg flex-1 text-center">Play</span>
|
<span class="text-white text-lg flex-1 text-center">Play</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" async>
|
<script setup lang="ts" async>
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
function continueBtnClick() {
|
function continueBtnClick() {
|
||||||
// Play music
|
// Play music
|
||||||
const audio = new Audio('/assets/music/login.mp3')
|
const audio = new Audio('/assets/music/login.mp3')
|
||||||
audio.play()
|
audio.play()
|
||||||
|
|
||||||
// Set isLoaded to true
|
// Set isLoaded to true
|
||||||
gameStore.game.isLoaded = true
|
gameStore.game.isLoaded = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
46
src/components/screens/Login.vue
Normal file
46
src/components/screens/Login.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<!-- @TODO this must be shown over the login screen -->
|
||||||
|
<div class="relative max-lg:h-dvh flex flex-row-reverse">
|
||||||
|
<ResetPassword :isModalOpen="isPasswordResetFormShown" @close="() => isPasswordResetFormShown = false" />
|
||||||
|
<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">
|
||||||
|
<div class="h-dvh flex items-center lg:justify-center flex-col px-8 max-lg:pt-20">
|
||||||
|
<img src="/assets/login/sq-logo-v1.svg" class="mb-10" alt="Sylvan Quest logo" />
|
||||||
|
<div class="relative">
|
||||||
|
<img src="/assets/ui-elements/ui-box-outer.svg" class="absolute w-full h-full" alt="UI box outer" />
|
||||||
|
<img src="/assets/ui-elements/ui-box-inner.svg" class="absolute left-2 top-2 w-[calc(100%_-_16px)] h-[calc(100%_-_16px)] max-lg:hidden" alt="UI box inner" />
|
||||||
|
|
||||||
|
<!-- Login Form -->
|
||||||
|
<LoginForm v-if="currentForm === 'login'" @openResetPasswordModal="() => isPasswordResetFormShown = true" @switchToRegister="currentForm = 'register'" />
|
||||||
|
|
||||||
|
<!-- Register Form -->
|
||||||
|
<RegisterForm v-if="currentForm === 'register'" @switchToLogin="currentForm = 'login'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
import LoginForm from '@/components/screens/partials/LoginForm.vue'
|
||||||
|
import RegisterForm from '@/components/screens/partials/RegisterForm.vue'
|
||||||
|
import ResetPassword from '@/components/utilities/ResetPassword.vue'
|
||||||
|
|
||||||
|
const isPasswordResetFormShown = ref(false)
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const currentForm = ref('login')
|
||||||
|
|
||||||
|
// automatic login because of development
|
||||||
|
onMounted(async () => {
|
||||||
|
const token = useCookies().get('token')
|
||||||
|
if (token) {
|
||||||
|
gameStore.setToken(token)
|
||||||
|
gameStore.initConnection()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
@ -1,85 +1,85 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex justify-center items-center h-dvh relative">
|
<div class="flex justify-center items-center h-dvh relative">
|
||||||
<Game :config="gameConfig" @create="createGame">
|
<Game :config="gameConfig" @create="createGame">
|
||||||
<Scene name="main" @preload="preloadScene" @create="createScene">
|
<Scene name="main" @preload="preloadScene" @create="createScene">
|
||||||
<ZoneEditor :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" />
|
<ZoneEditor :key="JSON.stringify(`${zoneEditorStore.zone?.id}_${zoneEditorStore.zone?.createdAt}_${zoneEditorStore.zone?.updatedAt}`)" />
|
||||||
</Scene>
|
</Scene>
|
||||||
</Game>
|
</Game>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/config'
|
import config from '@/config'
|
||||||
import 'phaser'
|
import 'phaser'
|
||||||
import { Game, Scene } from 'phavuer'
|
import { Game, Scene } from 'phavuer'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
import { useZoneEditorStore } from '@/stores/zoneEditorStore'
|
||||||
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
|
import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
|
||||||
import AwaitLoaderPlugin from 'phaser3-rex-plugins/plugins/awaitloader-plugin'
|
import AwaitLoaderPlugin from 'phaser3-rex-plugins/plugins/awaitloader-plugin'
|
||||||
import { loadTexture } from '@/composables/gameComposable'
|
import { loadTexture } from '@/composables/gameComposable'
|
||||||
import type { AssetDataT } from '@/types'
|
import type { AssetDataT } from '@/types'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const zoneEditorStore = useZoneEditorStore()
|
const zoneEditorStore = useZoneEditorStore()
|
||||||
|
|
||||||
const gameConfig = {
|
const gameConfig = {
|
||||||
name: config.name,
|
name: config.name,
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
type: Phaser.AUTO, // AUTO, CANVAS, WEBGL, HEADLESS
|
||||||
resolution: 5,
|
resolution: 5,
|
||||||
plugins: {
|
plugins: {
|
||||||
global: [
|
global: [
|
||||||
{
|
{
|
||||||
key: 'rexAwaitLoader',
|
key: 'rexAwaitLoader',
|
||||||
plugin: AwaitLoaderPlugin,
|
plugin: AwaitLoaderPlugin,
|
||||||
start: true
|
start: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const createGame = (game: Phaser.Game) => {
|
const createGame = (game: Phaser.Game) => {
|
||||||
/**
|
/**
|
||||||
* Resize the game when the window is resized
|
* Resize the game when the window is resized
|
||||||
*/
|
*/
|
||||||
addEventListener('resize', () => {
|
addEventListener('resize', () => {
|
||||||
game.scale.resize(window.innerWidth, window.innerHeight)
|
game.scale.resize(window.innerWidth, window.innerHeight)
|
||||||
})
|
})
|
||||||
|
|
||||||
// We don't support canvas mode, only WebGL
|
// We don't support canvas mode, only WebGL
|
||||||
if (game.renderer.type === Phaser.CANVAS) {
|
if (game.renderer.type === Phaser.CANVAS) {
|
||||||
gameStore.addNotification({
|
gameStore.addNotification({
|
||||||
title: 'Warning',
|
title: 'Warning',
|
||||||
message: 'Your browser does not support WebGL. Please use a modern browser like Chrome, Firefox, or Edge.'
|
message: 'Your browser does not support WebGL. Please use a modern browser like Chrome, Firefox, or Edge.'
|
||||||
})
|
})
|
||||||
gameStore.disconnectSocket()
|
gameStore.disconnectSocket()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const preloadScene = async (scene: Phaser.Scene) => {
|
const preloadScene = async (scene: Phaser.Scene) => {
|
||||||
/**
|
/**
|
||||||
* Load the base assets into the Phaser scene
|
* Load the base assets into the Phaser scene
|
||||||
*/
|
*/
|
||||||
scene.load.image('BLOCK', '/assets/zone/bt_tile.png')
|
scene.load.image('BLOCK', '/assets/zone/bt_tile.png')
|
||||||
scene.load.image('TELEPORT', '/assets/zone/tp_tile.png')
|
scene.load.image('TELEPORT', '/assets/zone/tp_tile.png')
|
||||||
scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
|
scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
|
||||||
scene.load.image('waypoint', '/assets/waypoint.png')
|
scene.load.image('waypoint', '/assets/waypoint.png')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because Phaser can't load tiles after the scene with map in it is created,
|
* Because Phaser can't load tiles after the scene with map in it is created,
|
||||||
* we need to load and cache all the tiles first.
|
* we need to load and cache all the tiles first.
|
||||||
* Then load them into the scene.
|
* Then load them into the scene.
|
||||||
*/
|
*/
|
||||||
scene.load.rexAwait(async function (successCallback: any) {
|
scene.load.rexAwait(async function (successCallback: any) {
|
||||||
const tiles: AssetDataT[] = await fetch(config.server_endpoint + '/assets/list_tiles').then((response) => response.json())
|
const tiles: AssetDataT[] = await fetch(config.server_endpoint + '/assets/list_tiles').then((response) => response.json())
|
||||||
for await (const tile of tiles) {
|
for await (const tile of tiles) {
|
||||||
await loadTexture(scene, tile)
|
await loadTexture(scene, tile)
|
||||||
}
|
}
|
||||||
|
|
||||||
successCallback()
|
successCallback()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const createScene = async (scene: Phaser.Scene) => {}
|
const createScene = async (scene: Phaser.Scene) => {}
|
||||||
</script>
|
</script>
|
69
src/components/screens/partials/LoginForm.vue
Normal file
69
src/components/screens/partials/LoginForm.vue
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="loginFunc" class="relative px-6 py-11">
|
||||||
|
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
||||||
|
<div class="w-full grid gap-3 relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="username-login" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
||||||
|
<div class="relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="password-login" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
||||||
|
<button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
||||||
|
</div>
|
||||||
|
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
||||||
|
</div>
|
||||||
|
<button @click.stop="() => emit('openResetPasswordModal')" 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 -->
|
||||||
|
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-36 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-8">
|
||||||
|
<p class="m-0 text-center">Don't have an account? <button class="text-cyan-300 text-base p-0" @click.prevent="() => emit('switchToRegister')">Sign up</button></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { login } from '@/services/authentication'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
|
||||||
|
const emit = defineEmits(['openResetPasswordModal', 'switchToRegister'])
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const username = ref('')
|
||||||
|
const password = ref('')
|
||||||
|
const loginError = ref('')
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
|
// automatic login because of development
|
||||||
|
onMounted(async () => {
|
||||||
|
const token = useCookies().get('token')
|
||||||
|
if (token) {
|
||||||
|
gameStore.setToken(token)
|
||||||
|
gameStore.initConnection()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loginFunc() {
|
||||||
|
// check if username and password are valid
|
||||||
|
if (username.value === '' || password.value === '') {
|
||||||
|
loginError.value = 'Please enter a valid username and password'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// send login event to server
|
||||||
|
const response = await login(username.value, password.value)
|
||||||
|
|
||||||
|
if (response.success === undefined) {
|
||||||
|
loginError.value = response.error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gameStore.setToken(response.token)
|
||||||
|
gameStore.initConnection()
|
||||||
|
return true // Indicate success
|
||||||
|
}
|
||||||
|
</script>
|
67
src/components/screens/partials/NewPasswordForm.vue
Normal file
67
src/components/screens/partials/NewPasswordForm.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="newPasswordFunc" class="relative px-6 py-11">
|
||||||
|
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
||||||
|
<div class="w-full grid gap-3 relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="username-register" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="email-register" v-model="email" type="email" name="email" placeholder="Email" required />
|
||||||
|
<div class="relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="password-register" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
||||||
|
<button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
||||||
|
</div>
|
||||||
|
<span v-if="newPasswordError" class="text-red-200 text-xs absolute top-full mt-1">{{ newPasswordError }}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn-cyan xs:w-full" type="submit">Register now</button>
|
||||||
|
|
||||||
|
<!-- Divider shape -->
|
||||||
|
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-36 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-8">
|
||||||
|
<p class="m-0 text-center"><button class="text-cyan-300 text-base p-0" @click.prevent="() => emit('switchToLogin')">Back to login</button></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { newPassword } from '@/services/authentication'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
|
||||||
|
const emit = defineEmits(['switchToLogin'])
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const username = ref('')
|
||||||
|
const password = ref('')
|
||||||
|
const email = ref('')
|
||||||
|
const newPasswordError = ref('')
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
|
// automatic login because of development
|
||||||
|
onMounted(async () => {
|
||||||
|
const token = useCookies().get('token')
|
||||||
|
if (token) {
|
||||||
|
gameStore.setToken(token)
|
||||||
|
gameStore.initConnection()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function newPasswordFunc() {
|
||||||
|
// check if username and password are valid
|
||||||
|
if (password.value === '') {
|
||||||
|
newPasswordError.value = 'Please enter a password'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// send new password event to server
|
||||||
|
const response = await newPassword(token, password.value)
|
||||||
|
|
||||||
|
if (response.success === undefined) {
|
||||||
|
newPasswordError.value = response.error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
97
src/components/screens/partials/RegisterForm.vue
Normal file
97
src/components/screens/partials/RegisterForm.vue
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<form @submit.prevent="registerFunc" class="relative px-6 py-11">
|
||||||
|
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
||||||
|
<div class="w-full grid gap-3 relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="username-register" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="email-register" v-model="email" type="email" name="email" placeholder="Email" required />
|
||||||
|
<div class="relative">
|
||||||
|
<input class="input-field xs:min-w-[350px] min-w-64" id="password-register" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
||||||
|
<button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
||||||
|
</div>
|
||||||
|
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn-cyan xs:w-full" type="submit">Register now</button>
|
||||||
|
|
||||||
|
<!-- Divider shape -->
|
||||||
|
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-36 h-full bg-gray-300"></div>
|
||||||
|
<div class="w-0.5 h-full bg-gray-300"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-8">
|
||||||
|
<p class="m-0 text-center">Already have an account? <button class="text-cyan-300 text-base p-0" @click.prevent="() => emit('switchToLogin')">Log in</button></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { login, register } from '@/services/authentication'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
|
||||||
|
const emit = defineEmits(['switchToLogin'])
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
const username = ref('')
|
||||||
|
const password = ref('')
|
||||||
|
const email = ref('')
|
||||||
|
const loginError = ref('')
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
|
// automatic login because of development
|
||||||
|
onMounted(async () => {
|
||||||
|
const token = useCookies().get('token')
|
||||||
|
if (token) {
|
||||||
|
gameStore.setToken(token)
|
||||||
|
gameStore.initConnection()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function loginFunc() {
|
||||||
|
// check if username and password are valid
|
||||||
|
if (username.value === '' || password.value === '') {
|
||||||
|
loginError.value = 'Please enter a valid username and password'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// send login event to server
|
||||||
|
const response = await login(username.value, password.value)
|
||||||
|
|
||||||
|
if (response.success === undefined) {
|
||||||
|
loginError.value = response.error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gameStore.setToken(response.token)
|
||||||
|
gameStore.initConnection()
|
||||||
|
return true // Indicate success
|
||||||
|
}
|
||||||
|
|
||||||
|
async function registerFunc() {
|
||||||
|
// check if username and password are valid
|
||||||
|
if (username.value === '' || email.value === '' || password.value === '') {
|
||||||
|
loginError.value = 'Please enter a valid username, email, and password'
|
||||||
|
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)
|
||||||
|
|
||||||
|
if (response.success === undefined) {
|
||||||
|
loginError.value = response.error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginSuccess = await loginFunc()
|
||||||
|
if (!loginSuccess) {
|
||||||
|
loginError.value = 'Login after registration failed. Please try logging in manually.'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -146,8 +146,8 @@ function stopDrag() {
|
|||||||
|
|
||||||
function adjustPosition() {
|
function adjustPosition() {
|
||||||
if (isFullScreen.value) return
|
if (isFullScreen.value) return
|
||||||
x.value = Math.max(0, Math.min(x.value, window.innerWidth - width.value))
|
x.value = Math.min(x.value, window.innerWidth - width.value)
|
||||||
y.value = Math.max(0, Math.min(y.value, window.innerHeight - height.value))
|
y.value = Math.min(y.value, window.innerHeight - height.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
|
@ -1,51 +1,53 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal :isModalOpen="isPasswordResetOpen" @modal:close="() => isPasswordResetOpen = !isPasswordResetOpen" :modal-width="400" :modal-height="300" :is-resizable="false">
|
<Modal @modal:close="() => emit('close')" :modal-width="400" :modal-height="300" :is-resizable="false">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0 text-white">Reset Password</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">Reset Password</h3>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="h-[calc(100%_-_32px)] p-4">
|
<div class="h-[calc(100%_-_32px)] p-4">
|
||||||
<form class="h-full flex flex-col justify-between" @submit.prevent="resetPasswordFunc">
|
<form class="h-full flex flex-col justify-between" @submit.prevent="resetPasswordFunc">
|
||||||
<div class="flex flex-col relative">
|
<div class="flex flex-col relative">
|
||||||
<p>Fill in your email to receive a password reset request.</p>
|
<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" />
|
<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>
|
<span v-if="resetPasswordError" class="text-red-200 text-xs absolute top-full mt-1">{{ resetPasswordError }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-flow-col justify-stretch gap-4">
|
<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="isPasswordResetOpen = !isPasswordResetOpen">Cancel</button>
|
<button class="btn-empty py-1.5 px-4 min-w-24 inline-block" @click.stop="() => emit('close')">Cancel</button>
|
||||||
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" type="submit">Send mail</button>
|
<button class="btn-cyan py-1.5 px-4 min-w-24 inline-block" type="submit">Send mail</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { resetPassword } from '@/services/authentication'
|
import { resetPassword } from '@/services/authentication'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const emit = defineEmits(['close'])
|
||||||
const email = ref('')
|
|
||||||
const resetPasswordError = ref('')
|
const gameStore = useGameStore()
|
||||||
const isPasswordResetOpen = ref(false)
|
const email = ref('')
|
||||||
|
const resetPasswordError = ref('')
|
||||||
async function resetPasswordFunc() {
|
const isPasswordResetOpen = ref(false)
|
||||||
// check if email is valid
|
|
||||||
if (email.value === '') {
|
async function resetPasswordFunc() {
|
||||||
resetPasswordError.value = 'Please enter an email'
|
// check if email is valid
|
||||||
return
|
if (email.value === '') {
|
||||||
}
|
resetPasswordError.value = 'Please enter an email'
|
||||||
|
return
|
||||||
// send reset password event to server
|
}
|
||||||
const response = await resetPassword(email.value)
|
|
||||||
|
// send reset password event to server
|
||||||
if (response.success === undefined) {
|
const response = await resetPassword(email.value)
|
||||||
resetPasswordError.value = response.error
|
|
||||||
return
|
if (response.success === undefined) {
|
||||||
}
|
resetPasswordError.value = response.error
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
@ -1,144 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- @TODO this must be shown over the login screen -->
|
|
||||||
<div class="relative max-lg:h-dvh flex flex-row-reverse">
|
|
||||||
<ResetPassword :isModalOpen="isPasswordResetFormShown" />
|
|
||||||
<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">
|
|
||||||
<div class="h-dvh flex items-center lg:justify-center flex-col px-8 max-lg:pt-20">
|
|
||||||
<img src="/assets/login/sq-logo-v1.svg" class="mb-10" alt="Sylvan Quest logo" />
|
|
||||||
<div class="relative">
|
|
||||||
<img src="/assets/ui-elements/ui-box-outer.svg" class="absolute w-full h-full" alt="UI box outer" />
|
|
||||||
<img src="/assets/ui-elements/ui-box-inner.svg" class="absolute left-2 top-2 w-[calc(100%_-_16px)] h-[calc(100%_-_16px)] max-lg:hidden" alt="UI box inner" />
|
|
||||||
|
|
||||||
<!-- Login Form -->
|
|
||||||
<form v-show="switchForm === 'login'" @submit.prevent="loginFunc" class="relative px-6 py-11">
|
|
||||||
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
|
||||||
<div class="w-full grid gap-3 relative">
|
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="username-login" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
|
||||||
<div class="relative">
|
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="password-login" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
|
||||||
<button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
|
||||||
</div>
|
|
||||||
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
|
||||||
</div>
|
|
||||||
<button @click.stop="isPasswordResetFormShown = !isPasswordResetFormShown" 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 -->
|
|
||||||
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
|
||||||
<div class="w-0.5 h-full bg-gray-300"></div>
|
|
||||||
<div class="w-36 h-full bg-gray-300"></div>
|
|
||||||
<div class="w-0.5 h-full bg-gray-300"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pt-8">
|
|
||||||
<p class="m-0 text-center">Don't have an account? <button class="text-cyan-300 text-base p-0" @click.prevent="switchForm = 'register'">Sign up</button></p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Register Form -->
|
|
||||||
<form v-show="switchForm === 'register'" @submit.prevent="registerFunc" class="relative px-6 py-11">
|
|
||||||
<div class="flex flex-col gap-5 p-2 mb-8 relative">
|
|
||||||
<div class="w-full grid gap-3 relative">
|
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="username-register" v-model="username" type="text" name="username" placeholder="Username" required autofocus />
|
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="email-register" v-model="email" type="email" name="email" placeholder="Email" required />
|
|
||||||
<div class="relative">
|
|
||||||
<input class="input-field xs:min-w-[350px] min-w-64" id="password-register" v-model="password" :type="showPassword ? 'text' : 'password'" name="password" placeholder="Password" required />
|
|
||||||
<button type="button" @click.prevent="showPassword = !showPassword" :class="{ 'eye-open': showPassword }" class="bg-[url('/assets/icons/eye.svg')] p-0 absolute right-3 w-4 h-3 top-1/2 -translate-y-1/2 bg-no-repeat"></button>
|
|
||||||
</div>
|
|
||||||
<span v-if="loginError" class="text-red-200 text-xs absolute top-full mt-1">{{ loginError }}</span>
|
|
||||||
</div>
|
|
||||||
<button class="btn-cyan xs:w-full" type="submit">Register now</button>
|
|
||||||
|
|
||||||
<!-- Divider shape -->
|
|
||||||
<div class="absolute w-40 h-0.5 -bottom-8 left-1/2 -translate-x-1/2 flex justify-between">
|
|
||||||
<div class="w-0.5 h-full bg-gray-300"></div>
|
|
||||||
<div class="w-36 h-full bg-gray-300"></div>
|
|
||||||
<div class="w-0.5 h-full bg-gray-300"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pt-8">
|
|
||||||
<p class="m-0 text-center">Already have an account? <button class="text-cyan-300 text-base p-0" @click.prevent="switchForm = 'login'">Log in</button></p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
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/utilities/ResetPassword.vue'
|
|
||||||
import Loading from '@/screens/Loading.vue'
|
|
||||||
|
|
||||||
const props = defineProps(['isModalOpen'])
|
|
||||||
const isPasswordResetFormShown = ref(props.isModalOpen)
|
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
|
||||||
const username = ref('')
|
|
||||||
const password = ref('')
|
|
||||||
const email = ref('')
|
|
||||||
const switchForm = ref('login')
|
|
||||||
const loginError = ref('')
|
|
||||||
const showPassword = ref(false)
|
|
||||||
|
|
||||||
// automatic login because of development
|
|
||||||
onMounted(async () => {
|
|
||||||
const token = useCookies().get('token')
|
|
||||||
if (token) {
|
|
||||||
gameStore.setToken(token)
|
|
||||||
gameStore.initConnection()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function loginFunc() {
|
|
||||||
// check if username and password are valid
|
|
||||||
if (username.value === '' || password.value === '') {
|
|
||||||
loginError.value = 'Please enter a valid username and password'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// send login event to server
|
|
||||||
const response = await login(username.value, password.value)
|
|
||||||
|
|
||||||
if (response.success === undefined) {
|
|
||||||
loginError.value = response.error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gameStore.setToken(response.token)
|
|
||||||
gameStore.initConnection()
|
|
||||||
return true // Indicate success
|
|
||||||
}
|
|
||||||
|
|
||||||
async function registerFunc() {
|
|
||||||
// check if username and password are valid
|
|
||||||
if (username.value === '' || email.value === '' || password.value === '') {
|
|
||||||
loginError.value = 'Please enter a valid username, email, and password'
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (response.success === undefined) {
|
|
||||||
loginError.value = response.error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginSuccess = await loginFunc()
|
|
||||||
if (!loginSuccess) {
|
|
||||||
loginError.value = 'Login after registration failed. Please try logging in manually.'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
Loading…
x
Reference in New Issue
Block a user