feat: Added team page
This commit is contained in:
parent
0588dd1c45
commit
fbcd0d1bb9
@ -5,3 +5,9 @@
|
||||
<UNotifications />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.router-link-active {
|
||||
background: rgba(0, 0, 0, 10%)
|
||||
}
|
||||
</style>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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
|
||||
server met het ip <UBadge>play.polarcraft.xeovalyte.com</UBadge> gaan. Vervolgens krijg je een code
|
||||
te zien, vul deze code hieronder in.
|
||||
@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const user = useState('user')
|
||||
const user = useState<IUser>('user')
|
||||
|
||||
const code = ref('')
|
||||
const disableButtons = ref(false)
|
||||
|
@ -1,13 +1,13 @@
|
||||
<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">
|
||||
<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
|
||||
</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" />
|
||||
Dashboard
|
||||
</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" />
|
||||
Team
|
||||
</NuxtLink>
|
||||
|
197
webv2/components/team/Default.vue
Normal file
197
webv2/components/team/Default.vue
Normal 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>
|
118
webv2/components/team/None.vue
Normal file
118
webv2/components/team/None.vue
Normal 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>
|
@ -10,7 +10,7 @@
|
||||
Welkom, <b>{{ user.username }}</b>
|
||||
</span>
|
||||
<p class="text-gray-400">
|
||||
Happy to see you again
|
||||
Veel plezier met Polarcraft S5
|
||||
</p>
|
||||
</h2>
|
||||
</div>
|
||||
|
15
webv2/pages/team.vue
Normal file
15
webv2/pages/team.vue
Normal 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>
|
@ -12,16 +12,20 @@ export default defineEventHandler(async (event) => {
|
||||
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) {
|
||||
throw createError({ statusCode: 500, statusMessage: 'Team does not exist' })
|
||||
}
|
||||
|
||||
team.members.push(userId)
|
||||
user.team = team._id
|
||||
user.teamInvites = []
|
||||
|
||||
team.save()
|
||||
await user.save()
|
||||
|
||||
return team
|
||||
})
|
||||
|
9
webv2/server/api/users/[id]/teamInvites.get.ts
Normal file
9
webv2/server/api/users/[id]/teamInvites.get.ts
Normal 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
|
||||
})
|
@ -1,6 +1,8 @@
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { teamId } = await readBody(event)
|
||||
|
||||
console.log(teamId)
|
||||
|
||||
const userId: string = event.context.params ? event.context.params.id : '@me'
|
||||
|
||||
if (userId === '@me') {
|
||||
@ -9,11 +11,11 @@ export default defineEventHandler(async (event) => {
|
||||
|
||||
const user = await getUser(userId, event)
|
||||
|
||||
const { teamInvites } = await UserModel.findByIdAndUpdate(user._id, {
|
||||
await UserModel.findByIdAndUpdate(user._id, {
|
||||
$addToSet: {
|
||||
teamInvites: teamId
|
||||
}
|
||||
}) ?? { teamInvites: [] }
|
||||
})
|
||||
|
||||
return teamInvites
|
||||
return user
|
||||
})
|
||||
|
2
webv2/types/global.d.ts
vendored
2
webv2/types/global.d.ts
vendored
@ -36,7 +36,7 @@ declare global {
|
||||
_id: types.ObjectId,
|
||||
name: string,
|
||||
color: string,
|
||||
members: string[],
|
||||
members: IUser[],
|
||||
textChannelId: string,
|
||||
voiceChannelId: string
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user