Refactor code structure and remove redundant sections for improved readability and maintainability
|
After Width: | Height: | Size: 321 KiB |
@@ -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>
|
||||
|
||||
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 161 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||