+
-
-
- {{ messageDate }}
-
-
+
+ {{ date.toLocaleTimeString(undefined, { timeStyle: "short" }) }}
+
\ No newline at end of file
diff --git a/components/Settings/AppSettings/Keybinds.vue b/components/Settings/AppSettings/Keybinds.vue
new file mode 100644
index 0000000..ea54137
--- /dev/null
+++ b/components/Settings/AppSettings/Keybinds.vue
@@ -0,0 +1,12 @@
+
+
+
Keybinds (TBA)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Settings/AppSettings/Language.vue b/components/Settings/AppSettings/Language.vue
new file mode 100644
index 0000000..b1c3a8a
--- /dev/null
+++ b/components/Settings/AppSettings/Language.vue
@@ -0,0 +1,12 @@
+
+
+
Language (TBA)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Settings/AppSettings/Notifications.vue b/components/Settings/AppSettings/Notifications.vue
new file mode 100644
index 0000000..2e6de9c
--- /dev/null
+++ b/components/Settings/AppSettings/Notifications.vue
@@ -0,0 +1,12 @@
+
+
+
Notifications (TBA)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Settings/UserSettings/Account.vue b/components/Settings/UserSettings/Account.vue
new file mode 100644
index 0000000..0fe5013
--- /dev/null
+++ b/components/Settings/UserSettings/Account.vue
@@ -0,0 +1,155 @@
+
+
+
My Account
+
+
+
+
AVATAR
+
+
+
+
DISPLAY NAME
+
+
USERNAME
+
+
PRONOUNS
+
+
ABOUT ME
+
+
+
+
+
+
+
+
+
+
Password (and eventually authenticator)
+
+
+
Account Deletion
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Settings/UserSettings/Connections.vue b/components/Settings/UserSettings/Connections.vue
new file mode 100644
index 0000000..97190ec
--- /dev/null
+++ b/components/Settings/UserSettings/Connections.vue
@@ -0,0 +1,12 @@
+
+
+
Connections (TBA)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Settings/UserSettings/Devices.vue b/components/Settings/UserSettings/Devices.vue
new file mode 100644
index 0000000..7006a12
--- /dev/null
+++ b/components/Settings/UserSettings/Devices.vue
@@ -0,0 +1,12 @@
+
+
+
Devices (TBA)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Settings/UserSettings/Privacy.vue b/components/Settings/UserSettings/Privacy.vue
new file mode 100644
index 0000000..0f1387d
--- /dev/null
+++ b/components/Settings/UserSettings/Privacy.vue
@@ -0,0 +1,13 @@
+
+
+
Privacy (TBA)
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/UserPopup.vue b/components/UserPopup.vue
new file mode 100644
index 0000000..092e7d5
--- /dev/null
+++ b/components/UserPopup.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/composables/api.ts b/composables/api.ts
new file mode 100644
index 0000000..603f71f
--- /dev/null
+++ b/composables/api.ts
@@ -0,0 +1,56 @@
+import type { ChannelResponse, GuildMemberResponse, GuildResponse, MessageResponse } from "~/types/interfaces";
+
+export const useApi = () => {
+ async function fetchGuilds(): Promise
{
+ return await fetchWithApi(`/guilds`);
+ }
+
+ async function fetchGuild(guildId: string): Promise {
+ return await fetchWithApi(`/guilds/${guildId}`);
+ }
+
+ async function fetchChannels(guildId: string): Promise {
+ return await fetchWithApi(`/guilds/${guildId}/channels`);
+ }
+
+ async function fetchChannel(channelId: string): Promise {
+ return await fetchWithApi(`/channels/${channelId}`)
+ }
+
+ async function fetchMembers(guildId: string): Promise {
+ return await fetchWithApi(`/guilds/${guildId}/members`);
+ }
+
+ async function fetchMember(guildId: string, memberId: string): Promise {
+ return await fetchWithApi(`/guilds/${guildId}/members/${memberId}`);
+ }
+
+ async function fetchUsers() {
+ return await fetchWithApi(`/users`);
+ }
+
+ async function fetchUser(userId: string) {
+ return await fetchWithApi(`/users/${userId}`);
+ }
+
+ async function fetchMessages(channelId: string, options?: { amount?: number, offset?: number }): Promise {
+ return await fetchWithApi(`/channels/${channelId}/messages`, { query: { amount: options?.amount ?? 100, offset: options?.offset ?? 0 } });
+ }
+
+ async function fetchMessage(channelId: string, messageId: string): Promise {
+ return await fetchWithApi(`/channels/${channelId}/messages/${messageId}`);
+ }
+
+ return {
+ fetchGuilds,
+ fetchGuild,
+ fetchChannels,
+ fetchChannel,
+ fetchMembers,
+ fetchMember,
+ fetchUsers,
+ fetchUser,
+ fetchMessages,
+ fetchMessage
+ }
+}
diff --git a/composables/auth.ts b/composables/auth.ts
index 19ac694..0ac2e8b 100644
--- a/composables/auth.ts
+++ b/composables/auth.ts
@@ -75,7 +75,7 @@ export const useAuth = () => {
async function fetchUser() {
if (!accessToken.value) return;
console.log("fetchuser access token:", accessToken.value);
- const res = await fetchWithApi("/users/me") as UserResponse;
+ const res = await fetchWithApi("/me") as UserResponse;
user.value = res;
return user.value;
}
@@ -88,6 +88,20 @@ export const useAuth = () => {
return user.value;
}
+
+ // as in email the password link
+ async function resetPassword() {
+ // ...
+ }
+
+ async function disableAccount() {
+ // ...
+ }
+
+ async function deleteAccount() {
+ // ...
+ }
+
return {
accessToken,
register,
diff --git a/layouts/auth.vue b/layouts/auth.vue
index b3e234e..b7d5c5e 100644
--- a/layouts/auth.vue
+++ b/layouts/auth.vue
@@ -51,28 +51,17 @@
\ No newline at end of file
diff --git a/pages/servers/[serverId]/index.vue b/pages/servers/[serverId]/index.vue
index ce6bea5..464a123 100644
--- a/pages/servers/[serverId]/index.vue
+++ b/pages/servers/[serverId]/index.vue
@@ -5,8 +5,9 @@
diff --git a/pages/settings.vue b/pages/settings.vue
new file mode 100644
index 0000000..5db18fe
--- /dev/null
+++ b/pages/settings.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4d4de9d..07816d4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,6 +20,12 @@ importers:
'@pinia/nuxt':
specifier: 0.11.0
version: 0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.3)(vue@3.5.13(typescript@5.8.3)))
+ dompurify:
+ specifier: ^3.2.6
+ version: 3.2.6
+ marked:
+ specifier: ^15.0.12
+ version: 15.0.12
nuxt:
specifier: ^3.17.0
version: 3.17.0(@netlify/blobs@8.2.0)(@parcel/watcher@2.5.1)(@types/node@22.15.3)(db0@0.3.2)(eslint@9.25.1(jiti@2.4.2))(ioredis@5.6.1)(lightningcss@1.29.2)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.40.1)(terser@5.39.0)(typescript@5.8.3)(vite@6.3.3(@types/node@22.15.3)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.0)(yaml@2.7.1))(yaml@2.7.1)
@@ -1199,6 +1205,9 @@ packages:
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
+ '@types/trusted-types@2.0.7':
+ resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
+
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
@@ -2113,6 +2122,9 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
+ dompurify@3.2.6:
+ resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==}
+
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
@@ -3092,6 +3104,11 @@ packages:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
+ marked@15.0.12:
+ resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
+ engines: {node: '>= 18'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -6094,6 +6111,9 @@ snapshots:
'@types/triple-beam@1.3.5': {}
+ '@types/trusted-types@2.0.7':
+ optional: true
+
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 22.15.3
@@ -7082,6 +7102,10 @@ snapshots:
dependencies:
domelementtype: 2.3.0
+ dompurify@3.2.6:
+ optionalDependencies:
+ '@types/trusted-types': 2.0.7
+
domutils@3.2.2:
dependencies:
dom-serializer: 2.0.0
@@ -8170,6 +8194,8 @@ snapshots:
dependencies:
semver: 6.3.1
+ marked@15.0.12: {}
+
math-intrinsics@1.1.0: {}
mdn-data@2.0.28: {}
diff --git a/public/favicon.ico b/public/favicon.ico
index 18993ad..b3d28fa 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/types/interfaces.ts b/types/interfaces.ts
index c840a68..e451a4b 100644
--- a/types/interfaces.ts
+++ b/types/interfaces.ts
@@ -23,6 +23,14 @@ export interface GuildResponse {
member_count: number
}
+export interface GuildMemberResponse {
+ uuid: string,
+ nickname: string,
+ user_uuid: string,
+ guild_uuid: string,
+ user: UserResponse
+}
+
export interface ChannelResponse {
uuid: string,
guild_uuid: string,
@@ -50,6 +58,8 @@ export interface UserResponse {
username: string,
display_name: string | null,
avatar: string | null,
+ pronouns: string | null,
+ about: string | null,
email?: string,
email_verified?: boolean
}
@@ -62,3 +72,14 @@ export interface StatsResponse {
email_verification_required: boolean,
build_number: string
}
+
+export interface ScrollPosition {
+ scrollHeight: number,
+ scrollWidth: number,
+ scrollTop: number,
+ scrollLeft: number
+ offsetHeight: number,
+ offsetWidth: number,
+ offsetTop: number,
+ offsetLeft: number
+}
diff --git a/utils/fetchMember.ts b/utils/fetchMember.ts
deleted file mode 100644
index 8f4a9e4..0000000
--- a/utils/fetchMember.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { UserResponse } from "~/types/interfaces"
-
-export default async (serverId: string, memberId: string): Promise => {
- const user = await fetchWithApi(`/guilds/${serverId}/members/${memberId}`) as UserResponse;
- return user;
-}
diff --git a/utils/fetchUser.ts b/utils/fetchUser.ts
deleted file mode 100644
index d509fe0..0000000
--- a/utils/fetchUser.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import type { UserResponse } from "~/types/interfaces"
-
-export default async (serverId: string, userId: string): Promise => {
- const user = await fetchWithApi(`/users/${userId}`) as UserResponse;
- return user;
-}
diff --git a/utils/fetchWithApi.ts b/utils/fetchWithApi.ts
index 79fdae8..4089db8 100644
--- a/utils/fetchWithApi.ts
+++ b/utils/fetchWithApi.ts
@@ -9,8 +9,6 @@ export default async (path: string, options: NitroFetchOptions = {})
path = path.slice(0, path.lastIndexOf("/"));
}
console.log("formatted path:", path);
- const accessToken = useCookie("access_token");
- console.log("access token:", accessToken.value);
const apiBase = useCookie("api_base").value;
const apiVersion = useRuntimeConfig().public.apiVersion;
console.log("heyoooo")
@@ -21,23 +19,24 @@ export default async (path: string, options: NitroFetchOptions = {})
}
console.log("path:", path)
const { revoke, refresh } = useAuth();
- console.log("access token 2:", accessToken.value);
-
+
let headers: HeadersInit = {};
-
- if (accessToken.value) {
- headers = {
- ...options.headers,
- "Authorization": `Bearer ${accessToken.value}`
- };
- } else {
- headers = {
- ...options.headers
- };
- }
-
+
+
let reauthFailed = false;
while (!reauthFailed) {
+ const accessToken = useCookie("access_token");
+ console.log("access token:", accessToken.value);
+ if (accessToken.value) {
+ headers = {
+ ...options.headers,
+ "Authorization": `Bearer ${accessToken.value}`
+ };
+ } else {
+ headers = {
+ ...options.headers
+ };
+ }
try {
console.log("fetching:", URL.parse(apiBase + path));
const res = await $fetch(URL.parse(apiBase + path)!.href, {
@@ -74,9 +73,10 @@ export default async (path: string, options: NitroFetchOptions = {})
console.log("Path is refresh endpoint, throwing error");
throw error;
}
+ } else {
+ console.log("throwing error:", error);
+ throw error;
}
- console.log("throwing error");
- throw error;
}
}
}
diff --git a/utils/getScrollPosition.ts b/utils/getScrollPosition.ts
new file mode 100644
index 0000000..1f4a99f
--- /dev/null
+++ b/utils/getScrollPosition.ts
@@ -0,0 +1,14 @@
+import type { ScrollPosition } from "~/types/interfaces";
+
+export default (element: HTMLElement): ScrollPosition => {
+ return {
+ scrollHeight: element.scrollHeight,
+ scrollWidth: element.scrollWidth,
+ scrollTop: element.scrollTop,
+ scrollLeft: element.scrollLeft,
+ offsetHeight: element.offsetHeight,
+ offsetWidth: element.offsetWidth,
+ offsetTop: element.offsetTop,
+ offsetLeft: element.offsetLeft
+ };
+}
diff --git a/utils/scrollToBottom.ts b/utils/scrollToBottom.ts
index aca99b4..8064bd8 100644
--- a/utils/scrollToBottom.ts
+++ b/utils/scrollToBottom.ts
@@ -1,6 +1,6 @@
-export default (element: Ref) => {
- if (element.value) {
- element.value.scrollTo({ top: element.value.scrollHeight });
+export default (element: HTMLElement) => {
+ if (element) {
+ element.scrollTo({ top: element.scrollHeight });
return;
}
}
diff --git a/utils/setScrollPosition.ts b/utils/setScrollPosition.ts
new file mode 100644
index 0000000..c7ddfd2
--- /dev/null
+++ b/utils/setScrollPosition.ts
@@ -0,0 +1,5 @@
+import type { ScrollPosition } from "~/types/interfaces";
+
+export default (element: HTMLElement, scrollPosition: ScrollPosition) => {
+ return element.scrollTo({ top: scrollPosition.scrollTop, left: scrollPosition.scrollLeft });
+}