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">
|
<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>
|
||||||
|
|||||||
|
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 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);
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,7 +91,10 @@ async function fetchPlaylists() {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openCreatePlaylist() {
|
function openCreatePlaylist() {
|
||||||
|
requireAuth({
|
||||||
|
redirectTo: '/login',
|
||||||
|
onAuthenticated: async () => {
|
||||||
const name = prompt('Playlist name:')
|
const name = prompt('Playlist name:')
|
||||||
if (!name?.trim()) return
|
if (!name?.trim()) return
|
||||||
try {
|
try {
|
||||||
@@ -98,6 +104,8 @@ async function openCreatePlaylist() {
|
|||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(fetchPlaylists)
|
onMounted(fetchPlaylists)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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,15 +111,21 @@ 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)
|
||||||
|
requireAuth({
|
||||||
|
redirectTo: '/login',
|
||||||
|
onAuthenticated: async () => {
|
||||||
await authStore.toggleLike(track._id)
|
await authStore.toggleLike(track._id)
|
||||||
if (wasLiked) {
|
if (wasLiked) {
|
||||||
likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id)
|
likedTracks.value = likedTracks.value.filter((t) => t._id !== track._id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
if (authStore.isLoggedIn) {
|
||||||
try {
|
try {
|
||||||
const { data: liked } = await api.get('/tracks/liked')
|
const { data: liked } = await api.get('/tracks/liked')
|
||||||
if (liked.success) {
|
if (liked.success) {
|
||||||
@@ -121,9 +133,9 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
loading.value = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: all } = await api.get('/tracks')
|
const { data: all } = await api.get('/tracks')
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||