feat: Added team page
Some checks failed
Build and Deploy / Deploy Web (push) Failing after 1s
Build and Deploy / Deploy Discord Bot (push) Failing after 1s

This commit is contained in:
Xeovalyte 2023-06-24 13:27:33 +02:00
parent 0588dd1c45
commit fbcd0d1bb9
11 changed files with 364 additions and 13 deletions

View File

@ -5,3 +5,9 @@
<UNotifications /> <UNotifications />
</div> </div>
</template> </template>
<style>
.router-link-active {
background: rgba(0, 0, 0, 10%)
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="mx-auto w-full max-w-xl flex-col items-center"> <div class="mx-auto w-full max-w-xl flex-col items-center">
<p class="mb-10 mt-52 text-sm sm:text-base"> <p class="mb-10 mt-64 text-sm sm:text-base">
Je bent momenteel niet verbonden met Minecraft. Om toegang te krijgen tot de Minecraft server moet je in Minecraft naar de Je bent momenteel niet verbonden met Minecraft. Om toegang te krijgen tot de Minecraft server moet je in Minecraft naar de
server met het ip <UBadge>play.polarcraft.xeovalyte.com</UBadge> gaan. Vervolgens krijg je een code server met het ip <UBadge>play.polarcraft.xeovalyte.com</UBadge> gaan. Vervolgens krijg je een code
te zien, vul deze code hieronder in. te zien, vul deze code hieronder in.
@ -13,7 +13,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
const user = useState('user') const user = useState<IUser>('user')
const code = ref('') const code = ref('')
const disableButtons = ref(false) const disableButtons = ref(false)

View File

@ -1,13 +1,13 @@
<template> <template>
<div class="fixed left-0 top-0 z-50 flex h-screen w-56 flex-col gap-y-1 border-r border-gray-700 bg-gray-800 px-2"> <div class="fixed left-0 top-0 z-50 flex h-screen w-56 flex-col gap-y-1 border-r border-gray-700 bg-gray-800 px-2">
<h1 class="text-primary-500 my-5 text-center text-2xl font-bold" to="/"> <h1 class="text-primary-500 my-5 text-center text-2xl font-bold">
Polarcraft S5 Polarcraft S5
</h1> </h1>
<NuxtLink class="flex w-full items-center rounded bg-black/0 p-2 text-lg hover:cursor-pointer hover:bg-black/20"> <NuxtLink to="/" class="flex w-full items-center rounded bg-black/0 p-2 text-lg hover:cursor-pointer hover:bg-black/20">
<Icon size="1.5em" name="heroicons:chart-pie" class="mr-2" /> <Icon size="1.5em" name="heroicons:chart-pie" class="mr-2" />
Dashboard Dashboard
</NuxtLink> </NuxtLink>
<NuxtLink class="flex w-full items-center rounded bg-black/0 p-2 text-lg hover:cursor-pointer hover:bg-black/20"> <NuxtLink to="/team" class="flex w-full items-center rounded bg-black/0 p-2 text-lg hover:cursor-pointer hover:bg-black/20">
<Icon size="1.5em" name="heroicons:users" class="mr-2" /> <Icon size="1.5em" name="heroicons:users" class="mr-2" />
Team Team
</NuxtLink> </NuxtLink>

View File

@ -0,0 +1,197 @@
<template>
<div v-if="user.team" class="grid w-full grid-cols-12 gap-6">
<UModal v-model="isInviteModalOpen">
<UCard>
<template #header>
<h1 class="text-lg font-bold">Invite User</h1>
</template>
<div class="flex flex-col items-center divide-y divide-gray-700">
<div v-for="newUser in users" :key="newUser._id" class="flex w-full max-w-sm py-2">
<span class="mr-auto">
{{ newUser.username }}
</span>
<span v-if="newUser.team" class="text-gray-500">Already in Team</span>
<span v-if="!newUser.team && newUser.teamInvites.includes(team._id)" class="text-gray-500">Already invited</span>
<UButton v-if="!newUser.team && !newUser.teamInvites.includes(team._id)" :disabled="disableButtons" @click="inviteUser(newUser)">Invite</UButton>
</div>
</div>
<template #footer>
<UButton :disabled="disableButtons" @click="isInviteModalOpen = false">OK</UButton>
</template>
</UCard>
</UModal>
<UModal v-model="editModal.open">
<UCard>
<template #header>
<h1 class="text-lg font-bold">Edit Team</h1>
</template>
<form class="flex flex-col gap-y-5">
<UFormGroup name="name" label="Name" :error="teamNameError">
<UInput v-model="editModal.name" placeholder=" " />
</UFormGroup>
<UFormGroup name="color" label="Color" :error="teamColorError">
<div class="flex">
<input v-model="editModal.color" type="color" class="mr-2">
<UInput v-model="editModal.color" placeholder=" " class="w-full" />
</div>
</UFormGroup>
</form>
<template #footer>
<UButton :disabled="disableButtons" @click="submitEditTeam">Edit Team</UButton>
</template>
</UCard>
</UModal>
<div class="col-span-6 h-min rounded-lg border border-gray-700 bg-gray-800 p-5">
<h3 class="mb-2 text-xl font-bold">
Team Information
</h3>
<ul>
<li>Name: {{ user.team.name }}</li>
<li class="relative">
Color: <span :style="{ 'text-decoration-color': user.team.color }" class="underline underline-offset-4">{{ user.team.color }}</span>
</li>
</ul>
<div class="mt-5 space-x-3">
<UButton @click="openModal">Edit Team</UButton>
<UButton color="red" variant="outline" @click="leaveTeam">Leave Team</UButton>
</div>
</div>
<div v-if="team" class="col-span-6 rounded-lg border border-gray-700 bg-gray-800 p-5">
<h3 class="mb-2 text-xl font-bold">
Team Members
</h3>
<ul class="divide-y divide-gray-700">
<li v-for="member in team.members" :key="member._id" class="flex items-center py-3 text-lg">
<UAvatarGroup size="sm" class="mr-3">
<UAvatar :src="discordAvatarUrl(member)" alt="Discord Avatar" placeholder="DC" />
<UAvatar v-if="minecraftAvatarUrl(member)" :src="minecraftAvatarUrl(member)" alt="Minecraft Avatar" placeholder="MC" />
</UAvatarGroup>
{{ member.username }}
</li>
</ul>
<div class="my-2 rounded border-2 border-dashed border-gray-500 px-5 py-2 text-center font-bold hover:cursor-pointer hover:bg-gray-700" @click="isInviteModalOpen = true">Invite User</div>
</div>
</div>
</template>
<script lang="ts" setup>
const disableButtons = ref(false)
const user = useState<IUser>('user')
const { data: team }: { data: any } = useFetch('/api/teams/@current')
const { data: users }: { data: any } = useFetch('/api/users')
const isInviteModalOpen = ref(false)
const editModal = ref({
open: false,
name: '',
color: ''
})
const teamNameError = computed(() => {
if (!editModal.value.name) {
return ''
}
if (!/^[a-zA-Z0-9]+$/.test(editModal.value.name)) {
return 'Team name must only include alfanumeric characters'
}
if (editModal.value.name.length < 3 || editModal.value.name.length > 16) {
return 'Team name must be between 3 and 16 characters'
}
})
const teamColorError = computed(() => {
if (!editModal.value.color) {
return ''
}
if (!/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(editModal.value.color)) {
return 'Team color is not a valid hex code'
}
})
const discordAvatarUrl = (member: IUser) => {
return 'https://cdn.discordapp.com/avatars/' + member.discord.id + '/' + member.discord.avatarHash + '.png'
}
const minecraftAvatarUrl = (member: IUser) => {
if (!member.minecraft) {
return ''
}
return 'https://api.mineatar.io/face/' + member.minecraft.uuid + '?scale=16'
}
const leaveTeam = async () => {
disableButtons.value = true
try {
await $fetch('/api/teams/@current/members/@me', {
method: 'DELETE'
})
user.value.team = undefined
useToast().add({ title: 'Successfully left team', color: 'green' })
} catch (e: any) {
console.error(e)
useToast().add({ title: e.statusMessage, color: 'red' })
}
disableButtons.value = false
}
const inviteUser = async (newUser: IUser) => {
disableButtons.value = true
try {
await $fetch(`/api/users/${newUser._id}/teamInvites`, {
method: 'POST',
body: {
teamId: team.value._id
}
})
newUser.teamInvites.push(team.value._id)
useToast().add({ title: 'Successfully invited user to team', color: 'green' })
} catch (e: any) {
console.error(e)
useToast().add({ title: e.statusMessage, color: 'red' })
}
disableButtons.value = false
}
const openModal = () => {
editModal.value.name = team.value.name
editModal.value.color = team.value.color
editModal.value.open = true
}
const submitEditTeam = async () => {
disableButtons.value = true
try {
const response: any = await $fetch('/api/teams/@current', {
method: 'PUT',
body: {
teamName: editModal.value.name,
teamColor: editModal.value.color
}
})
user.value.team = response
useToast().add({ title: 'Successfully edited team', color: 'green' })
} catch (e: any) {
console.error(e)
useToast().add({ title: e.statusMessage, color: 'red' })
}
editModal.value = { open: false, name: '', color: '' }
disableButtons.value = false
}
</script>

View File

@ -0,0 +1,118 @@
<template>
<p class="mx-auto mb-10 mt-20 max-w-sm text-center">
Je zit momenteel niet in een team. Maak een team aan of wacht tot dat je geinvite wordt
</p>
<div class="mb-8 flex justify-center gap-x-10">
<div class="text-primary-400 select-none font-bold hover:cursor-pointer " :class="{ 'underline underline-offset-4': !createTeam.show }" @click="createTeam.show = false">
Team Invites
</div>
<div class="text-primary-400 select-none font-bold hover:cursor-pointer" :class="{ 'underline underline-offset-4': createTeam.show }" @click="createTeam.show = true">
Create Team
</div>
</div>
<div v-if="!createTeam.show" class="mx-auto flex max-w-lg flex-col divide-y divide-gray-500 rounded-lg bg-gray-800 px-5 py-3">
<div v-for="team in teamInvites" :key="team._id" class="flex w-full items-center py-3">
{{ team.name }}
<UButton :disabled="disableButtons" color="red" variant="ghost" class="ml-auto mr-5">Reject</UButton>
<UButton color="green" @click="acceptInvite(team._id)">Accept</UButton>
</div>
<div v-if="!teamInvites[0]" class="p-5">
There are no team invites
</div>
</div>
<div v-else class="mx-auto flex max-w-lg flex-col gap-y-5 rounded-lg bg-gray-800 p-5">
<form class="flex flex-col gap-y-5">
<UFormGroup name="name" label="Name" :error="teamNameError">
<UInput v-model="createTeam.name" placeholder=" " />
</UFormGroup>
<UFormGroup name="color" label="Color" :error="teamColorError">
<div class="flex">
<input v-model="createTeam.color" type="color" class="mr-2">
<UInput v-model="createTeam.color" placeholder=" " class="w-full" />
</div>
</UFormGroup>
</form>
<UButton class="mx-auto" @click="submitCreateTeam">Create Team</UButton>
</div>
</template>
<script lang="ts" setup>
const createTeam = ref({
show: false,
name: '',
color: ''
})
const disableButtons = ref(false)
const user = useState<IUser>('user')
const { data: teamInvites } = useFetch('/api/users/@me/teamInvites')
const teamNameError = computed(() => {
if (!createTeam.value.name) {
return ''
}
if (!/^[a-zA-Z0-9]+$/.test(createTeam.value.name)) {
return 'Team name must only include alfanumeric characters'
}
if (createTeam.value.name.length < 3 || createTeam.value.name.length > 16) {
return 'Team name must be between 3 and 16 characters'
}
})
const teamColorError = computed(() => {
if (!createTeam.value.color) {
return ''
}
if (!/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(createTeam.value.color)) {
return 'Team color is not a valid hex code'
}
})
const submitCreateTeam = async () => {
disableButtons.value = true
try {
const response: any = await $fetch('/api/teams', {
method: 'POST',
body: {
name: createTeam.value.name,
color: createTeam.value.color
}
})
user.value.team = response
useToast().add({ title: 'Successfully created team', color: 'green' })
} catch (e: any) {
console.error(e)
useToast().add({ title: e.statusMessage, color: 'red' })
}
disableButtons.value = false
}
const acceptInvite = async (teamId: string) => {
disableButtons.value = true
try {
const response: any = await $fetch(`/api/teams/${teamId}/members`, {
method: 'POST',
body: {
userId: user.value._id
}
})
user.value.team = response
useToast().add({ title: 'Successfully joined team', color: 'green' })
} catch (e: any) {
console.error(e)
useToast().add({ title: e.statusMessage, color: 'red' })
}
disableButtons.value = false
}
</script>

View File

@ -10,7 +10,7 @@
Welkom, <b>{{ user.username }}</b> Welkom, <b>{{ user.username }}</b>
</span> </span>
<p class="text-gray-400"> <p class="text-gray-400">
Happy to see you again Veel plezier met Polarcraft S5
</p> </p>
</h2> </h2>
</div> </div>

15
webv2/pages/team.vue Normal file
View File

@ -0,0 +1,15 @@
<template>
<div>
<TeamDefault v-if="user.team" />
<TeamNone v-else />
</div>
</template>
<script lang="ts" setup>
definePageMeta({
middleware: ['auth'],
title: 'Team | Polarcraft'
})
const user = useState<IUser>('user')
</script>

View File

@ -12,16 +12,20 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 400, statusMessage: 'User already is in a team' }) throw createError({ statusCode: 400, statusMessage: 'User already is in a team' })
} }
const team = await TeamModel.findById(teamId).populate('members') const team = await TeamModel.findByIdAndUpdate(teamId, {
$addToSet: {
members: user._id
}
}).populate('members')
if (!team) { if (!team) {
throw createError({ statusCode: 500, statusMessage: 'Team does not exist' }) throw createError({ statusCode: 500, statusMessage: 'Team does not exist' })
} }
team.members.push(userId)
user.team = team._id user.team = team._id
user.teamInvites = []
team.save() await user.save()
return team return team
}) })

View File

@ -0,0 +1,9 @@
export default defineEventHandler(async (event) => {
const userId: string = event.context.params ? event.context.params.id : '@me'
const user = await getUser(userId, event)
const { teamInvites } = await UserModel.findById(user._id).populate('teamInvites')
return teamInvites
})

View File

@ -1,6 +1,8 @@
export default defineEventHandler(async (event) => { export default defineEventHandler(async (event) => {
const { teamId } = await readBody(event) const { teamId } = await readBody(event)
console.log(teamId)
const userId: string = event.context.params ? event.context.params.id : '@me' const userId: string = event.context.params ? event.context.params.id : '@me'
if (userId === '@me') { if (userId === '@me') {
@ -9,11 +11,11 @@ export default defineEventHandler(async (event) => {
const user = await getUser(userId, event) const user = await getUser(userId, event)
const { teamInvites } = await UserModel.findByIdAndUpdate(user._id, { await UserModel.findByIdAndUpdate(user._id, {
$addToSet: { $addToSet: {
teamInvites: teamId teamInvites: teamId
} }
}) ?? { teamInvites: [] } })
return teamInvites return user
}) })

View File

@ -36,7 +36,7 @@ declare global {
_id: types.ObjectId, _id: types.ObjectId,
name: string, name: string,
color: string, color: string,
members: string[], members: IUser[],
textChannelId: string, textChannelId: string,
voiceChannelId: string voiceChannelId: string
} }