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">
<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">
<script type="module" crossorigin src="/assets/index-C2ZrghzO.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-eqvJxEw1.css">
<script type="module" crossorigin src="/assets/index-Ulrd01cQ.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B-WaF5DI.css">
</head>
<body class="bg-noctune-cream">
<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 playerStore = usePlayerStore();
const isAuthPage = computed(() => {
return route.meta?.guest === true || route.path === '/register';
});
const isAuthPage = computed(() => route.meta?.guest === true)
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"/>
</svg>
</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">
<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">
<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>
</router-link>
</button>
</div>
</header>
</template>
@@ -61,11 +61,13 @@ import { usePlayerStore } from '../stores/playerStore.js'
import { useQueueStore } from '../stores/queueStore.js'
import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const router = useRouter()
const authStore = useAuthStore()
const playerStore = usePlayerStore()
const queueStore = useQueueStore()
const { requireAuth } = useAuthGate()
const query = ref('')
const searchResults = ref(null)
@@ -100,6 +102,16 @@ function playSearchTrack(track) {
}
function toggleLikeRedirect() {
router.push('/library')
requireAuth({
redirectTo: '/login',
onAuthenticated: () => router.push('/library')
})
}
function goToProfile() {
requireAuth({
redirectTo: '/login',
onAuthenticated: () => router.push('/profile')
})
}
</script>
+7 -2
View File
@@ -60,10 +60,12 @@ import { usePlayerStore } from '../stores/playerStore.js'
import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js'
import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore()
const queueStore = useQueueStore()
const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const isLiked = ref(false)
@@ -105,10 +107,13 @@ function playPrev() {
}
}
async function toggleLike() {
function toggleLike() {
if (!playerStore.currentTrack) return
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
+16 -8
View File
@@ -70,6 +70,7 @@ import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const route = useRoute()
const router = useRouter()
@@ -77,6 +78,8 @@ const authStore = useAuthStore()
const playlists = ref([])
const { requireAuth } = useAuthGate()
const isActive = (path) => route.path === path
const isActivePrefix = (prefix) => route.path.startsWith(prefix)
@@ -88,15 +91,20 @@ async function fetchPlaylists() {
} catch {}
}
async function openCreatePlaylist() {
const name = prompt('Playlist name:')
if (!name?.trim()) return
try {
const { data } = await api.post('/playlists', { name: name.trim() })
if (data.success) {
playlists.value.push(data.data)
function openCreatePlaylist() {
requireAuth({
redirectTo: '/login',
onAuthenticated: async () => {
const name = prompt('Playlist name:')
if (!name?.trim()) return
try {
const { data } = await api.post('/playlists', { name: name.trim() })
if (data.success) {
playlists.value.push(data.data)
}
} catch {}
}
} catch {}
})
}
onMounted(fetchPlaylists)
+7 -4
View File
@@ -1,16 +1,19 @@
import { useAuthStore } from '../stores/authStore.js'
import router from '../router/index.js'
let pendingAction = null
export function useAuthGate() {
const authStore = useAuthStore()
async function requireAuth({ message, onAuthenticated }) {
async function requireAuth({ message, onAuthenticated, redirectTo }) {
if (authStore.isLoggedIn) {
onAuthenticated()
onAuthenticated?.()
} else {
pendingAction = onAuthenticated
if (message) {
pendingAction = onAuthenticated || null
if (redirectTo) {
router.push(redirectTo)
} else if (message) {
const { useUiStore } = await import('../stores/uiStore.js')
const uiStore = useUiStore()
uiStore.showToast(message, 'info')
+8 -11
View File
@@ -10,14 +10,15 @@ import ProfilePage from '../views/ProfilePage.vue'
import AdminPage from '../views/AdminPage.vue'
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: '/home', name: 'Home', component: LandingPage, meta: { auth: true } },
{ path: '/library', name: 'Library', component: LibraryPage, meta: { auth: true } },
{ path: '/playlist', name: 'Playlist', component: PlaylistPage, meta: { auth: true } },
{ path: '/playlist/:id', name: 'PlaylistDetail', component: PlaylistPage, meta: { auth: true } },
{ path: '/profile', name: 'Profile', component: ProfilePage, meta: { auth: true } },
{ path: '/admin/tracks', name: 'AdminTracks', component: AdminPage, meta: { auth: true, admin: true } },
{ path: '/home', name: 'Home', component: LandingPage },
{ path: '/library', name: 'Library', component: LibraryPage },
{ path: '/playlist', name: 'Playlist', component: PlaylistPage },
{ path: '/playlist/:id', name: 'PlaylistDetail', component: PlaylistPage },
{ path: '/profile', name: 'Profile', component: ProfilePage },
{ path: '/admin/tracks', name: 'AdminTracks', component: AdminPage, meta: { admin: true } },
]
const router = createRouter({
@@ -32,10 +33,6 @@ router.beforeEach((to, from, next) => {
return next('/home')
}
if (to.meta.auth && !authStore.isLoggedIn) {
return next('/')
}
if (to.meta.guest && authStore.isLoggedIn) {
return next('/home')
}
+7 -2
View File
@@ -62,10 +62,12 @@ import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore()
const queueStore = useQueueStore()
const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const tracks = ref([])
const loading = ref(true)
@@ -93,8 +95,11 @@ function playFirstTrack() {
if (tracks.value.length) playTrack(tracks.value[0])
}
async function toggleLike(track) {
await authStore.toggleLike(track._id)
function toggleLike(track) {
requireAuth({
redirectTo: '/login',
onAuthenticated: () => authStore.toggleLike(track._id)
})
}
onMounted(async () => {
+25 -13
View File
@@ -3,6 +3,10 @@
<section class="mb-12">
<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-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">
<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>
@@ -75,10 +79,12 @@ import { useQueueStore } from '../stores/queueStore.js'
import { useAuthStore } from '../stores/authStore.js'
import api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const playerStore = usePlayerStore()
const queueStore = useQueueStore()
const authStore = useAuthStore()
const { requireAuth } = useAuthGate()
const likedTracks = ref([])
const allTracks = ref([])
@@ -105,25 +111,31 @@ function playTrack(track, fromLiked = false, idx) {
playerStore.play()
}
async function toggleLike(track) {
function toggleLike(track) {
const wasLiked = isLiked(track)
await authStore.toggleLike(track._id)
if (wasLiked) {
likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id)
}
requireAuth({
redirectTo: '/login',
onAuthenticated: async () => {
await authStore.toggleLike(track._id)
if (wasLiked) {
likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id)
}
}
})
}
onMounted(async () => {
try {
const { data: liked } = await api.get('/tracks/liked')
if (liked.success) {
likedTracks.value = liked.data || []
if (authStore.isLoggedIn) {
try {
const { data: liked } = await api.get('/tracks/liked')
if (liked.success) {
likedTracks.value = liked.data || []
}
} catch {
// ignore
}
} catch {
// ignore
} finally {
loading.value = false
}
loading.value = false
try {
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 api from '../lib/api.js'
import { formatUrl } from '../lib/utils.js'
import { useAuthGate } from '../composables/useAuthGate.js'
const router = useRouter()
const authStore = useAuthStore()
@@ -138,10 +139,14 @@ async function handleUpdate() {
async function handleLogout() {
await authStore.logout()
router.push('/')
router.push('/login')
}
onMounted(async () => {
if (!authStore.isLoggedIn) {
router.push('/login')
return
}
try {
const { data } = await api.get('/tracks/liked')
if (data.success) {
+1 -1
View File
@@ -47,7 +47,7 @@
</button>
<p class="text-center mt-6 text-gray-600">
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>
</div>
</div>