Refactor code structure and remove redundant sections for improved readability and maintainability

This commit is contained in:
2026-05-26 20:21:02 +07:00
parent 8df58d74de
commit c0be5d76d6
31 changed files with 97 additions and 52 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

+2 -2
View File
@@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Noctune - Music That Hits Different</title> <title>Noctune - Music That Hits Different</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<script type="module" crossorigin src="/assets/index-C2ZrghzO.js"></script> <script type="module" crossorigin src="/assets/index-Ulrd01cQ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-eqvJxEw1.css"> <link rel="stylesheet" crossorigin href="/assets/index-B-WaF5DI.css">
</head> </head>
<body class="bg-noctune-cream"> <body class="bg-noctune-cream">
<div id="app"></div> <div id="app"></div>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+1 -3
View File
@@ -52,9 +52,7 @@ const authStore = useAuthStore();
const uiStore = useUiStore(); const uiStore = useUiStore();
const playerStore = usePlayerStore(); const playerStore = usePlayerStore();
const isAuthPage = computed(() => { const isAuthPage = computed(() => route.meta?.guest === true)
return route.meta?.guest === true || route.path === '/register';
});
const hasPlayer = computed(() => !!playerStore.currentTrack); const hasPlayer = computed(() => !!playerStore.currentTrack);
+15 -3
View File
@@ -41,14 +41,14 @@
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/> <path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg> </svg>
</button> </button>
<router-link to="/profile" class="text-black hover:opacity-80"> <button @click="goToProfile" class="text-black hover:opacity-80">
<div class="w-8 h-8 rounded-full bg-noctune-dark overflow-hidden border-2 border-black"> <div class="w-8 h-8 rounded-full bg-noctune-dark overflow-hidden border-2 border-black">
<img v-if="authStore.user?.avatarUrl" :src="formatUrl(authStore.user.avatarUrl)" class="w-full h-full object-cover" /> <img v-if="authStore.user?.avatarUrl" :src="formatUrl(authStore.user.avatarUrl)" class="w-full h-full object-cover" />
<div v-else class="w-full h-full flex items-center justify-center"> <div v-else class="w-full h-full flex items-center justify-center">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg> <svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
</div> </div>
</div> </div>
</router-link> </button>
</div> </div>
</header> </header>
</template> </template>
@@ -61,11 +61,13 @@ import { usePlayerStore } from '../stores/playerStore.js'
import { useQueueStore } from '../stores/queueStore.js' import { useQueueStore } from '../stores/queueStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const { requireAuth } = useAuthGate()
const query = ref('') const query = ref('')
const searchResults = ref(null) const searchResults = ref(null)
@@ -100,6 +102,16 @@ function playSearchTrack(track) {
} }
function toggleLikeRedirect() { function toggleLikeRedirect() {
router.push('/library') requireAuth({
redirectTo: '/login',
onAuthenticated: () => router.push('/library')
})
}
function goToProfile() {
requireAuth({
redirectTo: '/login',
onAuthenticated: () => router.push('/profile')
})
} }
</script> </script>
+7 -2
View File
@@ -60,10 +60,12 @@ import { usePlayerStore } from '../stores/playerStore.js'
import { useQueueStore } from '../stores/queueStore.js' import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const isLiked = ref(false) const isLiked = ref(false)
@@ -105,10 +107,13 @@ function playPrev() {
} }
} }
async function toggleLike() { function toggleLike() {
if (!playerStore.currentTrack) return if (!playerStore.currentTrack) return
const id = playerStore.currentTrack._id || playerStore.currentTrack.id const id = playerStore.currentTrack._id || playerStore.currentTrack.id
await authStore.toggleLike(id) requireAuth({
redirectTo: '/login',
onAuthenticated: () => authStore.toggleLike(id)
})
} }
// Sync isLiked ref reactively from authStore // Sync isLiked ref reactively from authStore
+16 -8
View File
@@ -70,6 +70,7 @@ import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -77,6 +78,8 @@ const authStore = useAuthStore()
const playlists = ref([]) const playlists = ref([])
const { requireAuth } = useAuthGate()
const isActive = (path) => route.path === path const isActive = (path) => route.path === path
const isActivePrefix = (prefix) => route.path.startsWith(prefix) const isActivePrefix = (prefix) => route.path.startsWith(prefix)
@@ -88,15 +91,20 @@ async function fetchPlaylists() {
} catch {} } catch {}
} }
async function openCreatePlaylist() { function openCreatePlaylist() {
const name = prompt('Playlist name:') requireAuth({
if (!name?.trim()) return redirectTo: '/login',
try { onAuthenticated: async () => {
const { data } = await api.post('/playlists', { name: name.trim() }) const name = prompt('Playlist name:')
if (data.success) { if (!name?.trim()) return
playlists.value.push(data.data) try {
const { data } = await api.post('/playlists', { name: name.trim() })
if (data.success) {
playlists.value.push(data.data)
}
} catch {}
} }
} catch {} })
} }
onMounted(fetchPlaylists) onMounted(fetchPlaylists)
+7 -4
View File
@@ -1,16 +1,19 @@
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import router from '../router/index.js'
let pendingAction = null let pendingAction = null
export function useAuthGate() { export function useAuthGate() {
const authStore = useAuthStore() const authStore = useAuthStore()
async function requireAuth({ message, onAuthenticated }) { async function requireAuth({ message, onAuthenticated, redirectTo }) {
if (authStore.isLoggedIn) { if (authStore.isLoggedIn) {
onAuthenticated() onAuthenticated?.()
} else { } else {
pendingAction = onAuthenticated pendingAction = onAuthenticated || null
if (message) { if (redirectTo) {
router.push(redirectTo)
} else if (message) {
const { useUiStore } = await import('../stores/uiStore.js') const { useUiStore } = await import('../stores/uiStore.js')
const uiStore = useUiStore() const uiStore = useUiStore()
uiStore.showToast(message, 'info') uiStore.showToast(message, 'info')
+8 -11
View File
@@ -10,14 +10,15 @@ import ProfilePage from '../views/ProfilePage.vue'
import AdminPage from '../views/AdminPage.vue' import AdminPage from '../views/AdminPage.vue'
const routes = [ const routes = [
{ path: '/', name: 'Login', component: LoginPage, meta: { guest: true } }, { path: '/', redirect: '/home' },
{ path: '/login', name: 'Login', component: LoginPage, meta: { guest: true } },
{ path: '/register', name: 'Register', component: RegisterPage, meta: { guest: true } }, { path: '/register', name: 'Register', component: RegisterPage, meta: { guest: true } },
{ path: '/home', name: 'Home', component: LandingPage, meta: { auth: true } }, { path: '/home', name: 'Home', component: LandingPage },
{ path: '/library', name: 'Library', component: LibraryPage, meta: { auth: true } }, { path: '/library', name: 'Library', component: LibraryPage },
{ path: '/playlist', name: 'Playlist', component: PlaylistPage, meta: { auth: true } }, { path: '/playlist', name: 'Playlist', component: PlaylistPage },
{ path: '/playlist/:id', name: 'PlaylistDetail', component: PlaylistPage, meta: { auth: true } }, { path: '/playlist/:id', name: 'PlaylistDetail', component: PlaylistPage },
{ path: '/profile', name: 'Profile', component: ProfilePage, meta: { auth: true } }, { path: '/profile', name: 'Profile', component: ProfilePage },
{ path: '/admin/tracks', name: 'AdminTracks', component: AdminPage, meta: { auth: true, admin: true } }, { path: '/admin/tracks', name: 'AdminTracks', component: AdminPage, meta: { admin: true } },
] ]
const router = createRouter({ const router = createRouter({
@@ -32,10 +33,6 @@ router.beforeEach((to, from, next) => {
return next('/home') return next('/home')
} }
if (to.meta.auth && !authStore.isLoggedIn) {
return next('/')
}
if (to.meta.guest && authStore.isLoggedIn) { if (to.meta.guest && authStore.isLoggedIn) {
return next('/home') return next('/home')
} }
+7 -2
View File
@@ -62,10 +62,12 @@ import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const tracks = ref([]) const tracks = ref([])
const loading = ref(true) const loading = ref(true)
@@ -93,8 +95,11 @@ function playFirstTrack() {
if (tracks.value.length) playTrack(tracks.value[0]) if (tracks.value.length) playTrack(tracks.value[0])
} }
async function toggleLike(track) { function toggleLike(track) {
await authStore.toggleLike(track._id) requireAuth({
redirectTo: '/login',
onAuthenticated: () => authStore.toggleLike(track._id)
})
} }
onMounted(async () => { onMounted(async () => {
+25 -13
View File
@@ -3,6 +3,10 @@
<section class="mb-12"> <section class="mb-12">
<h2 class="text-3xl font-black text-black mb-6">Liked Tracks</h2> <h2 class="text-3xl font-black text-black mb-6">Liked Tracks</h2>
<div v-if="loading" class="text-center py-12 text-gray-500">Loading...</div> <div v-if="loading" class="text-center py-12 text-gray-500">Loading...</div>
<div v-else-if="!authStore.isLoggedIn" class="text-center py-12">
<p class="text-gray-500 mb-4">Login to see your liked tracks</p>
<router-link to="/login" class="bg-noctune-yellow border-2 border-black px-6 py-3 font-bold inline-block hover:bg-yellow-400 transition-colors rounded-lg">Login</router-link>
</div>
<div v-else-if="likedTracks.length === 0" class="text-center py-12"> <div v-else-if="likedTracks.length === 0" class="text-center py-12">
<p class="text-gray-500 mb-4">No liked tracks yet</p> <p class="text-gray-500 mb-4">No liked tracks yet</p>
<router-link to="/home" class="bg-noctune-yellow border-2 border-black px-6 py-3 font-bold inline-block hover:bg-yellow-400 transition-colors rounded-lg">Discover Music</router-link> <router-link to="/home" class="bg-noctune-yellow border-2 border-black px-6 py-3 font-bold inline-block hover:bg-yellow-400 transition-colors rounded-lg">Discover Music</router-link>
@@ -75,10 +79,12 @@ import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore() const playerStore = usePlayerStore()
const queueStore = useQueueStore() const queueStore = useQueueStore()
const authStore = useAuthStore() const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const likedTracks = ref([]) const likedTracks = ref([])
const allTracks = ref([]) const allTracks = ref([])
@@ -105,25 +111,31 @@ function playTrack(track, fromLiked = false, idx) {
playerStore.play() playerStore.play()
} }
async function toggleLike(track) { function toggleLike(track) {
const wasLiked = isLiked(track) const wasLiked = isLiked(track)
await authStore.toggleLike(track._id) requireAuth({
if (wasLiked) { redirectTo: '/login',
likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id) onAuthenticated: async () => {
} await authStore.toggleLike(track._id)
if (wasLiked) {
likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id)
}
}
})
} }
onMounted(async () => { onMounted(async () => {
try { if (authStore.isLoggedIn) {
const { data: liked } = await api.get('/tracks/liked') try {
if (liked.success) { const { data: liked } = await api.get('/tracks/liked')
likedTracks.value = liked.data || [] if (liked.success) {
likedTracks.value = liked.data || []
}
} catch {
// ignore
} }
} catch {
// ignore
} finally {
loading.value = false
} }
loading.value = false
try { try {
const { data: all } = await api.get('/tracks') const { data: all } = await api.get('/tracks')
+6 -1
View File
@@ -77,6 +77,7 @@ import { useRouter } from 'vue-router'
import { useAuthStore } from '../stores/authStore.js' import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js' import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js' import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
@@ -138,10 +139,14 @@ async function handleUpdate() {
async function handleLogout() { async function handleLogout() {
await authStore.logout() await authStore.logout()
router.push('/') router.push('/login')
} }
onMounted(async () => { onMounted(async () => {
if (!authStore.isLoggedIn) {
router.push('/login')
return
}
try { try {
const { data } = await api.get('/tracks/liked') const { data } = await api.get('/tracks/liked')
if (data.success) { if (data.success) {
+1 -1
View File
@@ -47,7 +47,7 @@
</button> </button>
<p class="text-center mt-6 text-gray-600"> <p class="text-center mt-6 text-gray-600">
Already have an account? Already have an account?
<router-link to="/" class="text-noctune-orange font-bold hover:underline">Login</router-link> <router-link to="/login" class="text-noctune-orange font-bold hover:underline">Login</router-link>
</p> </p>
</div> </div>
</div> </div>