audiobookshelf/client/components/recommendations/RecommendButton.vue

141 lines
5.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="enabled">
<button class="rounded px-3 py-1 text-sm bg-primary text-white border border-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary/40" @click="onClick">Recommend</button>
<div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center">
<div class="absolute inset-0 bg-black/60" @click="close"></div>
<div class="relative z-10 w-full max-w-md rounded-md border border-gray-700 bg-bg p-4">
<div class="flex items-center mb-3">
<h3 class="text-lg font-semibold">Recommend this</h3>
<button class="ml-auto text-gray-400 hover:text-gray-200" @click="close" aria-label="Close"></button>
</div>
<div v-if="tagsLoading" class="text-gray-400">Loading tags</div>
<div v-else>
<label class="block text-sm mb-1">Tag</label>
<select v-model="form.tagId" class="w-full mb-3 rounded border px-2 py-1 bg-[#111827] text-gray-100 border-gray-600" style="color-scheme: dark">
<option disabled value="">Select a tag</option>
<option v-for="t in tags" :key="t.id" :value="t.id" class="bg-[#111827] text-gray-100">
{{ t.label }}
</option>
</select>
<label class="block text-sm mb-1">Visibility</label>
<select v-model="form.visibility" class="w-full mb-3 rounded border px-2 py-1 bg-[#111827] text-gray-100 border-gray-600" style="color-scheme: dark">
<option value="public">public</option>
<option value="recipient-only">recipient-only</option>
</select>
<div v-if="form.visibility === 'recipient-only'">
<label class="block text-sm mb-1">Recipient (username)</label>
<input v-model.trim="form.recipient" class="w-full mb-3 rounded border px-2 py-1 bg-[#111827] text-gray-100 border-gray-600" placeholder="Type the recipients username" autocomplete="off" />
<p class="text-xs text-gray-400 -mt-2 mb-2">Enter the users <strong>username</strong>. Well resolve it on the server.</p>
</div>
<label class="block text-sm mb-1">Note <span class="opacity-60">(optional)</span></label>
<textarea v-model="form.note" rows="3" maxlength="1000" class="w-full rounded border border-gray-600 bg-transparent px-2 py-1" placeholder="Why should someone read/listen to this?"></textarea>
<div class="mt-4 flex items-center gap-2">
<button :disabled="submitting || !form.tagId || (form.visibility === 'recipient-only' && !form.recipient)" class="rounded px-3 py-1 text-sm bg-primary text-white border border-primary hover:bg-primary/90 disabled:opacity-50 focus:outline-none focus:ring-2 focus:ring-primary/40" @click="submit">
{{ submitting ? 'Posting' : 'Post recommendation' }}
</button>
<span v-if="error" class="text-sm text-error">{{ error }}</span>
<span v-if="ok" class="text-sm text-success">Posted</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RecommendButton',
props: { bookId: { type: String, required: true } },
data: () => ({
enabled: false,
user: null,
open: false,
tags: [],
tagsLoading: false,
submitting: false,
ok: false,
error: '',
form: { tagId: '', visibility: 'public', recipient: '', note: '' }
}),
mounted() {
const flag = this.$store.getters['getServerSetting']?.('recommendationsEnabled')
this.enabled = !!flag
this.user = this.$store?.state?.user?.user || null
if (!this.enabled) {
this.$axios
.$get('/api/recommendations/tags')
.then(() => {
this.enabled = true
})
.catch(() => {
this.enabled = false
})
}
},
methods: {
async onClick() {
if (!this.enabled) return
if (!this.user) {
const back = encodeURIComponent(this.$route.fullPath)
return this.$router.push(`/login?redirect=${back}`)
}
this.open = true
this.ok = false
this.error = ''
if (!this.tags.length) {
this.tagsLoading = true
try {
this.tags = await this.$axios.$get('/api/recommendations/tags')
} catch (e) {
this.error = e?.response?.data?.message || 'Failed to load tags'
} finally {
this.tagsLoading = false
}
}
},
close() {
this.open = false
this.form = { tagId: '', visibility: 'public', note: '' }
this.error = ''
this.ok = false
},
async submit() {
if (!this.form.tagId) return
if (this.form.visibility === 'recipient-only' && !this.form.recipient) {
this.error = 'Recipient is required'
return
}
this.submitting = true
this.error = ''
this.ok = false
try {
const payload = {
bookId: this.bookId,
tagId: this.form.tagId,
note: this.form.note || '',
visibility: this.form.visibility,
recipientUsername: this.form.visibility === 'recipient-only' ? this.form.recipient : null
}
const created = await this.$axios.$post('/api/recommendations', payload)
this.$emit('recommended', created)
this.ok = true
this.$toast?.success?.('Recommendation posted')
setTimeout(() => this.close(), 700)
} catch (e) {
this.error = e?.response?.data?.message || 'Failed to post'
} finally {
this.submitting = false
}
}
}
}
</script>