250 lines
7.6 KiB
Vue
250 lines
7.6 KiB
Vue
<script setup lang="ts">
|
|
definePageMeta({
|
|
middleware: 'auth'
|
|
})
|
|
|
|
interface Review {
|
|
song_id: number
|
|
user_id: number
|
|
score_song: number
|
|
score_show: number
|
|
score_costume: number
|
|
text_review?: string
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
interface Artist {
|
|
id: number
|
|
artist: string
|
|
title: string
|
|
img: string
|
|
lyrics_original: string
|
|
lyrics_translation_fi?: string
|
|
tags: string[]
|
|
}
|
|
|
|
interface User {
|
|
id: number
|
|
[key: string]: any
|
|
}
|
|
|
|
// Config and route setup
|
|
const config = useRuntimeConfig()
|
|
const apiBase = config.public.apiBase
|
|
const route = useRoute()
|
|
const { id } = route.params
|
|
|
|
// Reactive state
|
|
const artist = ref<Artist | null>(null)
|
|
const user = ref<User | null>(null)
|
|
const review = ref<Review | null>(null)
|
|
|
|
// Form state with default values
|
|
const song = ref(0)
|
|
const show = ref(0)
|
|
const costume = ref(0)
|
|
const textReview = ref('')
|
|
const isSubmitting = ref(false)
|
|
|
|
// UI state
|
|
const showOriginalLyrics = ref(false)
|
|
const showTranslationLyrics = ref(false)
|
|
|
|
// Toggle functions for lyrics display
|
|
function toggleOriginalLyrics() {
|
|
showOriginalLyrics.value = !showOriginalLyrics.value
|
|
}
|
|
|
|
function toggleTranslationLyrics() {
|
|
showTranslationLyrics.value = !showTranslationLyrics.value
|
|
}
|
|
|
|
// Load user data from localStorage
|
|
function loadUserData(): User | null {
|
|
if (!process.client) return null
|
|
|
|
const userData = localStorage.getItem('user')
|
|
return userData ? JSON.parse(userData) : null
|
|
}
|
|
|
|
// Fetch artist data
|
|
async function fetchArtist() {
|
|
try {
|
|
const response = await fetch(`${apiBase}/songs/${id}`)
|
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`)
|
|
|
|
artist.value = await response.json()
|
|
} catch (error) {
|
|
console.error('Error fetching artist data:', error)
|
|
}
|
|
}
|
|
|
|
// Fetch user's existing review for this song
|
|
async function fetchUserReview(userId: number) {
|
|
try {
|
|
const response = await fetch(`${apiBase}/reviews/?song_id=${id}&user_id=${userId}`)
|
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`)
|
|
|
|
const reviewData = await response.json()
|
|
review.value = reviewData
|
|
|
|
// Pre-fill form with existing review data
|
|
if (reviewData) {
|
|
song.value = reviewData.score_song || 0
|
|
show.value = reviewData.score_show || 0
|
|
costume.value = reviewData.score_costume || 0
|
|
textReview.value = reviewData.text_review || ''
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching review data:', error)
|
|
}
|
|
}
|
|
|
|
// Submit review to the API
|
|
async function submitReview() {
|
|
if (!user.value?.id) {
|
|
console.error('User not authenticated')
|
|
return
|
|
}
|
|
|
|
isSubmitting.value = true
|
|
|
|
const payload: Review = {
|
|
song_id: Number(id),
|
|
user_id: user.value.id,
|
|
score_song: song.value,
|
|
score_show: show.value,
|
|
score_costume: costume.value,
|
|
text_review: textReview.value
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${apiBase}/reviews/`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(payload),
|
|
})
|
|
|
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`)
|
|
|
|
console.log('Review submitted successfully')
|
|
// You could add a success notification or redirect here
|
|
} catch (error) {
|
|
console.error('Error submitting review:', error)
|
|
// You could add an error notification here
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
// Initialize data on component mount
|
|
onMounted(async () => {
|
|
// Get user from localStorage
|
|
user.value = loadUserData()
|
|
|
|
// Fetch artist data
|
|
await fetchArtist()
|
|
|
|
// If user is logged in, fetch their review for this song
|
|
if (user.value?.id) {
|
|
await fetchUserReview(user.value.id)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="artist" class="space-y-4">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-center">{{ artist.artist }}</h1>
|
|
<p class="text-center italic">{{ artist.title }}</p>
|
|
</div>
|
|
<img class="w-1/2 mx-auto" :src="artist.img" alt="">
|
|
<div>
|
|
<h2
|
|
@click="toggleOriginalLyrics"
|
|
class="cursor-pointer hover:text-blue-600 flex items-center"
|
|
>
|
|
Sanat
|
|
<span class="ml-2 text-sm">
|
|
{{ showOriginalLyrics ? '▼' : '►' }}
|
|
</span>
|
|
</h2>
|
|
<pre v-show="showOriginalLyrics" class="lyricsFont transition-all duration-300">{{ artist.lyrics_original }}</pre>
|
|
</div>
|
|
<div v-if="artist.lyrics_translation_fi">
|
|
<h2
|
|
@click="toggleTranslationLyrics"
|
|
class="cursor-pointer hover:text-blue-600 flex items-center"
|
|
>
|
|
Käännös
|
|
<span class="ml-2 text-sm">
|
|
{{ showTranslationLyrics ? '▼' : '►' }}
|
|
</span>
|
|
</h2>
|
|
<pre v-show="showTranslationLyrics" class="lyricsFont transition-all duration-300">{{ artist.lyrics_translation_fi }}</pre>
|
|
</div>
|
|
<ul class="flex flex-wrap justify-center list-none gap-2 p-0 m-0">
|
|
<li class="tag" v-for="tag in artist.tags" :key="tag">{{ tag }}</li>
|
|
</ul>
|
|
<form @submit.prevent="submitReview" class="space-y-4 flex flex-col gap-4">
|
|
<div class="flex flex-col gap-2">
|
|
<label for="slider-song">Kappale</label>
|
|
<input id="slider-song" type="range" min="1" max="100" v-model="song"/>
|
|
<div class="flex justify-between"><p>0</p><p class="sliderSeparator">|</p><p>25</p><p class="sliderSeparator">|</p><p>50</p><p class="sliderSeparator">|</p><p>75</p><p class="sliderSeparator">|</p><p>100</p></div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<label for="slider-show">Lava show</label>
|
|
<input id="slider-show" type="range" min="1" max="100" v-model="show"/>
|
|
<div class="flex justify-between"><p>0</p><p class="sliderSeparator">|</p><p>25</p><p class="sliderSeparator">|</p><p>50</p><p class="sliderSeparator">|</p><p>75</p><p class="sliderSeparator">|</p><p>100</p></div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<label for="slider-costume">Asuste</label>
|
|
<input id="slider-costume" type="range" min="1" max="100" v-model="costume"/>
|
|
<div class="flex justify-between"><p>0</p><p class="sliderSeparator">|</p><p>25</p><p class="sliderSeparator">|</p><p>50</p><p class="sliderSeparator">|</p><p>75</p><p class="sliderSeparator">|</p><p>100</p></div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<label for="text_review">Arvostelu (vaihtoehtoinen)</label>
|
|
<textarea id="text_review" placeholder="Arvostelu" class="textfield" rows="6" v-model="textReview"/>
|
|
</div>
|
|
|
|
<button type="submit" class="btn-primary">Arvostele</button>
|
|
</form>
|
|
<NuxtLink class="btn-primary" to="/">Palaa etusivulle</NuxtLink>
|
|
</div>
|
|
<div v-else>
|
|
<h1>Artist not found</h1>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
.tag {
|
|
display: inline-block;
|
|
padding: 2px 6px;
|
|
background-color: #ccc;
|
|
margin: 2px;
|
|
border-radius: 6px;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.lyricsFont {
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
font-size: 0.8rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.textfield {
|
|
border: none;
|
|
border-bottom: 1px solid #ccc;
|
|
width: 100%; /* Changed from max-width: 80% to width: 100% */
|
|
padding: 6px;
|
|
background-color: rgba(255, 255, 255, 0.2);
|
|
border-radius: 6px;
|
|
resize: none;
|
|
}
|
|
</style> |