浏览代码

refactor: Converted user VueX store into Pinia stores

Owen Diffey 2 年之前
父节点
当前提交
58c276a4a3
共有 35 个文件被更改,包括 416 次插入516 次删除
  1. 16 16
      frontend/src/App.vue
  2. 6 13
      frontend/src/components/AddToPlaylistDropdown.vue
  3. 4 5
      frontend/src/components/ChristmasLights.vue
  4. 4 5
      frontend/src/components/LongJobs.vue
  5. 6 6
      frontend/src/components/PlaylistTabBase.vue
  6. 5 4
      frontend/src/components/Queue.vue
  7. 0 4
      frontend/src/components/Request.vue
  8. 5 3
      frontend/src/components/SongItem.vue
  9. 4 4
      frontend/src/components/StationInfoBox.vue
  10. 9 14
      frontend/src/components/global/MainHeader.vue
  11. 2 4
      frontend/src/components/global/UserLink.vue
  12. 4 5
      frontend/src/components/modals/EditPlaylist/Tabs/Settings.vue
  13. 3 4
      frontend/src/components/modals/EditPlaylist/index.vue
  14. 3 1
      frontend/src/components/modals/Login.vue
  15. 4 3
      frontend/src/components/modals/ManageStation/index.vue
  16. 3 1
      frontend/src/components/modals/Register.vue
  17. 10 11
      frontend/src/composables/useSortablePlaylists.ts
  18. 32 37
      frontend/src/main.ts
  19. 4 5
      frontend/src/pages/Banned.vue
  20. 4 3
      frontend/src/pages/Home.vue
  21. 6 14
      frontend/src/pages/Profile/Tabs/RecentActivity.vue
  22. 5 5
      frontend/src/pages/Profile/index.vue
  23. 5 5
      frontend/src/pages/ResetPassword.vue
  24. 5 9
      frontend/src/pages/Settings/Tabs/Account.vue
  25. 11 17
      frontend/src/pages/Settings/Tabs/Preferences.vue
  26. 5 4
      frontend/src/pages/Settings/Tabs/Profile.vue
  27. 5 11
      frontend/src/pages/Settings/Tabs/Security.vue
  28. 4 7
      frontend/src/pages/Station/Sidebar/index.vue
  29. 6 8
      frontend/src/pages/Station/index.vue
  30. 0 2
      frontend/src/store/index.ts
  31. 0 285
      frontend/src/store/modules/user.ts
  32. 175 0
      frontend/src/stores/userAuth.ts
  33. 28 0
      frontend/src/stores/userPlaylists.ts
  34. 30 0
      frontend/src/stores/userPreferences.ts
  35. 3 1
      frontend/src/stores/websockets.ts

+ 16 - 16
frontend/src/App.vue

@@ -3,7 +3,10 @@ import { useStore } from "vuex";
 import { useRoute, useRouter } from "vue-router";
 import { defineAsyncComponent, ref, computed, watch, onMounted } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
+import { useUserPreferencesStore } from "@/stores/userPreferences";
 import ws from "@/ws";
 import aw from "@/aw";
 import keyboardShortcuts from "@/keyboardShortcuts";
@@ -23,15 +26,12 @@ const store = useStore();
 const route = useRoute();
 const router = useRouter();
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const banned = computed(() => store.state.user.auth.banned);
 const modals = computed(() => store.state.modalVisibility.modals);
 const activeModals = computed(() => store.state.modalVisibility.activeModals);
-const nightmode = computed(() => store.state.user.preferences.nightmode);
-const activityWatch = computed(
-	() => store.state.user.preferences.activityWatch
-);
+
 const { socket } = useWebsocketsStore();
+const userAuthStore = useUserAuthStore();
+const userPreferencesStore = useUserPreferencesStore();
 
 const apiDomain = ref("");
 const socketConnected = ref(true);
@@ -42,22 +42,22 @@ const broadcastChannel = ref();
 const christmas = ref(false);
 const disconnectedMessage = ref();
 
+const { loggedIn, banned } = storeToRefs(userAuthStore);
+const { nightmode, activityWatch } = storeToRefs(userPreferencesStore);
+const {
+	changeNightmode,
+	changeAutoSkipDisliked,
+	changeActivityLogPublic,
+	changeAnonymousSongRequests,
+	changeActivityWatch
+} = userPreferencesStore;
+
 const aModalIsOpen = computed(() => Object.keys(activeModals.value).length > 0);
 
 const openModal = payload =>
 	store.dispatch("modalVisibility/openModal", payload);
 const closeCurrentModal = () =>
 	store.dispatch("modalVisibility/closeCurrentModal");
-const changeNightmode = payload =>
-	store.dispatch("user/preferences/changeNightmode", payload);
-const changeAutoSkipDisliked = payload =>
-	store.dispatch("user/preferences/changeAutoSkipDisliked", payload);
-const changeActivityLogPublic = payload =>
-	store.dispatch("user/preferences/changeActivityLogPublic", payload);
-const changeAnonymousSongRequests = payload =>
-	store.dispatch("user/preferences/changeAnonymousSongRequests", payload);
-const changeActivityWatch = payload =>
-	store.dispatch("user/preferences/changeActivityWatch", payload);
 
 const toggleNightMode = () => {
 	localStorage.setItem("nightmode", !nightmode.value);

+ 6 - 13
frontend/src/components/AddToPlaylistDropdown.vue

@@ -1,8 +1,10 @@
 <script setup lang="ts">
-import { ref, computed, onMounted } from "vue";
+import { ref, onMounted } from "vue";
 import { useStore } from "vuex";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserPlaylistsStore } from "@/stores/userPlaylists";
 import ws from "@/ws";
 
 const store = useStore();
@@ -20,20 +22,11 @@ const props = defineProps({
 
 const dropdown = ref(null);
 
-const playlists = computed(() => store.state.user.playlists.playlists);
-const fetchedPlaylists = computed(
-	() => store.state.user.playlists.fetchedPlaylists
-);
 const { socket } = useWebsocketsStore();
+const userPlaylistsStore = useUserPlaylistsStore();
 
-const setPlaylists = payload =>
-	store.dispatch("user/playlists/setPlaylists", payload);
-
-const addPlaylist = payload =>
-	store.dispatch("user/playlists/addPlaylist", payload);
-
-const removePlaylist = payload =>
-	store.dispatch("user/playlists/removePlaylist", payload);
+const { playlists, fetchedPlaylists } = storeToRefs(userPlaylistsStore);
+const { setPlaylists, addPlaylist, removePlaylist } = userPlaylistsStore;
 
 const openModal = payload =>
 	store.dispatch("modalVisibility/openModal", payload);

+ 4 - 5
frontend/src/components/ChristmasLights.vue

@@ -1,15 +1,14 @@
 <script setup lang="ts">
-import { computed } from "vue";
-import { useStore } from "vuex";
-
-const store = useStore();
+import { storeToRefs } from "pinia";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 defineProps({
 	small: { type: Boolean, default: false },
 	lights: { type: Number, default: 1 }
 });
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
+const userAuthStore = useUserAuthStore();
+const { loggedIn } = storeToRefs(userAuthStore);
 </script>
 
 <template>

+ 4 - 5
frontend/src/components/LongJobs.vue

@@ -1,19 +1,18 @@
 <script setup lang="ts">
-import { useStore } from "vuex";
-import { defineAsyncComponent, ref, computed, onMounted } from "vue";
+import { defineAsyncComponent, ref, onMounted } from "vue";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useLongJobsStore } from "@/stores/longJobs";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 const FloatingBox = defineAsyncComponent(
 	() => import("@/components/FloatingBox.vue")
 );
 
-const store = useStore();
-
 const body = ref(document.body);
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
+const userAuthStore = useUserAuthStore();
+const { loggedIn } = storeToRefs(userAuthStore);
 
 const { socket } = useWebsocketsStore();
 

+ 6 - 6
frontend/src/components/PlaylistTabBase.vue

@@ -7,6 +7,8 @@ import ws from "@/ws";
 
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useStationStore } from "@/stores/station";
+import { useUserAuthStore } from "@/stores/userAuth";
+import { useUserPlaylistsStore } from "@/stores/userPlaylists";
 import { useModalState } from "@/vuex_helpers";
 
 import useSortablePlaylists from "@/composables/useSortablePlaylists";
@@ -33,6 +35,7 @@ const store = useStore();
 
 const { socket } = useWebsocketsStore();
 const stationStore = useStationStore();
+const userAuthStore = useUserAuthStore();
 
 const tab = ref("current");
 const search = reactive({
@@ -58,10 +61,7 @@ const {
 	calculatePlaylistOrder
 } = useSortablePlaylists();
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const role = computed(() => store.state.user.auth.role);
-const userId = computed(() => store.state.user.auth.userId);
-
+const { loggedIn, role, userId } = storeToRefs(userAuthStore);
 const { autoRequest } = storeToRefs(stationStore);
 
 const { autofill } = useModalState("modals/manageStation/MODAL_UUID", {
@@ -108,8 +108,8 @@ const nextPageResultsCount = computed(() =>
 
 const openModal = payload =>
 	store.dispatch("modalVisibility/openModal", payload);
-const setPlaylists = payload =>
-	store.dispatch("user/playlists/setPlaylists", payload);
+
+const { setPlaylists } = useUserPlaylistsStore();
 
 const { addPlaylistToAutoRequest, removePlaylistFromAutoRequest } =
 	stationStore;

+ 5 - 4
frontend/src/components/Queue.vue

@@ -3,8 +3,10 @@ import { useStore } from "vuex";
 import { defineAsyncComponent, ref, computed, onUpdated } from "vue";
 import { Sortable } from "sortablejs-vue3";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useStationStore } from "@/stores/station";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 const SongItem = defineAsyncComponent(
 	() => import("@/components/SongItem.vue")
@@ -17,12 +19,11 @@ const props = defineProps({
 
 const store = useStore();
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const userId = computed(() => store.state.user.auth.userId);
-const userRole = computed(() => store.state.user.auth.role);
-
 const { socket } = useWebsocketsStore();
 const stationStore = useStationStore();
+const userAuthStore = useUserAuthStore();
+
+const { loggedIn, userId, role: userRole } = storeToRefs(userAuthStore);
 
 const repositionSongInList = payload => {
 	if (props.sector === "manageStation")

+ 0 - 4
frontend/src/components/Request.vue

@@ -34,10 +34,6 @@ const tab = ref("songs");
 const sitename = ref("Musare");
 const tabs = ref({});
 
-// const loggedIn = computed(() => store.state.user.auth.loggedIn);
-// const role = computed(() => store.state.user.auth.role);
-// const userId = computed(() => store.state.user.auth.userId);
-
 const station = computed({
 	get() {
 		if (props.sector === "manageStation")

+ 5 - 3
frontend/src/components/SongItem.vue

@@ -1,8 +1,10 @@
 <script setup lang="ts">
 import { useStore } from "vuex";
-import { ref, computed, onMounted, onUnmounted } from "vue";
+import { ref, onMounted, onUnmounted } from "vue";
 import { formatDistance, parseISO } from "date-fns";
+import { storeToRefs } from "pinia";
 import AddToPlaylistDropdown from "./AddToPlaylistDropdown.vue";
+import { useUserAuthStore } from "@/stores/userAuth";
 import utils from "@/utils";
 
 const store = useStore();
@@ -39,8 +41,8 @@ const formatRequestedAtInterval = ref();
 const hoveredTippy = ref(false);
 const songActions = ref(null);
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const userRole = computed(() => store.state.user.auth.role);
+const userAuthStore = useUserAuthStore();
+const { loggedIn, role: userRole } = storeToRefs(userAuthStore);
 
 function formatRequestedAt() {
 	if (props.requestedBy && props.song.requestedAt)

+ 4 - 4
frontend/src/components/StationInfoBox.vue

@@ -1,10 +1,12 @@
 <script setup lang="ts">
-import { computed } from "vue";
 import { useStore } from "vuex";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 const store = useStore();
+const userAuthStore = useUserAuthStore();
 
 const props = defineProps({
 	station: { type: Object, default: null },
@@ -13,10 +15,8 @@ const props = defineProps({
 	showGoToStation: { type: Boolean, default: false }
 });
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const userId = computed(() => store.state.user.auth.userId);
-const role = computed(() => store.state.user.auth.role);
 const { socket } = useWebsocketsStore();
+const { loggedIn, userId, role } = storeToRefs(userAuthStore);
 
 function isOwnerOnly() {
 	return loggedIn.value && userId.value === props.station.owner;

+ 9 - 14
frontend/src/components/global/MainHeader.vue

@@ -1,15 +1,11 @@
 <script setup lang="ts">
 import { useStore } from "vuex";
-import {
-	defineAsyncComponent,
-	ref,
-	computed,
-	onMounted,
-	watch,
-	nextTick
-} from "vue";
+import { defineAsyncComponent, ref, onMounted, watch, nextTick } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
+import { useUserPreferencesStore } from "@/stores/userPreferences";
 
 const ChristmasLights = defineAsyncComponent(
 	() => import("@/components/ChristmasLights.vue")
@@ -22,6 +18,7 @@ defineProps({
 });
 
 const store = useStore();
+const userAuthStore = useUserAuthStore();
 
 const localNightmode = ref(false);
 const isMobile = ref(false);
@@ -34,15 +31,13 @@ const siteSettings = ref({
 });
 const windowWidth = ref(0);
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const username = computed(() => store.state.user.auth.username);
-const role = computed(() => store.state.user.auth.role);
 const { socket } = useWebsocketsStore();
 
+const { loggedIn, username, role } = storeToRefs(userAuthStore);
+const { logout } = userAuthStore;
+const { changeNightmode } = useUserPreferencesStore();
+
 const openModal = modal => store.dispatch("modalVisibility/openModal", modal);
-const logout = () => store.dispatch("user/auth/logout");
-const changeNightmode = nightmode =>
-	store.dispatch("user/preferences/changeNightmode", nightmode);
 
 const toggleNightmode = toggle => {
 	localNightmode.value = toggle || !localNightmode.value;

+ 2 - 4
frontend/src/components/global/UserLink.vue

@@ -1,20 +1,18 @@
 <script setup lang="ts">
-import { useStore } from "vuex";
 import { ref, onMounted } from "vue";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 const props = defineProps({
 	userId: { type: String, default: "" },
 	link: { type: Boolean, default: true }
 });
 
-const store = useStore();
-
 const user = ref({
 	name: "Unknown",
 	username: null
 });
 
-const getBasicUser = userId => store.dispatch("user/auth/getBasicUser", userId);
+const { getBasicUser } = useUserAuthStore();
 
 onMounted(() => {
 	getBasicUser(props.userId).then(basicUser => {

+ 4 - 5
frontend/src/components/modals/EditPlaylist/Tabs/Settings.vue

@@ -1,19 +1,18 @@
 <script setup lang="ts">
-import { useStore } from "vuex";
 import { computed } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useModalState } from "@/vuex_helpers";
 import validation from "@/validation";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 const props = defineProps({
 	modalUuid: { type: String, default: "" }
 });
 
-const store = useStore();
-
-const userId = computed(() => store.state.user.auth.userId);
-const userRole = computed(() => store.state.user.auth.role);
+const userAuthStore = useUserAuthStore();
+const { userId, role: userRole } = storeToRefs(userAuthStore);
 
 const { socket } = useWebsocketsStore();
 

+ 3 - 4
frontend/src/components/modals/EditPlaylist/index.vue

@@ -12,6 +12,7 @@ import Toast from "toasters";
 import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useStationStore } from "@/stores/station";
+import { useUserAuthStore } from "@/stores/userAuth";
 import { useModalState, useModalActions } from "@/vuex_helpers";
 import ws from "@/ws";
 import utils from "@/utils";
@@ -31,14 +32,12 @@ const props = defineProps({
 
 const store = useStore();
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const userId = computed(() => store.state.user.auth.userId);
-const userRole = computed(() => store.state.user.auth.role);
-
 const { socket } = useWebsocketsStore();
 const stationStore = useStationStore();
+const userAuthStore = useUserAuthStore();
 
 const { station } = storeToRefs(stationStore);
+const { loggedIn, userId, role: userRole } = storeToRefs(userAuthStore);
 
 const drag = ref(false);
 const apiDomain = ref("");

+ 3 - 1
frontend/src/components/modals/Login.vue

@@ -3,6 +3,7 @@ import { useStore } from "vuex";
 import { ref, onMounted } from "vue";
 import { useRoute } from "vue-router";
 import Toast from "toasters";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 const route = useRoute();
 
@@ -20,7 +21,8 @@ const passwordElement = ref();
 
 const store = useStore();
 
-const login = payload => store.dispatch("user/auth/login", payload);
+const { login } = useUserAuthStore();
+
 const openModal = payload =>
 	store.dispatch("modalVisibility/openModal", payload);
 const closeCurrentModal = () =>

+ 4 - 3
frontend/src/components/modals/ManageStation/index.vue

@@ -9,8 +9,10 @@ import {
 	onBeforeUnmount
 } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useModalState, useModalActions } from "@/vuex_helpers";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 
 const Queue = defineAsyncComponent(() => import("@/components/Queue.vue"));
 const SongItem = defineAsyncComponent(
@@ -31,9 +33,8 @@ const props = defineProps({
 
 const store = useStore();
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const userId = computed(() => store.state.user.auth.userId);
-const role = computed(() => store.state.user.auth.role);
+const userAuthStore = useUserAuthStore();
+const { loggedIn, userId, role } = storeToRefs(userAuthStore);
 
 const { socket } = useWebsocketsStore();
 

+ 3 - 1
frontend/src/components/modals/Register.vue

@@ -3,6 +3,7 @@ import { useStore } from "vuex";
 import { defineAsyncComponent, ref, watch, onMounted } from "vue";
 import { useRoute } from "vue-router";
 import Toast from "toasters";
+import { useUserAuthStore } from "@/stores/userAuth";
 import validation from "@/validation";
 
 const InputHelpBox = defineAsyncComponent(
@@ -45,7 +46,8 @@ const passwordElement = ref();
 
 const store = useStore();
 
-const register = payload => store.dispatch("user/auth/register", payload);
+const { register } = useUserAuthStore();
+
 const openModal = payload =>
 	store.dispatch("modalVisibility/openModal", payload);
 const closeCurrentModal = () =>

+ 10 - 11
frontend/src/composables/useSortablePlaylists.ts

@@ -1,8 +1,10 @@
 import { ref, computed, onMounted, onBeforeUnmount, nextTick } from "vue";
-import { useStore } from "vuex";
 import { Sortable } from "sortablejs-vue3";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
+import { useUserPlaylistsStore } from "@/stores/userPlaylists";
 import ws from "@/ws";
 
 export default function useSortablePlaylists() {
@@ -10,15 +12,17 @@ export default function useSortablePlaylists() {
 	const drag = ref(false);
 	const userId = ref();
 
-	const store = useStore();
+	const userAuthStore = useUserAuthStore();
+	const userPlaylistsStore = useUserPlaylistsStore();
+
+	const { userId: myUserId } = storeToRefs(userAuthStore);
 
 	const playlists = computed({
-		get: () => store.state.user.playlists.playlists,
+		get: () => userPlaylistsStore.playlists,
 		set: playlists => {
-			store.commit("user/playlists/updatePlaylists", playlists);
+			userPlaylistsStore.updatePlaylists(playlists);
 		}
 	});
-	const myUserId = computed(() => store.state.user.auth.userId);
 	const isCurrentUser = computed(() => userId.value === myUserId.value);
 	const dragOptions = computed(() => ({
 		animation: 200,
@@ -29,12 +33,7 @@ export default function useSortablePlaylists() {
 
 	const { socket } = useWebsocketsStore();
 
-	const setPlaylists = playlists =>
-		store.dispatch("user/playlists/setPlaylists", playlists);
-	const addPlaylist = playlist =>
-		store.dispatch("user/playlists/addPlaylist", playlist);
-	const removePlaylist = playlist =>
-		store.dispatch("user/playlists/removePlaylist", playlist);
+	const { setPlaylists, addPlaylist, removePlaylist } = userPlaylistsStore;
 
 	const calculatePlaylistOrder = () => {
 		const calculatedOrder = [];

+ 32 - 37
frontend/src/main.ts

@@ -6,6 +6,8 @@ import { createRouter, createWebHistory } from "vue-router";
 import { createPinia } from "pinia";
 import "lofig";
 
+import { useUserAuthStore } from "@/stores/userAuth";
+import { useUserPreferencesStore } from "@/stores/userPreferences";
 import ws from "@/ws";
 import ms from "@/ms";
 import store from "./store";
@@ -234,6 +236,8 @@ const router = createRouter({
 	]
 });
 
+const userAuthStore = useUserAuthStore();
+
 router.beforeEach((to, from, next) => {
 	if (window.stationInterval) {
 		clearInterval(window.stationInterval);
@@ -253,27 +257,25 @@ router.beforeEach((to, from, next) => {
 
 	if (to.meta.loginRequired || to.meta.adminRequired || to.meta.guestsOnly) {
 		const gotData = () => {
-			if (to.meta.loginRequired && !store.state.user.auth.loggedIn)
+			if (to.meta.loginRequired && !userAuthStore.loggedIn)
 				next({ path: "/login", query: "" });
-			else if (
-				to.meta.adminRequired &&
-				store.state.user.auth.role !== "admin"
-			)
+			else if (to.meta.adminRequired && userAuthStore.role !== "admin")
 				next({ path: "/", query: "" });
-			else if (to.meta.guestsOnly && store.state.user.auth.loggedIn)
+			else if (to.meta.guestsOnly && userAuthStore.loggedIn)
 				next({ path: "/", query: "" });
 			else next();
 		};
 
-		if (store.state.user.auth.gotData) gotData();
+		if (userAuthStore.gotData) gotData();
 		else {
-			const watcher = store.watch(
-				state => state.user.auth.gotData,
-				() => {
-					watcher();
-					gotData();
-				}
-			);
+			// TODO
+			// const watcher = store.watch(
+			// 	state => userAuthStore.gotData,
+			// 	() => {
+			// 		watcher();
+			// 		gotData();
+			// 	}
+			// );
 		}
 	} else next();
 });
@@ -305,7 +307,7 @@ lofig.folder = defaultConfigURL;
 	ws.socket.on("ready", res => {
 		const { loggedIn, role, username, userId, email } = res.data;
 
-		store.dispatch("user/auth/authData", {
+		userAuthStore.authData({
 			loggedIn,
 			role,
 			username,
@@ -315,47 +317,40 @@ lofig.folder = defaultConfigURL;
 	});
 
 	ws.socket.on("keep.event:user.banned", res =>
-		store.dispatch("user/auth/banUser", res.data.ban)
+		userAuthStore.banUser(res.data.ban)
 	);
 
 	ws.socket.on("keep.event:user.username.updated", res =>
-		store.dispatch("user/auth/updateUsername", res.data.username)
+		userAuthStore.updateUsername(res.data.username)
 	);
 
 	ws.socket.on("keep.event:user.preferences.updated", res => {
 		const { preferences } = res.data;
 
+		const {
+			changeAutoSkipDisliked,
+			changeNightmode,
+			changeActivityLogPublic,
+			changeAnonymousSongRequests,
+			changeActivityWatch
+		} = useUserPreferencesStore();
+
 		if (preferences.autoSkipDisliked !== undefined)
-			store.dispatch(
-				"user/preferences/changeAutoSkipDisliked",
-				preferences.autoSkipDisliked
-			);
+			changeAutoSkipDisliked(preferences.autoSkipDisliked);
 
 		if (preferences.nightmode !== undefined) {
 			localStorage.setItem("nightmode", preferences.nightmode);
-			store.dispatch(
-				"user/preferences/changeNightmode",
-				preferences.nightmode
-			);
+			changeNightmode(preferences.nightmode);
 		}
 
 		if (preferences.activityLogPublic !== undefined)
-			store.dispatch(
-				"user/preferences/changeActivityLogPublic",
-				preferences.activityLogPublic
-			);
+			changeActivityLogPublic(preferences.activityLogPublic);
 
 		if (preferences.anonymousSongRequests !== undefined)
-			store.dispatch(
-				"user/preferences/changeAnonymousSongRequests",
-				preferences.anonymousSongRequests
-			);
+			changeAnonymousSongRequests(preferences.anonymousSongRequests);
 
 		if (preferences.activityWatch !== undefined)
-			store.dispatch(
-				"user/preferences/changeActivityWatch",
-				preferences.activityWatch
-			);
+			changeActivityWatch(preferences.activityWatch);
 	});
 
 	app.mount("#root");

+ 4 - 5
frontend/src/pages/Banned.vue

@@ -1,11 +1,10 @@
 <script setup lang="ts">
-import { computed } from "vue";
+import { storeToRefs } from "pinia";
 import { formatDistance } from "date-fns";
-import { useStore } from "vuex";
+import { useUserAuthStore } from "@/stores/userAuth";
 
-const store = useStore();
-
-const ban = computed(() => store.state.user.auth.ban);
+const userAuthStore = useUserAuthStore();
+const { ban } = storeToRefs(userAuthStore);
 </script>
 
 <template>

+ 4 - 3
frontend/src/pages/Home.vue

@@ -4,17 +4,18 @@ import { useRoute, useRouter } from "vue-router";
 import { ref, computed, onMounted, onBeforeUnmount } from "vue";
 import { Sortable } from "sortablejs-vue3";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 import keyboardShortcuts from "@/keyboardShortcuts";
 import ws from "@/ws";
 
 const store = useStore();
+const userAuthStore = useUserAuthStore();
 const route = useRoute();
 const router = useRouter();
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const userId = computed(() => store.state.user.auth.userId);
-const role = computed(() => store.state.user.auth.role);
+const { loggedIn, userId, role } = storeToRefs(userAuthStore);
 
 const { socket } = useWebsocketsStore();
 

+ 6 - 14
frontend/src/pages/Profile/Tabs/RecentActivity.vue

@@ -1,22 +1,15 @@
 <script setup lang="ts">
-import {
-	defineAsyncComponent,
-	ref,
-	computed,
-	onMounted,
-	onUnmounted
-} from "vue";
-import { useStore } from "vuex";
+import { defineAsyncComponent, ref, onMounted, onUnmounted } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 import ws from "@/ws";
 
 const ActivityItem = defineAsyncComponent(
 	() => import("@/components/ActivityItem.vue")
 );
 
-const store = useStore();
-
 const { socket } = useWebsocketsStore();
 
 const props = defineProps({
@@ -33,10 +26,9 @@ const maxPosition = ref(1);
 const offsettedFromNextSet = ref(0);
 const isGettingSet = ref(false);
 
-const myUserId = computed(() => store.state.user.auth.userId);
-
-const getBasicUser = payload =>
-	store.dispatch("user/auth/getBasicUser", payload);
+const userAuthStore = useUserAuthStore();
+const { userId: myUserId } = storeToRefs(userAuthStore);
+const { getBasicUser } = userAuthStore;
 
 const hideActivity = activityId => {
 	socket.dispatch("activities.hideActivity", activityId, res => {

+ 5 - 5
frontend/src/pages/Profile/index.vue

@@ -1,9 +1,10 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, computed, onMounted } from "vue";
-import { useStore } from "vuex";
+import { defineAsyncComponent, ref, onMounted } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { format, parseISO } from "date-fns";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 import ws from "@/ws";
 import useTabQueryHandler from "@/composables/useTabQueryHandler";
 
@@ -15,7 +16,6 @@ const RecentActivity = defineAsyncComponent(
 );
 const Playlists = defineAsyncComponent(() => import("./Tabs/Playlists.vue"));
 
-const store = useStore();
 const route = useRoute();
 const router = useRouter();
 const { tab, showTab } = useTabQueryHandler("recent-activity");
@@ -26,8 +26,8 @@ const user = ref();
 const userId = ref("");
 const isUser = ref(false);
 
-const role = computed(() => store.state.user.auth.role);
-const myUserId = computed(() => store.state.user.auth.userId);
+const userAuthStore = useUserAuthStore();
+const { userId: myUserId, role } = storeToRefs(userAuthStore);
 
 const init = () => {
 	socket.dispatch("users.getBasicUser", route.params.username, res => {

+ 5 - 5
frontend/src/pages/ResetPassword.vue

@@ -1,7 +1,8 @@
 <script setup lang="ts">
-import { useStore } from "vuex";
-import { defineAsyncComponent, ref, computed, watch, onMounted } from "vue";
+import { defineAsyncComponent, ref, watch, onMounted } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
+import { useUserAuthStore } from "@/stores/userAuth";
 import validation from "@/validation";
 import { useWebsocketsStore } from "@/stores/websockets";
 
@@ -13,9 +14,8 @@ const props = defineProps({
 	mode: { type: String, enum: ["reset", "set"], default: "reset" }
 });
 
-const store = useStore();
-
-const accountEmail = computed(() => store.state.user.auth.email);
+const userAuthStore = useUserAuthStore();
+const { email: accountEmail } = storeToRefs(userAuthStore);
 
 const { socket } = useWebsocketsStore();
 

+ 5 - 9
frontend/src/pages/Settings/Tabs/Account.vue

@@ -1,17 +1,12 @@
 <script setup lang="ts">
-import {
-	defineAsyncComponent,
-	ref,
-	watch,
-	reactive,
-	computed,
-	onMounted
-} from "vue";
+import { defineAsyncComponent, ref, watch, reactive, onMounted } from "vue";
 import { useStore } from "vuex";
 import { useRoute } from "vue-router";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useSettingsStore } from "@/stores/settings";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 import _validation from "@/validation";
 
 const InputHelpBox = defineAsyncComponent(
@@ -22,6 +17,7 @@ const SaveButton = defineAsyncComponent(
 );
 
 const settingsStore = useSettingsStore();
+const userAuthStore = useUserAuthStore();
 const store = useStore();
 const route = useRoute();
 
@@ -29,7 +25,7 @@ const { socket } = useWebsocketsStore();
 
 const saveButton = ref();
 
-const userId = computed(() => store.state.user.auth.userId);
+const { userId } = storeToRefs(userAuthStore);
 const { originalUser, modifiedUser } = settingsStore;
 
 const validation = reactive({

+ 11 - 17
frontend/src/pages/Settings/Tabs/Preferences.vue

@@ -1,17 +1,17 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, computed, onMounted } from "vue";
-import { useStore } from "vuex";
+import { defineAsyncComponent, ref, onMounted } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserPreferencesStore } from "@/stores/userPreferences";
 import ws from "@/ws";
 
 const SaveButton = defineAsyncComponent(
 	() => import("@/components/SaveButton.vue")
 );
 
-const store = useStore();
-
 const { socket } = useWebsocketsStore();
+const userPreferencesStore = useUserPreferencesStore();
 
 const saveButton = ref();
 
@@ -21,19 +21,13 @@ const localActivityLogPublic = ref(false);
 const localAnonymousSongRequests = ref(false);
 const localActivityWatch = ref(false);
 
-const nightmode = computed(() => store.state.user.preferences.nightmode);
-const autoSkipDisliked = computed(
-	() => store.state.user.preferences.autoSkipDisliked
-);
-const activityLogPublic = computed(
-	() => store.state.user.preferences.activityLogPublic
-);
-const anonymousSongRequests = computed(
-	() => store.state.user.preferences.anonymousSongRequests
-);
-const activityWatch = computed(
-	() => store.state.user.preferences.activityWatch
-);
+const {
+	nightmode,
+	autoSkipDisliked,
+	activityLogPublic,
+	anonymousSongRequests,
+	activityWatch
+} = storeToRefs(userPreferencesStore);
 
 const saveChanges = () => {
 	if (

+ 5 - 4
frontend/src/pages/Settings/Tabs/Profile.vue

@@ -1,9 +1,10 @@
 <script setup lang="ts">
-import { defineAsyncComponent, ref, computed } from "vue";
-import { useStore } from "vuex";
+import { defineAsyncComponent, ref } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useSettingsStore } from "@/stores/settings";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 import validation from "@/validation";
 
 const ProfilePicture = defineAsyncComponent(
@@ -14,13 +15,13 @@ const SaveButton = defineAsyncComponent(
 );
 
 const settingsStore = useSettingsStore();
-const store = useStore();
+const userAuthStore = useUserAuthStore();
 
 const { socket } = useWebsocketsStore();
 
 const saveButton = ref();
 
-const userId = computed(() => store.state.user.auth.userId);
+const { userId } = storeToRefs(userAuthStore);
 const { originalUser, modifiedUser } = settingsStore;
 
 const { updateOriginalUser } = settingsStore;

+ 5 - 11
frontend/src/pages/Settings/Tabs/Security.vue

@@ -1,16 +1,10 @@
 <script setup lang="ts">
-import {
-	defineAsyncComponent,
-	ref,
-	watch,
-	reactive,
-	computed,
-	onMounted
-} from "vue";
-import { useStore } from "vuex";
+import { defineAsyncComponent, ref, watch, reactive, onMounted } from "vue";
 import Toast from "toasters";
+import { storeToRefs } from "pinia";
 import { useSettingsStore } from "@/stores/settings";
 import { useWebsocketsStore } from "@/stores/websockets";
+import { useUserAuthStore } from "@/stores/userAuth";
 import _validation from "@/validation";
 
 const InputHelpBox = defineAsyncComponent(
@@ -18,7 +12,7 @@ const InputHelpBox = defineAsyncComponent(
 );
 
 const settingsStore = useSettingsStore();
-const store = useStore();
+const userAuthStore = useUserAuthStore();
 
 const { socket } = useWebsocketsStore();
 
@@ -46,7 +40,7 @@ const newPassword = ref();
 const oldPassword = ref();
 
 const { isPasswordLinked, isGithubLinked } = settingsStore;
-const userId = computed(() => store.state.user.auth.userId);
+const { userId } = storeToRefs(userAuthStore);
 
 const togglePasswordVisibility = refName => {
 	const ref = refName === "oldPassword" ? oldPassword : newPassword;

+ 4 - 7
frontend/src/pages/Station/Sidebar/index.vue

@@ -1,8 +1,8 @@
 <script setup lang="ts">
-import { useStore } from "vuex";
 import { useRoute } from "vue-router";
-import { defineAsyncComponent, computed, watch, onMounted } from "vue";
+import { defineAsyncComponent, watch, onMounted } from "vue";
 import { storeToRefs } from "pinia";
+import { useUserAuthStore } from "@/stores/userAuth";
 import { useStationStore } from "@/stores/station";
 import useTabQueryHandler from "@/composables/useTabQueryHandler";
 
@@ -12,16 +12,13 @@ const Users = defineAsyncComponent(
 );
 const Request = defineAsyncComponent(() => import("@/components/Request.vue"));
 
-const store = useStore();
 const route = useRoute();
+const userAuthStore = useUserAuthStore();
 const stationStore = useStationStore();
 
 const { tab, showTab } = useTabQueryHandler("queue");
 
-const userId = computed(() => store.state.user.auth.userId);
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const role = computed(() => store.state.user.auth.rol);
-
+const { loggedIn, userId, role } = storeToRefs(userAuthStore);
 const { station } = storeToRefs(stationStore);
 
 const isOwner = () =>

+ 6 - 8
frontend/src/pages/Station/index.vue

@@ -15,6 +15,8 @@ import { ContentLoader } from "vue-content-loader";
 import canAutoPlay from "can-autoplay";
 import { useWebsocketsStore } from "@/stores/websockets";
 import { useStationStore } from "@/stores/station";
+import { useUserAuthStore } from "@/stores/userAuth";
+import { useUserPreferencesStore } from "@/stores/userPreferences";
 import aw from "@/aw";
 import ms from "@/ms";
 import ws from "@/ws";
@@ -44,6 +46,8 @@ const router = useRouter();
 
 const { socket } = useWebsocketsStore();
 const stationStore = useStationStore();
+const userAuthStore = useUserAuthStore();
+const userPreferencesStore = useUserPreferencesStore();
 
 // TODO this might need a different place, like onMounted
 const isApple = ref(
@@ -96,14 +100,8 @@ const activeModals = computed(() => store.state.modalVisibility.activeModals);
 // TODO fix this if it still has some use, as this is no longer accurate
 // const video = computed(() => store.state.modals.editSong);
 
-const loggedIn = computed(() => store.state.user.auth.loggedIn);
-const userId = computed(() => store.state.user.auth.userId);
-const role = computed(() => store.state.user.auth.role);
-const nightmode = computed(() => store.state.user.preferences.nightmode);
-const autoSkipDisliked = computed(
-	() => store.state.user.preferences.autoSkipDisliked
-);
-
+const { loggedIn, userId, role } = storeToRefs(userAuthStore);
+const { nightmode, autoSkipDisliked } = storeToRefs(userPreferencesStore);
 const {
 	station,
 	currentSong,

+ 0 - 2
frontend/src/store/index.ts

@@ -1,7 +1,6 @@
 /* eslint-disable import/no-cycle */
 import { createStore } from "vuex";
 
-import user from "./modules/user";
 import modalVisibility from "./modules/modalVisibility";
 
 const emptyModule = {
@@ -10,7 +9,6 @@ const emptyModule = {
 
 export default createStore({
 	modules: {
-		user,
 		modalVisibility,
 		modals: {
 			namespaced: true,

+ 0 - 285
frontend/src/store/modules/user.ts

@@ -1,285 +0,0 @@
-/* eslint no-param-reassign: 0 */
-/* eslint-disable import/no-cycle */
-
-import validation from "@/validation";
-import ws from "@/ws";
-import auth from "@/api/auth";
-
-const state = {};
-const getters = {};
-const actions = {};
-const mutations = {};
-
-const modules = {
-	auth: {
-		namespaced: true,
-		state: {
-			userIdMap: {},
-			userIdRequested: {},
-			pendingUserIdCallbacks: {},
-			loggedIn: false,
-			role: "",
-			username: "",
-			email: "",
-			userId: "",
-			banned: false,
-			ban: {},
-			gotData: false
-		},
-		actions: {
-			/* eslint-disable-next-line */
-			register: ({ commit }, user) =>
-				new Promise((resolve, reject) => {
-					const { username, email, password } = user;
-
-					if (!email || !username || !password)
-						reject(new Error("Please fill in all fields"));
-					else if (!validation.isLength(email, 3, 254))
-						reject(
-							new Error(
-								"Email must have between 3 and 254 characters."
-							)
-						);
-					else if (
-						email.indexOf("@") !== email.lastIndexOf("@") ||
-						!validation.regex.emailSimple.test(email)
-					)
-						reject(new Error("Invalid email format."));
-					else if (!validation.isLength(username, 2, 32))
-						reject(
-							new Error(
-								"Username must have between 2 and 32 characters."
-							)
-						);
-					else if (!validation.regex.azAZ09_.test(username))
-						reject(
-							new Error(
-								"Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _."
-							)
-						);
-					else if (username.replaceAll(/[_]/g, "").length === 0)
-						reject(
-							new Error(
-								"Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _, and there has to be at least one letter or number."
-							)
-						);
-					else if (!validation.isLength(password, 6, 200))
-						reject(
-							new Error(
-								"Password must have between 6 and 200 characters."
-							)
-						);
-					else if (!validation.regex.password.test(password))
-						reject(
-							new Error(
-								"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character."
-							)
-						);
-					else
-						auth.register(user)
-							.then(res => resolve(res))
-							.catch(err => reject(new Error(err.message)));
-				}),
-			/* eslint-disable-next-line */
-			login: ({ commit }, user) =>
-				new Promise((resolve, reject) => {
-					auth.login(user)
-						.then(() => {
-							lofig.get("cookie.SIDname").then(sid => {
-								const bc = new BroadcastChannel(
-									`${sid}.user_login`
-								);
-								bc.postMessage(true);
-								bc.close();
-							});
-							resolve({
-								status: "success",
-								message: "Logged in!"
-							});
-						})
-						.catch(err => reject(new Error(err.message)));
-				}),
-			logout: () =>
-				new Promise<void>((resolve, reject) => {
-					auth.logout()
-						.then(() => resolve())
-						.catch(() => reject());
-				}),
-			getBasicUser: ({ commit, state }, userId) =>
-				new Promise(resolve => {
-					if (typeof state.userIdMap[`Z${userId}`] !== "string") {
-						if (state.userIdRequested[`Z${userId}`] !== true) {
-							commit("requestingUserId", userId);
-							ws.socket.dispatch(
-								"users.getBasicUser",
-								userId,
-								res => {
-									if (res.status === "success") {
-										const user = res.data;
-
-										commit("mapUserId", {
-											userId,
-											user: {
-												name: user.name,
-												username: user.username
-											}
-										});
-
-										state.pendingUserIdCallbacks[
-											`Z${userId}`
-										].forEach(cb => cb(user));
-
-										commit("clearPendingCallbacks", userId);
-
-										return resolve(user);
-									}
-									return resolve(null);
-								}
-							);
-						} else {
-							commit("pendingUser", {
-								userId,
-								callback: user => resolve(user)
-							});
-						}
-					} else {
-						resolve(state.userIdMap[`Z${userId}`]);
-					}
-				}),
-			authData: ({ commit }, data) => {
-				commit("authData", data);
-			},
-			banUser: ({ commit }, ban) => {
-				commit("banUser", ban);
-			},
-			updateUsername: ({ commit }, username) => {
-				commit("updateUsername", username);
-			}
-		},
-		mutations: {
-			mapUserId(state, data) {
-				state.userIdMap[`Z${data.userId}`] = data.user;
-				state.userIdRequested[`Z${data.userId}`] = false;
-			},
-			requestingUserId(state, userId) {
-				state.userIdRequested[`Z${userId}`] = true;
-				if (!state.pendingUserIdCallbacks[`Z${userId}`])
-					state.pendingUserIdCallbacks[`Z${userId}`] = [];
-			},
-			pendingUser(state, data) {
-				state.pendingUserIdCallbacks[`Z${data.userId}`].push(
-					data.callback
-				);
-			},
-			clearPendingCallbacks(state, userId) {
-				state.pendingUserIdCallbacks[`Z${userId}`] = [];
-			},
-			authData(state, data) {
-				state.loggedIn = data.loggedIn;
-				state.role = data.role;
-				state.username = data.username;
-				state.email = data.email;
-				state.userId = data.userId;
-				state.gotData = true;
-			},
-			banUser(state, ban) {
-				state.banned = true;
-				state.ban = ban;
-			},
-			updateUsername(state, username) {
-				state.username = username;
-			}
-		}
-	},
-	playlists: {
-		namespaced: true,
-		state: {
-			playlists: [],
-			fetchedPlaylists: false
-		},
-		actions: {
-			setPlaylists: ({ commit }, playlists) =>
-				commit("setPlaylists", playlists),
-			updatePlaylists: ({ commit }, playlists) =>
-				commit("updatePlaylists", playlists),
-			addPlaylist: ({ commit }, playlist) =>
-				commit("addPlaylist", playlist),
-			removePlaylist: ({ commit }, playlistId) =>
-				commit("removePlaylist", playlistId)
-		},
-		mutations: {
-			setPlaylists(state, playlists) {
-				state.fetchedPlaylists = true;
-				state.playlists = playlists;
-			},
-			updatePlaylists(state, playlists) {
-				state.playlists = playlists;
-			},
-			addPlaylist(state, playlist) {
-				state.playlists.push(playlist);
-			},
-			removePlaylist(state, playlistId) {
-				state.playlists.forEach((playlist, index) => {
-					if (playlist._id === playlistId)
-						state.playlists.splice(index, 1);
-				});
-			}
-		}
-	},
-	preferences: {
-		namespaced: true,
-		state: {
-			nightmode: false,
-			autoSkipDisliked: true,
-			activityLogPublic: false,
-			anonymousSongRequests: false,
-			activityWatch: false
-		},
-		actions: {
-			changeNightmode: ({ commit }, nightmode) => {
-				commit("changeNightmode", nightmode);
-			},
-			changeAutoSkipDisliked: ({ commit }, autoSkipDisliked) => {
-				commit("changeAutoSkipDisliked", autoSkipDisliked);
-			},
-			changeActivityLogPublic: ({ commit }, activityLogPublic) => {
-				commit("changeActivityLogPublic", activityLogPublic);
-			},
-			changeAnonymousSongRequests: (
-				{ commit },
-				anonymousSongRequests
-			) => {
-				commit("changeAnonymousSongRequests", anonymousSongRequests);
-			},
-			changeActivityWatch: ({ commit }, activityWatch) => {
-				commit("changeActivityWatch", activityWatch);
-			}
-		},
-		mutations: {
-			changeNightmode(state, nightmode) {
-				state.nightmode = nightmode;
-			},
-			changeAutoSkipDisliked(state, autoSkipDisliked) {
-				state.autoSkipDisliked = autoSkipDisliked;
-			},
-			changeActivityLogPublic(state, activityLogPublic) {
-				state.activityLogPublic = activityLogPublic;
-			},
-			changeAnonymousSongRequests(state, anonymousSongRequests) {
-				state.anonymousSongRequests = anonymousSongRequests;
-			},
-			changeActivityWatch(state, activityWatch) {
-				state.activityWatch = activityWatch;
-			}
-		}
-	}
-};
-
-export default {
-	namespaced: true,
-	state,
-	getters,
-	actions,
-	mutations,
-	modules
-};

+ 175 - 0
frontend/src/stores/userAuth.ts

@@ -0,0 +1,175 @@
+import { defineStore } from "pinia";
+import validation from "@/validation";
+import ws from "@/ws";
+import auth from "@/api/auth";
+
+// TODO fix/decide eslint rule properly
+// eslint-disable-next-line
+export const useUserAuthStore = defineStore("userAuth", {
+	state: () => ({
+		userIdMap: {},
+		userIdRequested: {},
+		pendingUserIdCallbacks: {},
+		loggedIn: false,
+		role: "",
+		username: "",
+		email: "",
+		userId: "",
+		banned: false,
+		ban: {},
+		gotData: false
+	}),
+	actions: {
+		register(user) {
+			return new Promise((resolve, reject) => {
+				const { username, email, password } = user;
+
+				if (!email || !username || !password)
+					reject(new Error("Please fill in all fields"));
+				else if (!validation.isLength(email, 3, 254))
+					reject(
+						new Error(
+							"Email must have between 3 and 254 characters."
+						)
+					);
+				else if (
+					email.indexOf("@") !== email.lastIndexOf("@") ||
+					!validation.regex.emailSimple.test(email)
+				)
+					reject(new Error("Invalid email format."));
+				else if (!validation.isLength(username, 2, 32))
+					reject(
+						new Error(
+							"Username must have between 2 and 32 characters."
+						)
+					);
+				else if (!validation.regex.azAZ09_.test(username))
+					reject(
+						new Error(
+							"Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _."
+						)
+					);
+				else if (username.replaceAll(/[_]/g, "").length === 0)
+					reject(
+						new Error(
+							"Invalid username format. Allowed characters: a-z, A-Z, 0-9 and _, and there has to be at least one letter or number."
+						)
+					);
+				else if (!validation.isLength(password, 6, 200))
+					reject(
+						new Error(
+							"Password must have between 6 and 200 characters."
+						)
+					);
+				else if (!validation.regex.password.test(password))
+					reject(
+						new Error(
+							"Invalid password format. Must have one lowercase letter, one uppercase letter, one number and one special character."
+						)
+					);
+				else
+					auth.register(user)
+						.then(res => resolve(res))
+						.catch(err => reject(new Error(err.message)));
+			});
+		},
+		login(user) {
+			return new Promise((resolve, reject) => {
+				auth.login(user)
+					.then(() => {
+						lofig.get("cookie.SIDname").then(sid => {
+							const bc = new BroadcastChannel(
+								`${sid}.user_login`
+							);
+							bc.postMessage(true);
+							bc.close();
+						});
+						resolve({
+							status: "success",
+							message: "Logged in!"
+						});
+					})
+					.catch(err => reject(new Error(err.message)));
+			});
+		},
+		logout() {
+			return new Promise<void>((resolve, reject) => {
+				auth.logout()
+					.then(() => resolve())
+					.catch(() => reject());
+			});
+		},
+		getBasicUser(userId) {
+			return new Promise(resolve => {
+				if (typeof this.userIdMap[`Z${userId}`] !== "string") {
+					if (this.userIdRequested[`Z${userId}`] !== true) {
+						this.requestingUserId(userId);
+						ws.socket.dispatch(
+							"users.getBasicUser",
+							userId,
+							res => {
+								if (res.status === "success") {
+									const user = res.data;
+
+									this.mapUserId({
+										userId,
+										user: {
+											name: user.name,
+											username: user.username
+										}
+									});
+
+									this.pendingUserIdCallbacks[
+										`Z${userId}`
+									].forEach(cb => cb(user));
+
+									this.clearPendingCallbacks(userId);
+
+									return resolve(user);
+								}
+								return resolve(null);
+							}
+						);
+					} else {
+						this.pendingUser({
+							userId,
+							callback: user => resolve(user)
+						});
+					}
+				} else {
+					resolve(this.userIdMap[`Z${userId}`]);
+				}
+			});
+		},
+		mapUserId(data) {
+			this.userIdMap[`Z${data.userId}`] = data.user;
+			this.userIdRequested[`Z${data.userId}`] = false;
+		},
+		requestingUserId(userId) {
+			this.userIdRequested[`Z${userId}`] = true;
+			if (!this.pendingUserIdCallbacks[`Z${userId}`])
+				this.pendingUserIdCallbacks[`Z${userId}`] = [];
+		},
+		pendingUser(data) {
+			this.pendingUserIdCallbacks[`Z${data.userId}`].push(data.callback);
+		},
+		clearPendingCallbacks(userId) {
+			this.pendingUserIdCallbacks[`Z${userId}`] = [];
+		},
+		authData(data) {
+			this.loggedIn = data.loggedIn;
+			this.role = data.role;
+			this.username = data.username;
+			this.email = data.email;
+			this.userId = data.userId;
+			this.gotData = true;
+		},
+		banUser(ban) {
+			this.banned = true;
+			this.ban = ban;
+		},
+		updateUsername(username) {
+			this.username = username;
+		}
+	}
+});

+ 28 - 0
frontend/src/stores/userPlaylists.ts

@@ -0,0 +1,28 @@
+import { defineStore } from "pinia";
+
+// TODO fix/decide eslint rule properly
+// eslint-disable-next-line
+export const useUserPlaylistsStore = defineStore("userPlaylists", {
+	state: () => ({
+		playlists: [],
+		fetchedPlaylists: false
+	}),
+	actions: {
+		setPlaylists(playlists) {
+			this.fetchedPlaylists = true;
+			this.playlists = playlists;
+		},
+		updatePlaylists(playlists) {
+			this.playlists = playlists;
+		},
+		addPlaylist(playlist) {
+			this.playlists.push(playlist);
+		},
+		removePlaylist(playlistId) {
+			this.playlists.forEach((playlist, index) => {
+				if (playlist._id === playlistId)
+					this.playlists.splice(index, 1);
+			});
+		}
+	}
+});

+ 30 - 0
frontend/src/stores/userPreferences.ts

@@ -0,0 +1,30 @@
+import { defineStore } from "pinia";
+
+// TODO fix/decide eslint rule properly
+// eslint-disable-next-line
+export const useUserPreferencesStore = defineStore("userPreferences", {
+	state: () => ({
+		nightmode: false,
+		autoSkipDisliked: true,
+		activityLogPublic: false,
+		anonymousSongRequests: false,
+		activityWatch: false
+	}),
+	actions: {
+		changeNightmode(nightmode) {
+			this.nightmode = nightmode;
+		},
+		changeAutoSkipDisliked(autoSkipDisliked) {
+			this.autoSkipDisliked = autoSkipDisliked;
+		},
+		changeActivityLogPublic(activityLogPublic) {
+			this.activityLogPublic = activityLogPublic;
+		},
+		changeAnonymousSongRequests(anonymousSongRequests) {
+			this.anonymousSongRequests = anonymousSongRequests;
+		},
+		changeActivityWatch(activityWatch) {
+			this.activityWatch = activityWatch;
+		}
+	}
+});

+ 3 - 1
frontend/src/stores/websockets.ts

@@ -30,6 +30,8 @@ export const useWebsocketsStore = defineStore("websockets", {
 		}
 	},
 	getters: {
-		getSocket: state => state.socket
+		getSocket() {
+			return this.socket;
+		}
 	}
 });