fixed a bunch of bugs, yay!!!

This commit is contained in:
Zoe
2023-01-13 03:34:54 -06:00
parent 3bad12c646
commit c39da0678d
24 changed files with 526 additions and 267 deletions

View File

@@ -0,0 +1,8 @@
<template>
<li>
<button
class="w-full cursor-pointer bg-[hsl(225,7.7%,10.2%)] hover:bg-[hsl(225,7.7%,17.4%)] text-left px-3 py-1.5 rounded-md flex items-center">
<slot />
</button>
</li>
</template>

View File

@@ -0,0 +1,48 @@
<template>
<Transition name="pop-in">
<div ref="dropdown" class="z-[2] absolute m-2 bg-[hsl(225,7.7%,10.2%)] w-[calc(100%-1rem)] p-3 rounded text-left"
:class="(inverted) ? 'dropdown-inverse' : 'dropdown'"
v-if="opened">
<slot />
</div>
</Transition>
</template>
<script lang="ts">
export default {
props: ['opened', 'inverted'],
}
</script>
<style>
.dropdown {
transform-origin: top center;
}
.dropdown-inverse {
transform-origin: bottom center;
}
.dropdown-inverse > ul {
display: flex;
flex-direction: column-reverse;
}
.pop-in-enter-active {
animation: pop-in 150ms cubic-bezier(.81, .5, .44, .83);
}
.pop-in-leave-active {
animation: pop-in 150ms reverse cubic-bezier(.81, .5, .44, .83);
}
@keyframes pop-in {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
</style>

53
components/InviteCard.vue Normal file
View File

@@ -0,0 +1,53 @@
<template>
<div class="w-6/12 bg-[hsl(223,6.9%,19.8%)] p-4 rounded-md shadow-md mr-2">
<p class="text-sm font-semibold text-zinc-100">You've been invited to join a
server</p>
<span class="text-xl font-bold capitalize leading-loose">{{ invite.server.name }}</span>
<div class="flex items-center">
<span
class="before:bg-[hsl(214,9.9%,50.4%)] before:h-2 before:w-2 before:inline-block before:my-auto before:rounded-full before:mr-1"></span>
<span>{{ invite.server.participants.length }} Members</span>
</div>
<div class="flex w-full justify-end">
<button @click="joinServer(invite)"
class="font-semibold rounded px-4 py-2 transition-colors"
:class="(userInServer) ? 'bg-green-800 cursor-not-allowed' : 'bg-green-700 hover:bg-green-600'">
<span v-if="userInServer">
Joined
</span>
<span v-else>
Join
</span>
</button>
</div>
</div>
</template>
<script lang="ts">
import { useGlobalStore } from '~/stores/store'
import { IInviteCode, IUser } from '~/types'
export default {
props: ['invite'],
data() {
return {
user: storeToRefs(useGlobalStore()).user,
servers: storeToRefs(useGlobalStore()).servers,
}
},
computed: {
userInServer(): boolean {
return !!this.invite.server.participants.find((e: IUser) => e.id === this.user.id)
}
},
methods: {
async joinServer(invite: IInviteCode) {
if (this.userInServer) return;
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
const { server } = await $fetch('/api/guilds/joinGuild', { method: 'POST', body: { inviteId: invite.id }, headers })
if (!server) return;
this.servers?.push(server)
},
}
}
</script>

View File

@@ -1,5 +1,17 @@
<template>
<div class="h-full bg-[hsl(220,calc(1*7.7%),22.9%)] relative text-white">
<div class="bg-[hsl(220,calc(1*7.7%),22.9%)] absolute w-full shadow px-4 py-3 flex items-center z-[1] shadow-zinc-900/50">
<span>
<svg class="text-zinc-300/80 my-auto" xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24">
<path fill="currentColor"
d="m5.41 21l.71-4h-4l.35-2h4l1.06-6h-4l.35-2h4l.71-4h2l-.71 4h6l.71-4h2l-.71 4h4l-.35 2h-4l-1.06 6h4l-.35 2h-4l-.71 4h-2l.71-4h-6l-.71 4h-2M9.53 9l-1.06 6h6l1.06-6h-6Z" />
</svg>
</span>
<span class="text-zinc-100 font-semibold">{{ server.name }}</span>
</div>
<div class="w-full h-[calc(100%-60px)] overflow-y-scroll pb-1"
id="conversation-pane">
<div>
@@ -16,41 +28,18 @@
</p>
<p class="break-words max-w-full">{{ message.body }}</p>
</div>
<div>
<div v-for="invite in message.invites">
<div class="w-6/12 bg-[hsl(223,6.9%,19.8%)] p-4 rounded-md shadow-md mr-2">
<p class="text-sm font-semibold text-zinc-100">You've been invited to join a
server</p>
<span class="text-xl font-bold capitalize">{{ invite.server.name }}</span>
<div class="flex items-center">
<span
class="before:bg-[hsl(214,9.9%,50.4%)] before:h-2 before:w-2 before:inline-block before:my-auto before:rounded-full before:mr-1"></span>
<span>{{ invite.server.participants.length }} Members</span>
</div>
<div class="flex w-full justify-end">
<button @click="joinServer(invite)"
class="font-semibold rounded px-4 py-2 transition-colors"
:class="(invite.server.participants.find((e) => e.id === user.id)) ? 'bg-green-800 cursor-not-allowed' : 'bg-green-700 hover:bg-green-600'">
<span v-if="invite.server.participants.find((e) => e.id === user.id)">
Joined
</span>
<span v-else>
Join
</span>
</button>
</div>
</div>
</div>
<div v-for="invite in message.invites">
<InviteCard :invite="invite" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="conversation-input w-[calc(100vw-88px-240px)]">
<div class="conversation-input w-[calc(100vw-88px-240px)] h-[61.1px]">
<form @submit.prevent="sendMessage"
@keydown.enter.exact.prevent="sendMessage"
class="relative px-4 w-full">
class="relative px-4 w-full pt-1.5">
<div id="textbox"
class="px-4 rounded-md w-full h-[44px] bg-[hsl(218,calc(1*7.9%),27.3%)] placeholder:text-[hsl(218,calc(1*4.6%),46.9%)] flex flex-row">
<textarea type="text"
@@ -121,47 +110,22 @@ export default {
if (!lastElementChild) return;
setTimeout(() => {
if (conversationDiv.scrollTop + 11.2 < (conversationDiv.scrollHeight - conversationDiv.clientHeight) - lastElementChild.clientHeight) return;
if (conversationDiv.scrollTop + 20 < (conversationDiv.scrollHeight - conversationDiv.clientHeight) - lastElementChild.clientHeight) return;
conversationDiv.scrollTop = conversationDiv.scrollHeight;
})
});
},
// updated() {
// const route = useRoute()
// const socket = io();
// const conversationDiv = document.getElementById('conversation-pane');
// if (!conversationDiv) throw new Error('conversation div not found')
// this.scrollToBottom()
// socket.removeAllListeners('connect')
// socket.on('connect', () => {
// // listen for messages from the server
// socket.on(`message-${route.params.id}`, (ev) => {
// const { message } = ev
// console.log(message.userId, this.user.id, message, this.conversation)
// if (message.userId == this.user.id) return;
// this.conversation.push(message)
// const lastElementChild = conversationDiv.children[0]?.lastElementChild
// if (!lastElementChild) return;
// setTimeout(() => {
// console.log(conversationDiv.scrollTop, conversationDiv.scrollHeight, conversationDiv.clientHeight, lastElementChild.clientHeight, (conversationDiv.scrollHeight - conversationDiv.clientHeight) - lastElementChild.clientHeight)
// if (conversationDiv.scrollTop + 11.2 < (conversationDiv.scrollHeight - conversationDiv.clientHeight) - lastElementChild.clientHeight) return;
// conversationDiv.scrollTop = conversationDiv.scrollHeight;
// })
// })
// });
// },
unmounted() {
const socket = io();
socket.removeAllListeners();
},
methods: {
async sendMessage() {
const route = useRoute()
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
if (!this.messageContent) return;
const message: IMessage = await $fetch(`/api/channels/sendMessage`, { method: 'post', body: { body: this.messageContent, channelId: route.params.id } })
const message: IMessage = await $fetch(`/api/channels/sendMessage`, { method: 'post', body: { body: this.messageContent, channelId: route.params.id }, headers })
if (!message) return;
if (this.conversation.includes(message)) return;
@@ -174,17 +138,10 @@ export default {
conversationDiv.scrollTop = conversationDiv.scrollHeight;
})
},
async joinServer(invite: IInviteCode) {
const { server } = await $fetch('/api/guilds/joinGuild', { method: 'POST', body: { inviteId: invite.id } })
if (!server) return;
this.servers?.push(server)
},
scrollToBottom() {
const conversationDiv = document.getElementById('conversation-pane');
if (!conversationDiv) throw new Error('wtf');
setTimeout(() => {
conversationDiv.scrollTop = conversationDiv.scrollHeight;
})
conversationDiv.scrollTo(0, conversationDiv.scrollHeight);
}
// resizeTextarea() {
// const textArea = document.getElementById('messageBox')
@@ -208,7 +165,6 @@ export default {
flex-direction: row;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
height: 60px;
background-color: hsl(220, calc(1 * 7.7%), 22.9%);
bottom: calc(0px - 0.5rem);
}

View File

@@ -5,21 +5,36 @@
<nuxt-link to="/channel/@me">
<div @click="openServer('@me', 'dms')"
class="bg-zinc-600/80 p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-zinc-500/60 duration-300">
<svg width="32"
height="32"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 12c2-2.96 0-7-1-8c0 3.038-1.773 4.741-3 6c-1.226 1.26-2 3.24-2 5a6 6 0 1 0 12 0c0-1.532-1.056-3.94-2-5c-1.786 3-2.791 3-4 2z" />
</svg>
<span>
<svg width="32"
height="32"
viewBox="0 0 24 24">
<defs>
<linearGradient id="fire"
x1="-2.778%"
x2="100%"
y1="24%"
y2="48%">
<stop offset="0%"
stop-color="#ff0c41" />
<stop offset="100%"
stop-color="#ff6b0c" />
</linearGradient>
</defs>
<path fill="none"
stroke="url(#fire)"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 12c2-2.96 0-7-1-8c0 3.038-1.773 4.741-3 6c-1.226 1.26-2 3.24-2 5a6 6 0 1 0 12 0c0-1.532-1.056-3.94-2-5c-1.786 3-2.791 3-4 2z" />
</svg>
</span>
</div>
</nuxt-link>
</div>
<div class="overflow-y-scroll my-2 flex gap-y-2 flex-col">
<nuxt-link v-for="server in servers" :to="'/channel/' + server.channels[0].id">
<nuxt-link v-for="server in servers"
:to="'/channel/' + server.channels[0].id">
<div :key="server.id"
@click="openServer(server.id, 'servers')"
class="bg-zinc-600/80 p-3 rounded-full transition-all hover:rounded-2xl ease-in-out hover:bg-zinc-500/60 duration-300 h-[56px] w-[56px]">
@@ -63,9 +78,6 @@
<div v-if="createServerModelOpen"
class="absolute z-10 top-0 bottom-0 left-0 right-0">
<div class="bg-zinc-900/80 w-screen h-screen"
@click="createServerModelOpen = false">
</div>
<div
class="p-4 z-20 absolute bg-zinc-800 shadow-md rounded-md -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2 text-white">
<h2 class="font-semibold text-xl">
@@ -83,7 +95,11 @@
</form>
</div>
</div>
<div class="bg-zinc-900/80 w-screen h-screen"
@click="createServerModelOpen = false">
</div>
</div>
</template>
<script lang="ts">
@@ -101,12 +117,13 @@ export default {
methods: {
async createServer() {
const globalStore = useGlobalStore();
const server: IServer = await $fetch('/api/channels/create', { method: 'post', body: { serverName: this.serverName } })
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
const server: IServer = await $fetch('/api/channels/create', { method: 'post', body: { serverName: this.serverName }, headers })
this.createServerModelOpen = false;
this.serverName = '';
globalStore.addServer(server)
},
openServer(id: string, type: string): void {
openServer(id: string, type: "servers" | "dms"): void {
useGlobalStore().setActive(type, id)
}
},

View File

@@ -1,7 +1,7 @@
<template>
<div
class="bg-[hsl(223,calc(1*6.9%),19.8%)] min-w-60 w-60 h-screen shadow-sm text-white select-none grid grid-rows-[93.5%_1fr]">
<div v-if="!server.id || server.DM == true">
<aside
class="bg-[hsl(223,calc(1*6.9%),19.8%)] min-w-60 w-60 h-screen shadow-sm text-white select-none grid grid-rows-[93.5%_1fr] relative z-[2]">
<div v-if="serverType === 'dms' || !server.id">
<div>
<nuxt-link v-for="dm in dms"
:to="'/channel/@me/' + dm.id">
@@ -14,36 +14,14 @@
</div>
<div class="w-full"
v-else>
<div class="flex p-4 border-b border-zinc-600/80">
<h4 class="text-lg font-semibold grid gap-1 grid-cols-[1fr_28px] w-full">
<span>{{ server.name }}</span>
<button class="cursor-pointer p-1 bg-[hsl(223,calc(1*6.9%),19.8%)] hover:bg-[hsl(223,calc(1*6.9%),26.4%)] transition-all">
<span class="h-fit w-[20px]">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m6 9l6 6l6-6" />
</svg>
</span>
</button>
</h4>
</div>
<div class="flex gap-y-1.5 px-1.5 mt-2 flex-col">
<button @click="createInvite"
v-if="userIsOwner || userIsAdmin">make invite</button>
<button
class="flex text-center hover:bg-[hsl(223,calc(1*6.9%),26.4%)] px-2 py-1.5 w-full transition-colors rounded drop-shadow-sm gap-1/5 cursor-pointer"
v-for="channel in server.channels"
@click="openChannel(channel.id)"
:key="channel.id">
<span>
<svg class="text-zinc-300 my-auto"
<h4 @click="serverDropdownOpen = !serverDropdownOpen"
class="py-3 px-4 font-semibold grid gap-1 grid-cols-[1fr_28px] w-full items-center cursor-pointer p-1 bg-[hsl(223,calc(1*6.9%),19.8%)] transition-all"
:class="(!serverDropdownOpen) ? 'hover:bg-[hsl(223,calc(1*6.9%),26.4%)]' : 'bg-[hsl(223,calc(1*6.9%),26.4%)]'">
<span>{{ server.name }}</span>
<button>
<span v-if="!serverDropdownOpen"
class="h-fit w-[20px]">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
@@ -52,16 +30,79 @@
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 9h14M5 15h14M11 4L7 20M17 4l-4 16" />
d="m6 9l6 6l6-6" />
</svg>
</span>
<span class="h-fit w-[20px]"
v-else>
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M18 6L6 18M6 6l12 12" />
</svg>
</span>
</button>
</h4>
<div>
<DropdownMenu :opened="serverDropdownOpen">
<div>
<ul class="flex flex-col gap-y-1">
<DropdownItem v-if="userIsOwner || userIsAdmin"
@click="createInvite">
<span class="mr-1.5 h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<circle cx="9"
cy="7"
r="4" />
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2m1-10h6m-3-3v6" />
</g>
</svg>
</span>
<span>
Invite a friend
</span>
</DropdownItem>
</ul>
</div>
</DropdownMenu>
</div>
<div class="flex gap-y-1.5 px-1.5 mt-2 flex-col">
<button
class="flex text-center hover:bg-[hsl(223,calc(1*6.9%),26.4%)] px-2 py-1.5 w-full transition-colors rounded drop-shadow-sm gap-1/5 cursor-pointer items-center"
v-for="channel in server.channels"
@click="openChannel(channel.id)"
:key="channel.id">
<span class="h-fit">
<svg class="text-zinc-300/80 my-auto"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<path fill="currentColor"
d="m5.41 21l.71-4h-4l.35-2h4l1.06-6h-4l.35-2h4l.71-4h2l-.71 4h6l.71-4h2l-.71 4h4l-.35 2h-4l-1.06 6h4l-.35 2h-4l-.71 4h-2l.71-4h-6l-.71 4h-2M9.53 9l-1.06 6h6l1.06-6h-6Z" />
</svg>
</span>
<span>{{ channel.name }}</span>
</button>
<button v-if="userIsOwner || userIsAdmin"
@click="openCreateChannelModel"
class="flex text-center hover:bg-[hsl(223,calc(1*6.9%),26.4%)] px-2 py-1.5 w-full transition-colors rounded drop-shadow-sm cursor-pointer">
class="flex text-center hover:bg-[hsl(223,calc(1*6.9%),26.4%)] px-2 py-1.5 w-full transition-colors rounded drop-shadow-sm cursor-pointer items-center">
<span>
<svg xmlns="http://www.w3.org/2000/svg"
<svg class="text-zinc-300/80 my-auto" xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
@@ -78,12 +119,44 @@
</div>
</div>
<div>
<div class="relative">
<DropdownMenu class="bottom-full"
:inverted="true"
:opened="userDropdownOpen">
<div>
<ul class="flex flex-col gap-y-1">
<DropdownItem v-if="userIsOwner || userIsAdmin"
@click="createInvite">
<span class="mr-1.5 h-fit">
<svg xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24">
<g fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2">
<circle cx="9"
cy="7"
r="4" />
<path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2m1-10h6m-3-3v6" />
</g>
</svg>
</span>
<span>
Invite a friend
</span>
</DropdownItem>
</ul>
</div>
</DropdownMenu>
<div class="bg-[hsl(220,calc(1*6.8%),17.3%)] h-full p-3">
<div class="grid grid-cols-[32px_1fr_32px] gap-x-2 items-center">
<span class="bg-[hsl(220,calc(1*6.8%),22.6%)] w-[32px] h-[32px] rounded-full"></span>
<span class="h-fit w-fit overflow-ellipsis">{{ user.username }}</span>
<span class="text-zinc-300 hover:bg-[hsl(220,calc(1*6.8%),14.3%)] p-1 transition-colors">
<button @click="userDropdownOpen = !userDropdownOpen"
class="text-zinc-300 hover:bg-[hsl(220,calc(1*6.8%),14.3%)] p-1 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
@@ -100,11 +173,11 @@
r="3" />
</g>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
</aside>
<div v-if="createChannelModelOpen"
class="absolute z-10 top-0 bottom-0 left-0 right-0">
@@ -139,31 +212,30 @@ export default {
data() {
return {
server: storeToRefs(useGlobalStore()).activeServer,
serverType: storeToRefs(useGlobalStore()).activeServerType,
user: storeToRefs(useGlobalStore()).user,
dms: storeToRefs(useGlobalStore()).dms,
createChannelModelOpen: false,
serverDropdownOpen: false,
userDropdownOpen: false,
channelName: '',
userIsOwner: false,
userIsAdmin: false,
}
},
async mounted() {
const that = this;
var interval = setInterval(function () {
// get elem
if (typeof that.server.roles == 'undefined') return;
clearInterval(interval);
that.userIsOwner = that.server.roles?.find((e: IRole) => e.users.some((el) => el.id === that.user.id))?.owner || false
that.userIsAdmin = that.server.roles?.find((e: IRole) => e.users.some((el) => el.id === that.user.id))?.administer || false
}, 10);
computed: {
userIsOwner() {
return this.server && this.serverType === "servers" && this.server.roles?.find((e: IRole) => e.users.some((el) => el.id === this.user.id))?.owner
},
userIsAdmin() {
return this.server && this.serverType === "servers" && this.server.roles?.find((e: IRole) => e.users.some((el) => el.id === this.user.id))?.administer
}
},
methods: {
openCreateChannelModel() {
this.createChannelModelOpen = true;
},
async createChannel() {
const channel = await $fetch(`/api/guilds/${this.server.id}/addChannel`, { method: 'POST', body: { channelName: this.channelName } }) as IChannel
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
const channel = await $fetch(`/api/guilds/${this.server.id}/addChannel`, { method: 'POST', body: { channelName: this.channelName }, headers }) as IChannel
if (!channel) return;
@@ -176,7 +248,8 @@ export default {
router.push({ params: { id } })
},
async createInvite() {
const inviteCode = await $fetch(`/api/guilds/${this.server.id}/createInvite`, { method: 'POST' })
const headers = useRequestHeaders(['cookie']) as Record<string, string>
const inviteCode = await $fetch(`/api/guilds/${this.server.id}/createInvite`, { method: 'POST', headers })
},
},
}

View File

@@ -1,7 +1,6 @@
<template>
<Suspense>
<div v-if="user.id"
class="flex h-screen max-h-screen text-white">
<div class="flex h-screen max-h-screen text-white">
<Nav />
<Sidebar />
<div class="w-[calc(100vw-88px-240px)] h-full">
@@ -23,24 +22,36 @@ import { SafeUser } from '~/types'
export default {
data() {
return {
activeServer: storeToRefs(useGlobalStore()).activeServer,
user: storeToRefs(useGlobalStore()).user
user: storeToRefs(useGlobalStore()).user,
}
},
async setup() {
const userStore = useGlobalStore()
const globalStore = useGlobalStore()
const sessionToken = useCookie('sessionToken')
if (userStore.user.id === undefined && sessionToken.value) {
const user: SafeUser = await $fetch('/api/getCurrentUser')
if (globalStore.user.id === undefined && sessionToken.value) {
const route = useRoute()
const headers = useRequestHeaders(['cookie']) as Record<string, string>
const [user, { dms, servers }] = await Promise.all([
$fetch('/api/getCurrentUser', { headers }) as unknown as SafeUser,
$fetch('/api/user/getServers', { headers })
])
if (!user) return;
if (!user || !servers || !dms) return;
userStore.setUser(user)
const { channels: dms, servers } = await $fetch('/api/user/getServers')
globalStore.setUser(user)
useGlobalStore().servers = servers
useGlobalStore().dms = dms
globalStore.setServers(servers)
globalStore.setDms(dms)
console.log('params', route.params.id)
if (route.params.id && typeof route.params.id === 'string') {
globalStore.setActive(route.path.includes('@me') ? 'dms' : 'servers', route.params.id)
}
const server = globalStore.activeServer
return {
server
}
}
}
}

View File

@@ -36,4 +36,8 @@ export default {
},
],
],
typescript: {
strict: true
}
}

View File

@@ -2,16 +2,6 @@
<MessagePane :server="server" />
</template>
<script async setup lang="ts">
const route = useRoute()
const server: IChannel = await $fetch(`/api/channels/${route.params.id}`)
if (server) {
useGlobalStore().addDM(server);
useGlobalStore().setActive('dms', server.id);
}
</script>
<script lang="ts">
import { useGlobalStore } from '~/stores/store'
import { IChannel } from '~/types'
@@ -21,8 +11,24 @@ definePageMeta({
})
export default {
async setup() {
const route = useRoute()
const headers = useRequestHeaders(['cookie']) as Record<string, string>
const server: IChannel = await $fetch(`/api/channels/${route.params.id}`, { headers })
if (!server) throw new Error('could not find the dm')
useGlobalStore().addDM(server);
if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumably?')
useGlobalStore().setActive('dms', route.params.id);
return {
server
}
},
async updated() {
if (!useGlobalStore().activeServer == this.server) useGlobalStore().setActive('dms', this.server.id)
const route = useRoute()
if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumably?')
if (useGlobalStore().activeServer !== this.server) useGlobalStore().setActive('dms', route.params.id)
},
}
</script>

View File

@@ -19,9 +19,14 @@ export default {
userId: ''
}
},
mounted() {
console.log('mounted')
useGlobalStore().setActive('dms', '@me')
},
methods: {
async startDM() {
const server: IChannel = await $fetch('/api/channels/createDM', { method: 'post', body: { partnerId: this.userId } })
const headers = useRequestHeaders(['cookie']) as Record<string, string>
const server: IChannel = await $fetch('/api/channels/createDM', { method: 'post', body: { partnerId: this.userId }, headers })
useGlobalStore().addDM(server)
useRouter().push({ path: '/channel/@me/' + server.id })

View File

@@ -2,19 +2,6 @@
<MessagePane :server="server" />
</template>
<script async setup lang="ts">
const route = useRoute()
const server: IChannel = await $fetch(`/api/channels/${route.params.id}`)
const realServer = useGlobalStore().servers?.filter((e) => e.channels.some((el) => el.id == route.params.id))[0]
if (realServer) {
useGlobalStore().addServer(realServer);
useGlobalStore().setActive('servers', realServer.id)
}
</script>
<script lang="ts">
import { useGlobalStore } from '~/stores/store'
import { IChannel } from '~/types'
@@ -24,12 +11,32 @@ definePageMeta({
})
export default {
async setup() {
const route = useRoute()
const headers = useRequestHeaders(['cookie']) as Record<string, string>
const server: IChannel = await $fetch(`/api/channels/${route.params.id}`, { headers })
const realServer = useGlobalStore().servers?.find((e) => e.channels.some((el) => el.id == route.params.id))
if (!realServer) throw new Error('realServer not found, this means that the channel is serverless but not a dm????');
useGlobalStore().addServer(realServer);
if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumiably?')
useGlobalStore().setActive('servers', route.params.id)
return {
server
}
},
async updated() {
const route = useRoute()
const headers = useRequestHeaders(['cookie']) as Record<string, string>;
if (!this.server) return;
this.server = await $fetch(`/api/channels/${route.params.id}`);
this.server = await $fetch(`/api/channels/${route.params.id}`, { headers });
if (!useGlobalStore().activeServer == this.server.id) useGlobalStore().setActive('servers', this.server.id)
if (typeof route.params.id !== 'string') throw new Error('route.params.id must be a string, but got an array presumiably?')
if (useGlobalStore().activeServer.id !== this.server.id) useGlobalStore().setActive('servers', route.params.id)
}
}
</script>

View File

@@ -37,12 +37,14 @@ export default {
},
methods: {
async signup() {
const headers = useRequestHeaders(['cookie'])
if (!this.username || !this.password) return;
const user = await $fetch('/api/login', {
method: 'post', body: {
username: this.username,
password: this.password
}
},
headers
}) as { userId: string; token: string; user: SafeUser; }
const userId = useCookie('userId')

View File

@@ -48,13 +48,15 @@ export default {
},
methods: {
async signup() {
const headers = useRequestHeaders(['cookie']) as Record<string, string>
if (!this.username || !this.password || !this.email) return;
const user = await $fetch('/api/signup', {
method: 'post', body: {
username: this.username,
email: this.email,
password: this.password
}
},
headers
}) as { userId: string; token: string; user: SafeUser; }
const userId = useCookie('userId')

View File

@@ -4,6 +4,7 @@ const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) {
event.node.res.statusCode = 401;
return {
message: 'You must be logged in to view a channel.'
}

View File

@@ -4,7 +4,7 @@ const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) {
// event.node.res.statusCode = 401;
event.node.res.statusCode = 401;
return {
message: "Unauthenticated"
}

View File

@@ -4,6 +4,7 @@ const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) {
event.node.res.statusCode = 401;
return {
message: 'You must be logged in to view a channel.'
}

View File

@@ -4,6 +4,7 @@ const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) {
event.node.res.statusCode = 401;
return {
message: 'You must be logged in to view a channel.'
}

View File

@@ -3,8 +3,11 @@ import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) return {
message: 'You must be logged in to view a channel.'
if (!event.context.user.authenticated) {
event.node.res.statusCode = 401;
return {
message: 'You must be logged in to view a channel.'
}
}
if (!event.context.params.id) {

View File

@@ -1,10 +1,13 @@
import { IServer } from '~/types'
import { IInviteCode, IServer } from '~/types'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) return {
message: 'You must be logged in to view a channel.'
if (!event.context.user.authenticated) {
event.node.res.statusCode = 401;
return {
message: 'You must be logged in to view a channel.'
}
}
const { inviteId } = await readBody(event);
@@ -20,10 +23,24 @@ export default defineEventHandler(async (event) => {
where: {
id: inviteId
},
include: {
server: true
select: {
id: true,
server: {
select: {
id: true,
name: true,
participants: {
select: {
id: true,
}
}
}
},
expires: true,
expiryDate: true,
maxUses: true
}
})
}) as IInviteCode | null;
if (!invite) {
event.node.res.statusCode = 404;
@@ -32,6 +49,15 @@ export default defineEventHandler(async (event) => {
}
}
const userInServer = invite.server.participants.find((e) => e.id === event.context.user.id);
if (userInServer) {
event.node.res.statusCode = 409;
return {
message: `You are already in that server.`
}
}
// TODO: check if invite is valid
const server = await prisma.server.update({
@@ -50,7 +76,7 @@ export default defineEventHandler(async (event) => {
channels: true,
roles: true
}
}) as IServer
}) as unknown as IServer
if (!server) {
event.node.res.statusCode = 404;

View File

@@ -1,65 +1,79 @@
import { PrismaClient } from '@prisma/client'
import { IServer, IUser } from '~/types'
import { IChannel, IServer, IUser } from '~/types'
const prisma = new PrismaClient()
export default defineEventHandler(async (event) => {
if (!event.context.user.authenticated) {
// event.node.res.statusCode = 401;
event.node.res.statusCode = 401;
return {
message: "Unauthenticated"
}
}
const { servers, channels } = await prisma.user.findFirst({
const servers = await prisma.server.findMany({
where: {
id: event.context.user.id
participants: {
some: {
id: event.context.user.id
}
}
},
select: {
id: true,
name: true,
channels: {
select: {
id: true,
name: true,
messages: false,
DM: true,
dmParticipants: true
name: true
}
},
servers: {
participants: {
select: {
id: true,
username: true
}
},
roles: {
select: {
id: true,
name: true,
channels: {
administrator: true,
owner: true,
users: {
select: {
id: true,
DM: true,
name: true
}
},
participants: {
select: {
id: true,
username: true
}
},
roles: {
select: {
id: true,
name: true,
administrator: true,
owner: true,
users: {
select: {
id: true
}
}
id: true
}
}
},
},
}
}
}
}) as IUser | null;
}) as unknown as IServer[] | null;
const dms = await prisma.channel.findMany({
where: {
DM: true,
dmParticipants: {
some: {
id: event.context.user.id
}
}
},
select: {
id: true,
name: true,
messages: false,
DM: true,
dmParticipants: {
select: {
id: true,
username: true
}
}
}
}) as IChannel[] | null;
return {
servers, channels
servers, dms
}
})

View File

@@ -1,9 +1,9 @@
import { Ref } from "vue";
import { SafeUser, IServer, IChannel } from "../types";
export const useGlobalStore = defineStore('global', {
state: () => ({
activeServer: {} as IServer,
activeServer: {} as IServer | IChannel,
activeServerType: '' as "dms" | "servers" | undefined,
user: {} as SafeUser,
dms: [] as IChannel[],
servers: [] as IServer[]
@@ -17,20 +17,38 @@ export const useGlobalStore = defineStore('global', {
this.servers.push(server)
},
addDM(dmChannel: IChannel) {
if (!this.channels || this.channels.find((e) => e.id === dmChannel.id)) return;
this.channels.push(dmChannel)
if (!this.dms || this.dms.find((e) => e.id === dmChannel.id)) return;
this.dms.push(dmChannel)
},
setActive(type: string, serverId: string) {
if (serverId === '@me') {
this.activeServer = {} as IServer
setServers(servers: Array<IServer>) {
this.servers = servers
},
setDms(dms: Array<IChannel>) {
this.dms = dms
},
setActive(type: "servers" | "dms", channelId: string) {
if (channelId === '@me') {
this.activeServer = {} as IServer | IChannel
this.activeServerType = 'dms'
return;
}
console.log(this.activeServer)
this.activeServerType = type
const searchableArray: IChannel[] | IServer[] | undefined = this[type]
if (!searchableArray) return;
this.activeServer = searchableArray.find((e: IServer | IChannel) => e.id === serverId)
console.log(this.activeServer, searchableArray.find((e: IServer | IChannel) => e.id === serverId))
let activeServerIndex: number;
if (type === 'servers') {
activeServerIndex = searchableArray.findIndex((e) => {
return e.channels.some((channel: IChannel) => channel.id === channelId)
})
} else {
activeServerIndex = searchableArray.findIndex((e) => {
return e.id === channelId
})
}
this.activeServer = this.servers[activeServerIndex]
},
},
})

View File

@@ -1,30 +1,3 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
"allowJs": true,
"sourceMap": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"jsx": "preserve",
"noUncheckedIndexedAccess": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
},
"extends": "./.nuxt/tsconfig.json"
}
"extends": "./.nuxt/tsconfig.json"
}

30
tsconfig.json.bak Normal file
View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
"allowJs": true,
"sourceMap": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"jsx": "preserve",
"noUncheckedIndexedAccess": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
},
"extends": "./.nuxt/tsconfig.json"
}

View File

@@ -13,16 +13,16 @@ export type SafeUser = Omit<Omit<IUser, 'passwordhash'>, 'email'>
export interface IServer {
id: string;
name: string;
channels?: Array<IChannel>;
channels: Array<IChannel>;
participants: Array<SafeUser>;
roles?: Array<IRole>;
roles: Array<IRole>;
inviteCode?: Array<IInviteCode>;
}
export interface IChannel {
id: string;
name: string;
server?: IServer;
server: IServer;
messages?: Array<IMessage>
DM: boolean;
dmParticipants?: Array<SafeUser>;