dev #1
18 changed files with 366 additions and 154 deletions
13
app.vue
13
app.vue
|
@ -1,7 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<NuxtPage />
|
<div>
|
||||||
|
<NuxtPage />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
|
@ -9,10 +15,13 @@ body {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: rgb(190, 190, 190);
|
color: rgb(190, 190, 190);
|
||||||
background-color: rgb(30, 30, 30);
|
background-color: rgb(30, 30, 30);
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*:focus-visible {
|
||||||
|
outline: 1px solid rgb(150, 150, 150);
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: aquamarine;
|
color: aquamarine;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div id="loading-container">
|
||||||
Loading...
|
<Icon name="lucide:loader-circle" id="loading-circle" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -8,6 +8,31 @@
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
|
|
||||||
|
#loading-container {
|
||||||
|
position: fixed;
|
||||||
|
left: 50dvw;
|
||||||
|
top: 50dvh;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading-circle {
|
||||||
|
animation-name: spin;
|
||||||
|
animation-duration: 1s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="message">
|
<div class="message">
|
||||||
<div>
|
<div>
|
||||||
<img v-if="props.img" class="message-author-avatar" :src="img" :alt="username">
|
<img v-if="props.img" class="message-author-avatar" :src="props.img" :alt="username">
|
||||||
<Icon v-else name="lucide:user" class="message-author-avatar" />
|
<Icon v-else name="lucide:user" class="message-author-avatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="message-data">
|
<div class="message-data">
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const props = defineProps<{ class?: string, img?: string, username: string, text: string, timestamp: number, format: "12" | "24" }>();
|
const props = defineProps<{ class?: string, img?: string | null, username: string, text: string, timestamp: number, format: "12" | "24" }>();
|
||||||
|
|
||||||
const messageDate = ref<string>();
|
const messageDate = ref<string>();
|
||||||
const showHover = ref(false);
|
const showHover = ref(false);
|
||||||
|
@ -74,6 +74,7 @@ if (now.getUTCHours() >= 0) {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1dvh;
|
gap: 1dvh;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-author {
|
.message-author {
|
||||||
|
@ -84,6 +85,7 @@ if (now.getUTCHours() >= 0) {
|
||||||
.message-author-avatar {
|
.message-author-avatar {
|
||||||
margin-right: 1dvw;
|
margin-right: 1dvw;
|
||||||
width: 3em;
|
width: 3em;
|
||||||
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-username {
|
.author-username {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="message-area">
|
<div id="message-area">
|
||||||
<div id="messages" ref="messagesElement">
|
<div id="messages" ref="messagesElement">
|
||||||
<Message v-for="message of messages" :username="displayNames[message.user_uuid]" :text="message.message"
|
<Message v-for="message of messages" :username="message.user.display_name ?? message.user.username" :text="message.message"
|
||||||
:timestamp="uuidToTimestamp(message.uuid)" format="12" />
|
:timestamp="uuidToTimestamp(message.uuid)" :img="message.user.avatar" format="12" />
|
||||||
</div>
|
</div>
|
||||||
<div id="message-box">
|
<div id="message-box">
|
||||||
<form id="message-form" @submit="sendMessage">
|
<form id="message-form" @submit="sendMessage">
|
||||||
<input v-model="messageInput" type="text" name="message-input" id="message-box-input">
|
<input v-model="messageInput" type="text" name="message-input" id="message-box-input" autocomplete="off">
|
||||||
<button type="submit">
|
<button id="submit-button" type="submit">
|
||||||
<Icon name="lucide:send" />
|
<Icon name="lucide:send" />
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { MessageResponse } from '~/types/interfaces';
|
import type { MessageResponse } from '~/types/interfaces';
|
||||||
import fetchUser from '~/utils/fetchUser';
|
import scrollToBottom from '~/utils/scrollToBottom';
|
||||||
|
|
||||||
const props = defineProps<{ channelUrl: string, amount?: number, offset?: number, reverse?: boolean }>();
|
const props = defineProps<{ channelUrl: string, amount?: number, offset?: number, reverse?: boolean }>();
|
||||||
|
|
||||||
|
@ -31,8 +31,6 @@ if (messagesRes && props.reverse) {
|
||||||
|
|
||||||
const messages = ref<MessageResponse[]>([]);
|
const messages = ref<MessageResponse[]>([]);
|
||||||
|
|
||||||
const displayNames = ref<Record<string, string>>({});
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const messageInput = ref<string>();
|
const messageInput = ref<string>();
|
||||||
|
@ -40,14 +38,6 @@ const messageInput = ref<string>();
|
||||||
const messagesElement = ref<HTMLDivElement>();
|
const messagesElement = ref<HTMLDivElement>();
|
||||||
|
|
||||||
if (messagesRes) messages.value = messagesRes;
|
if (messagesRes) messages.value = messagesRes;
|
||||||
const displayNamesArr: Record<string, string> = {};
|
|
||||||
for (const message of messages.value) {
|
|
||||||
if (!displayNamesArr[message.user_uuid]) {
|
|
||||||
const displayName = await getDisplayName(message.user_uuid);
|
|
||||||
displayNamesArr[message.user_uuid] = displayName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
displayNames.value = displayNamesArr;
|
|
||||||
|
|
||||||
const accessToken = useCookie("access_token").value;
|
const accessToken = useCookie("access_token").value;
|
||||||
const apiBase = useCookie("api_base").value;
|
const apiBase = useCookie("api_base").value;
|
||||||
|
@ -71,23 +61,21 @@ ws.addEventListener("open", (event) => {
|
||||||
console.log("WebSocket connected!");
|
console.log("WebSocket connected!");
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.addEventListener("message", (event) => {
|
ws.addEventListener("message", async (event) => {
|
||||||
console.log("event data:", event.data);
|
console.log("event data:", event.data);
|
||||||
messages.value?.push(
|
messages.value?.push(
|
||||||
JSON.parse(event.data)
|
JSON.parse(event.data)
|
||||||
);
|
);
|
||||||
|
await nextTick();
|
||||||
|
if (messagesElement.value) {
|
||||||
|
console.log("scrolling to bottom");
|
||||||
|
scrollToBottom(messagesElement);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await refresh();
|
await refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDisplayName(memberId: string): Promise<string> {
|
|
||||||
//const user = await fetchMember((route.params.serverId as string), memberId);
|
|
||||||
const user = await fetchUser((route.params.serverId as string), memberId);
|
|
||||||
return user!.display_name ?? user!.username;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessage(e: Event) {
|
function sendMessage(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const text = messageInput.value;
|
const text = messageInput.value;
|
||||||
|
@ -100,7 +88,9 @@ function sendMessage(e: Event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
messagesElement.value?.scrollTo({ top: messagesElement.value.scrollHeight });
|
if (messagesElement.value) {
|
||||||
|
scrollToBottom(messagesElement);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -108,27 +98,38 @@ onMounted(async () => {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
#message-area {
|
#message-area {
|
||||||
padding-top: 3dvh;
|
display: grid;
|
||||||
}
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 8fr 1fr;
|
||||||
#message-area {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding-top: 3dvh;
|
||||||
padding-left: 1dvw;
|
padding-left: 1dvw;
|
||||||
padding-right: 1dvw;
|
padding-right: 1dvw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-box {
|
#message-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
border: 1px solid rgb(70, 70, 70);
|
border: 1px solid rgb(70, 70, 70);
|
||||||
padding-bottom: 1dvh;
|
padding-bottom: 1dvh;
|
||||||
padding-top: 1dvh;
|
padding-top: 1dvh;
|
||||||
margin-bottom: 1dvh;
|
margin-bottom: 1dvh;
|
||||||
|
margin-top: 1dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-input {
|
#message-form {
|
||||||
width: 100%;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message-box-input {
|
||||||
|
width: 80%;
|
||||||
|
background-color: rgb(50, 50, 50);
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#messages {
|
#messages {
|
||||||
|
@ -138,4 +139,14 @@ onMounted(async () => {
|
||||||
gap: 1dvh;
|
gap: 1dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#submit-button {
|
||||||
|
background-color: inherit;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit-button:hover {
|
||||||
|
background-color: rgb(40, 40, 40);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
|
@ -30,11 +30,11 @@ export const useAuth = () => {
|
||||||
{
|
{
|
||||||
username, password: hashedPass, device_name: "Linux Laptop"
|
username, password: hashedPass, device_name: "Linux Laptop"
|
||||||
}
|
}
|
||||||
}) as { access_token: string, refresh_token: string }; fetch
|
}) as { access_token: string, refresh_token: string };
|
||||||
console.log("hi");
|
console.log("hi");
|
||||||
accessToken.value = res.access_token;
|
accessToken.value = res.access_token;
|
||||||
console.log("access token:", accessToken.value);
|
console.log("access token:", accessToken.value);
|
||||||
await fetchUser();
|
//await fetchUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout(password: string) {
|
async function logout(password: string) {
|
||||||
|
@ -60,19 +60,21 @@ export const useAuth = () => {
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
console.log("refreshing");
|
console.log("refreshing");
|
||||||
try {
|
const res = await fetchWithApi("/auth/refresh", {
|
||||||
const res = await fetchWithApi("/auth/refresh", {
|
method: "POST"
|
||||||
method: "POST"
|
}) as any;
|
||||||
}) as { access_token: string };
|
console.log("finished refreshing:", res);
|
||||||
|
if (res && res.access_token) {
|
||||||
accessToken.value = res.access_token;
|
accessToken.value = res.access_token;
|
||||||
console.log("set new access token");
|
console.log("set new access token");
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error("refresh error:", error);
|
console.log("refresh didn't return access token");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUser() {
|
async function fetchUser() {
|
||||||
if (!accessToken.value) return;
|
if (!accessToken.value) return;
|
||||||
|
console.log("fetchuser access token:", accessToken.value);
|
||||||
const res = await fetchWithApi("/users/me") as UserResponse;
|
const res = await fetchWithApi("/users/me") as UserResponse;
|
||||||
user.value = res;
|
user.value = res;
|
||||||
return user.value;
|
return user.value;
|
||||||
|
|
29
error.vue
Normal file
29
error.vue
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<div id="error-container">
|
||||||
|
<h2>{{ error?.statusCode }}</h2>
|
||||||
|
<p>{{ error?.message }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { NuxtError } from '#app';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
error: Object as () => NuxtError
|
||||||
|
});
|
||||||
|
|
||||||
|
if (props.error?.statusCode == 401) {
|
||||||
|
console.log("HELLO THERE");
|
||||||
|
clearError({ redirect: `/login?redirect_to=${useRoute().fullPath}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
#error-container {
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="root-container" style="margin-top: 5dvh;">
|
<div id="root-container" style="margin-top: 5dvh;">
|
||||||
<Loading v-if="!mounted" />
|
<div id="main-container">
|
||||||
<div v-else id="main-container">
|
|
||||||
<div v-if="!instanceUrl">
|
<div v-if="!instanceUrl">
|
||||||
<div v-if="instanceError" style="color: red;">
|
<div v-if="instanceError" style="color: red;">
|
||||||
{{ instanceError }}
|
{{ instanceError }}
|
||||||
|
@ -49,7 +48,6 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { FetchError } from 'ofetch';
|
import { FetchError } from 'ofetch';
|
||||||
|
|
||||||
const mounted = ref(false);
|
|
||||||
const redirectTo = useRoute().query.redirect_to;
|
const redirectTo = useRoute().query.redirect_to;
|
||||||
|
|
||||||
const apiVersion = useRuntimeConfig().public.apiVersion;
|
const apiVersion = useRuntimeConfig().public.apiVersion;
|
||||||
|
@ -64,7 +62,6 @@ if (auth.accessToken.value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
mounted.value = true;
|
|
||||||
const cookie = useCookie("instance_url").value;
|
const cookie = useCookie("instance_url").value;
|
||||||
instanceUrl.value = cookie;
|
instanceUrl.value = cookie;
|
||||||
console.log(cookie);
|
console.log(cookie);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="client-root">
|
<Loading v-show="loading" />
|
||||||
|
<div :class="{ hidden: loading, visible: !loading }" id="client-root">
|
||||||
<div id="homebar">
|
<div id="homebar">
|
||||||
<div class="homebar-item">
|
<div class="homebar-item">
|
||||||
main bar
|
main bar
|
||||||
|
@ -21,6 +22,8 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const loading = useState("loading", () => false);
|
||||||
|
|
||||||
const servers = [
|
const servers = [
|
||||||
{
|
{
|
||||||
name: "Test",
|
name: "Test",
|
||||||
|
@ -90,11 +93,21 @@ function sendMessage(e: Event) {
|
||||||
<style>
|
<style>
|
||||||
#client-root {
|
#client-root {
|
||||||
/* border: 1px solid white; */
|
/* border: 1px solid white; */
|
||||||
height: 100%;
|
height: 100dvh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 4fr 18fr 4fr;
|
grid-template-columns: 1fr 4fr 18fr 4fr;
|
||||||
grid-template-rows: 4dvh auto;
|
grid-template-rows: 4dvh auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
opacity: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible {
|
||||||
|
opacity: 100%;
|
||||||
|
transition-duration: 500ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
#homebar {
|
#homebar {
|
||||||
|
@ -114,7 +127,6 @@ function sendMessage(e: Event) {
|
||||||
#__nuxt {
|
#__nuxt {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-column {
|
.grid-column {
|
||||||
|
|
24
middleware/auth.global.ts
Normal file
24
middleware/auth.global.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||||
|
console.log("to.fullPath:", to.fullPath);
|
||||||
|
const loading = useState("loading");
|
||||||
|
const accessToken = useCookie("access_token").value;
|
||||||
|
if (["/login", "/register"].includes(to.path)) {
|
||||||
|
if (accessToken) {
|
||||||
|
return await navigateTo("/");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!accessToken) {
|
||||||
|
loading.value = true;
|
||||||
|
console.log("set loading to true");
|
||||||
|
const { refresh } = useAuth();
|
||||||
|
console.log("hi");
|
||||||
|
await refresh();
|
||||||
|
const query = new URLSearchParams();
|
||||||
|
query.set("redirect_to", to.path);
|
||||||
|
loading.value = false;
|
||||||
|
console.log("set loading to false");
|
||||||
|
return await navigateTo("/login?" + (query ?? ""));
|
||||||
|
}
|
||||||
|
})
|
|
@ -3,13 +3,14 @@ export default defineNuxtConfig({
|
||||||
compatibilityDate: '2024-11-01',
|
compatibilityDate: '2024-11-01',
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
modules: ['@nuxt/eslint', '@nuxt/image', "@pinia/nuxt", "@nuxt/icon"],
|
modules: ['@nuxt/eslint', '@nuxt/image', "@pinia/nuxt", "@nuxt/icon"],
|
||||||
|
ssr: false,
|
||||||
app: {
|
app: {
|
||||||
/*
|
/*
|
||||||
Defines what prefix the client runs on
|
Defines what prefix the client runs on
|
||||||
E.g.: baseURL set to "/web" would host at https://gorb.app/web
|
E.g.: baseURL set to "/web" would host at https://gorb.app/web
|
||||||
Default is "/" (aka root), which hosts at https://gorb.app/
|
Default is "/" (aka root), which hosts at https://gorb.app/
|
||||||
*/
|
*/
|
||||||
baseURL: "/web",
|
baseURL: "/",
|
||||||
head: {
|
head: {
|
||||||
title: 'Gorb',
|
title: 'Gorb',
|
||||||
// this is purely used to embed in that other chat app, and similar stuff
|
// this is purely used to embed in that other chat app, and similar stuff
|
||||||
|
|
17
pages/index.vue
Normal file
17
pages/index.vue
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<NuxtLayout>
|
||||||
|
|
||||||
|
</NuxtLayout>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
layout: "client"
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,46 +1,60 @@
|
||||||
<template>
|
<template>
|
||||||
<NuxtLayout>
|
<NuxtLayout>
|
||||||
<form @submit="formLogin">
|
<form @submit="formLogin">
|
||||||
<div>
|
<div>
|
||||||
<label for="username">Username/Email</label>
|
<label for="username">Username/Email</label>
|
||||||
<br>
|
<br>
|
||||||
<input type="text" name="username" id="username" v-model="form.username">
|
<input type="text" name="username" id="username" v-model="form.username">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<br>
|
<br>
|
||||||
<input type="password" name="password" id="password" v-model="form.password">
|
<input type="password" name="password" id="password" v-model="form.password">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button type="submit">Login</button>
|
<button type="submit">Login</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
Don't have an account? <NuxtLink href="/register">Register</NuxtLink> one!
|
Don't have an account? <NuxtLink :href="registerUrl">Register</NuxtLink> one!
|
||||||
</div>
|
</div>
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "auth"
|
layout: "auth"
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
//const authStore = useAuthStore();
|
//const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const query = useRoute().query as Record<string, string>;
|
||||||
|
const searchParams = new URLSearchParams(query);
|
||||||
|
const registerUrl = `/register?${searchParams}`
|
||||||
|
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
|
|
||||||
async function formLogin(e: Event) {
|
async function formLogin(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log("Sending login data");
|
console.log("Sending login data");
|
||||||
await login(form.username, form.password, "Linux Laptop");
|
try {
|
||||||
//return navigateTo(redirectTo ? redirectTo as string : useAppConfig().baseURL as string);
|
await login(form.username, form.password, "Linux Laptop");
|
||||||
|
console.log("logged in");
|
||||||
|
if (query.redirect_to) {
|
||||||
|
console.log("redirecting to:", query.redirect_to);
|
||||||
|
return await navigateTo(query.redirect_to);
|
||||||
|
}
|
||||||
|
return await navigateTo("/");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error logging in:", error);
|
||||||
|
}
|
||||||
|
//return navigateTo(redirectTo ? redirectTo as string : useAppConfig().baseURL as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div>
|
<div>
|
||||||
Already have an account? <NuxtLink href="/login">Log in</NuxtLink>!
|
Already have an account? <NuxtLink :href="loginUrl">Log in</NuxtLink>!
|
||||||
</div>
|
</div>
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</template>
|
</template>
|
||||||
|
@ -74,7 +74,9 @@ const errorMessages = reactive({
|
||||||
|
|
||||||
//const authStore = useAuthStore();
|
//const authStore = useAuthStore();
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const redirectTo = useRoute().query.redirect_to;
|
const query = useRoute().query as Record<string, string>;
|
||||||
|
const searchParams = new URLSearchParams(query);
|
||||||
|
const loginUrl = `/login?${searchParams}`
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (auth.accessToken.value) {
|
if (auth.accessToken.value) {
|
||||||
|
@ -120,7 +122,12 @@ const apiVersion = useRuntimeConfig().public.apiVersion;
|
||||||
async function register(e: Event) {
|
async function register(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log("Sending registration data");
|
console.log("Sending registration data");
|
||||||
await auth.register(form.username, form.email, form.password);
|
try {
|
||||||
|
await auth.register(form.username, form.email, form.password);
|
||||||
|
return await navigateTo(query.redirect_to);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error registering:", error);
|
||||||
|
}
|
||||||
//return navigateTo(redirectTo ? redirectTo as string : useAppConfig().baseURL as string);
|
//return navigateTo(redirectTo ? redirectTo as string : useAppConfig().baseURL as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,25 +39,20 @@
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const server: GuildResponse | undefined = await fetchWithApi(`servers/${route.params.serverId}`);
|
const loading = useState("loading");
|
||||||
|
|
||||||
const channels: ChannelResponse[] | undefined = await fetchWithApi(
|
|
||||||
`servers/${route.params.serverId}/channels`
|
|
||||||
);
|
|
||||||
const channel: ChannelResponse | undefined = await fetchWithApi(
|
|
||||||
route.path
|
|
||||||
);
|
|
||||||
|
|
||||||
const channelUrlPath = `servers/${route.params.serverId}/channels/${route.params.channelId}`;
|
const channelUrlPath = `servers/${route.params.serverId}/channels/${route.params.channelId}`;
|
||||||
|
|
||||||
console.log("channel:", channel);
|
const server = ref<GuildResponse | undefined>();
|
||||||
|
const channels = ref<ChannelResponse[] | undefined>();
|
||||||
|
const channel = ref<ChannelResponse | undefined>();
|
||||||
|
|
||||||
const showInvitePopup = ref(false);
|
const showInvitePopup = ref(false);
|
||||||
|
|
||||||
import type { ChannelResponse, GuildResponse, MessageResponse } from "~/types/interfaces";
|
import type { ChannelResponse, GuildResponse, MessageResponse } from "~/types/interfaces";
|
||||||
|
|
||||||
//const servers = await fetchWithApi("/servers") as { uuid: string, name: string, description: string }[];
|
//const servers = await fetchWithApi("/servers") as { uuid: string, name: string, description: string }[];
|
||||||
//console.log("servers:", servers);
|
//console.log("channelid: servers:", servers);
|
||||||
const members = [
|
const members = [
|
||||||
{
|
{
|
||||||
id: "3287484395",
|
id: "3287484395",
|
||||||
|
@ -106,6 +101,23 @@ const members = [
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
loading.value = true;
|
||||||
|
console.log("channelid: set loading to true");
|
||||||
|
server.value = await fetchWithApi(`servers/${route.params.serverId}`);
|
||||||
|
|
||||||
|
channels.value = await fetchWithApi(
|
||||||
|
`servers/${route.params.serverId}/channels`
|
||||||
|
);
|
||||||
|
channel.value = await fetchWithApi(
|
||||||
|
route.path
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("channelid: channel:", channel);
|
||||||
|
loading.value = false;
|
||||||
|
console.log("channelid: set loading to false");
|
||||||
|
});
|
||||||
|
|
||||||
function showServerSettings() { }
|
function showServerSettings() { }
|
||||||
|
|
||||||
function toggleInvitePopup(e: Event) {
|
function toggleInvitePopup(e: Event) {
|
||||||
|
|
32
pages/verify-email.vue
Normal file
32
pages/verify-email.vue
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<div id="container">
|
||||||
|
<div v-if="errorMessage">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
const errorMessage = ref();
|
||||||
|
|
||||||
|
const token = useRoute().query.token;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetchWithApi("/auth/verify-email", { query: { token } });
|
||||||
|
console.log("hi");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error verifying email:", error);
|
||||||
|
errorMessage.value = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
#container {
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -32,10 +32,11 @@ export interface ChannelResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageResponse {
|
export interface MessageResponse {
|
||||||
uuid: string
|
uuid: string,
|
||||||
channel_uuid: string
|
channel_uuid: string,
|
||||||
user_uuid: string
|
user_uuid: string,
|
||||||
message: string
|
message: string,
|
||||||
|
user: UserResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InviteResponse {
|
export interface InviteResponse {
|
||||||
|
@ -49,6 +50,6 @@ export interface UserResponse {
|
||||||
username: string,
|
username: string,
|
||||||
display_name: string | null,
|
display_name: string | null,
|
||||||
avatar: string | null,
|
avatar: string | null,
|
||||||
email: string,
|
email?: string,
|
||||||
email_verified: boolean
|
email_verified?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,63 +9,74 @@ export default async <T>(path: string, options: NitroFetchOptions<string> = {})
|
||||||
path = path.slice(0, path.lastIndexOf("/"));
|
path = path.slice(0, path.lastIndexOf("/"));
|
||||||
}
|
}
|
||||||
console.log("formatted path:", path);
|
console.log("formatted path:", path);
|
||||||
try {
|
const accessToken = useCookie("access_token");
|
||||||
const accessToken = useCookie("access_token");
|
console.log("access token:", accessToken.value);
|
||||||
console.log("access token:", accessToken.value);
|
const apiBase = useCookie("api_base").value;
|
||||||
const apiBase = useCookie("api_base").value;
|
const apiVersion = useRuntimeConfig().public.apiVersion;
|
||||||
const apiVersion = useRuntimeConfig().public.apiVersion;
|
console.log("heyoooo")
|
||||||
console.log("heyoooo")
|
console.log("apiBase:", apiBase);
|
||||||
console.log("apiBase:", apiBase);
|
if (!apiBase) {
|
||||||
if (!apiBase) {
|
console.log("no api base");
|
||||||
console.log("no api base");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
console.log("path:", path)
|
||||||
console.log("path:", path)
|
const { revoke, refresh } = useAuth();
|
||||||
const { revoke, refresh } = useAuth();
|
console.log("access token 2:", accessToken.value);
|
||||||
console.log("access token 2:", accessToken.value);
|
|
||||||
|
|
||||||
let headers: HeadersInit = {};
|
let headers: HeadersInit = {};
|
||||||
|
|
||||||
if (accessToken.value) {
|
if (accessToken.value) {
|
||||||
headers = {
|
headers = {
|
||||||
...options.headers,
|
...options.headers,
|
||||||
"Authorization": `Bearer ${accessToken.value}`
|
"Authorization": `Bearer ${accessToken.value}`
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
headers = {
|
headers = {
|
||||||
...options.headers
|
...options.headers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let reauthFailed = false;
|
let reauthFailed = false;
|
||||||
while (!reauthFailed) {
|
while (!reauthFailed) {
|
||||||
try {
|
try {
|
||||||
console.log("fetching:", URL.parse(apiBase + path));
|
console.log("fetching:", URL.parse(apiBase + path));
|
||||||
const res = await $fetch<T>(URL.parse(apiBase + path)!.href, {
|
const res = await $fetch<T>(URL.parse(apiBase + path)!.href, {
|
||||||
...options,
|
...options,
|
||||||
headers,
|
headers,
|
||||||
credentials: "include"
|
credentials: "include"
|
||||||
});
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error?.response?.status === 401) {
|
console.error("Error fetching resource");
|
||||||
if (!path.startsWith("/auth/refresh")) {
|
if (error?.response?.status === 401) {
|
||||||
try {
|
console.log("Error status is 401");
|
||||||
await refresh();
|
if (!path.startsWith("/auth/refresh")) {
|
||||||
} catch (error: any) {
|
console.log("Path is not refresh endpoint");
|
||||||
if (error?.response?.status === 401) {
|
try {
|
||||||
reauthFailed = true;
|
console.log("Trying to refresh");
|
||||||
await revoke();
|
await refresh();
|
||||||
return;
|
console.log("Successfully refreshed token");
|
||||||
}
|
} catch (error: any) {
|
||||||
|
console.log("Failed to refresh token");
|
||||||
|
if (error?.response?.status === 401) {
|
||||||
|
console.log("Refresh returned 401");
|
||||||
|
reauthFailed = true;
|
||||||
|
console.log("Revoking");
|
||||||
|
await revoke();
|
||||||
|
console.log("Redirecting to login");
|
||||||
|
await navigateTo("/login");
|
||||||
|
console.log("redirected");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Path is refresh endpoint, throwing error");
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
console.log("throwing error");
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("error:", error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6
utils/scrollToBottom.ts
Normal file
6
utils/scrollToBottom.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export default (element: Ref<HTMLElement | undefined, HTMLElement | undefined>) => {
|
||||||
|
if (element.value) {
|
||||||
|
element.value.scrollTo({ top: element.value.scrollHeight });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue