Update Cards UI
This commit is contained in:
@@ -2,12 +2,18 @@
|
||||
|
||||
<v-infinite-scroll :items="contents"
|
||||
:onLoad="fetchContents"
|
||||
class="gap-2">
|
||||
class="md:gap-2">
|
||||
|
||||
<template v-for="content in contents" :key="content">
|
||||
<content-card :content="content"
|
||||
@content-deleted="onContentDeleted"
|
||||
></content-card>
|
||||
<template v-for="content in contents" :key="content.id">
|
||||
|
||||
<div class="d-sm-none mb-1">
|
||||
<content-card-sm :content="content" @content-deleted="onContentDeleted"></content-card-sm>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-none d-sm-flex">
|
||||
<content-card-normal :content="content" @content-deleted="onContentDeleted"></content-card-normal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:empty>
|
||||
@@ -22,11 +28,12 @@
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
<script setup>
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import {ref, watch} from 'vue';
|
||||
import ContentCard from "./ContentCard.vue";
|
||||
import ContentCardNormal from "@/views/contents/contentcards/NContentCard.vue";
|
||||
import ContentCardSm from "@/views/contents/contentcards/SmContentCard.vue";
|
||||
|
||||
const props = defineProps({
|
||||
creatorId: {
|
||||
@@ -52,10 +59,8 @@ const creatorIdWatcher = watch(
|
||||
() => props.creatorId,
|
||||
(newCreatorId) => {
|
||||
if (newCreatorId) {
|
||||
// Reset contents and last_id when the creatorId changes
|
||||
contents.value = []
|
||||
last_id = null
|
||||
// Fetch contents for the new creator
|
||||
fetchContents({
|
||||
done: () => {
|
||||
}
|
||||
|
||||
257
src/views/contents/contentcards/NContentCard.vue
Normal file
257
src/views/contents/contentcards/NContentCard.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="shadow-md rounded-2xl bg-gray-50 border custom-border w-full mb-2 ">
|
||||
<div>
|
||||
<v-card-title>
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
:src="props.content.createdByPortraitUrl"
|
||||
alt="Profile Image"
|
||||
class="rounded-full"
|
||||
width="32px"
|
||||
height="32px">
|
||||
<div class="capitalize px-2">
|
||||
{{ props.content.createdByName }}
|
||||
</div>
|
||||
<span class="text-subtitle-2 mt-1">
|
||||
{{ time_ago(props.content.createdAt) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="plain" v-bind="props">
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item v-if="creatorIsCurrentUser" @click="editContent">
|
||||
<v-list-item-title>Modifier le contenu</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="creatorIsCurrentUser" @click="openDeleteConfirmationDialog">
|
||||
<v-list-item-title>Effacer le contenu</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="reportContent">
|
||||
<v-list-item-title>Reporter le contenu</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
<div class="uppercase">
|
||||
{{ props.content.title }}
|
||||
</div>
|
||||
<div>
|
||||
{{ props.content.description }}
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-carousel
|
||||
hide-delimiters
|
||||
v-if="hasUrls"
|
||||
:show-arrows="props.content.urls.length > 1"
|
||||
:show-indicators="props.content.urls.length > 1"
|
||||
>
|
||||
<v-carousel-item
|
||||
v-for="url in props.content.urls"
|
||||
:key="url"
|
||||
class="image-container"
|
||||
@click="redirectToContent"
|
||||
>
|
||||
<component :is="getComponent(url)" :src="url"></component>
|
||||
</v-carousel-item>
|
||||
</v-carousel>
|
||||
</div>
|
||||
|
||||
<div class="px-4">
|
||||
<div class="flex justify-around py-2">
|
||||
<v-btn variant="plain" icon @click="likeContent">
|
||||
<v-icon>mdi-thumb-up-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="dislikeContent">
|
||||
<v-icon>mdi-thumb-down-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:class="{'comment-active': hasMessages}"
|
||||
variant="plain"
|
||||
icon
|
||||
@click="toggleComments">
|
||||
<v-icon>mdi-comment-outline</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<donation-button :color-border="colorMenu"
|
||||
:color-accent="colorAccent"
|
||||
:creator-id="creatorId"
|
||||
:creator-name="creatorName"
|
||||
:creator-logo="creatorLogo"
|
||||
iconColorClass="text-black"
|
||||
></donation-button>
|
||||
|
||||
</div>
|
||||
|
||||
<div :class="{'hidden': !messagesVisible}">
|
||||
<h2 class="font-sans font-semibold mt-2">Commentaires</h2>
|
||||
<message-list
|
||||
:subject-id="props.content.id"
|
||||
:messages="messages"
|
||||
></message-list>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
<post-message :subject-id="props.content.id"
|
||||
@message-posted="addMessage"
|
||||
></post-message>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<v-dialog v-model="openDeleteConfirmationModal" max-width="500">
|
||||
<v-form>
|
||||
<v-card class="text-center rounded-xl"
|
||||
:style="{
|
||||
border: `2px solid `
|
||||
}">
|
||||
<div class="flex items-center justify-between py-4 text-2xl font-bold border-b mb-2">
|
||||
<div class="flex-1 text-center">
|
||||
Supprimer ce contenu ?
|
||||
</div>
|
||||
|
||||
<v-btn icon @click="openDeleteConfirmationModal = false" class="ml-auto mr-2" variant="text">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
|
||||
</div>
|
||||
|
||||
<div class=" mr-2">
|
||||
Êtes-vous sûr de vouloir supprimer le contenu ?
|
||||
</div>
|
||||
|
||||
<div class="py-2 space-x-3">
|
||||
<v-btn variant="flat"
|
||||
@click="deleteContent()" class=" mt-5">
|
||||
Oui
|
||||
</v-btn>
|
||||
<v-btn variant="outlined"
|
||||
@click="openDeleteConfirmationModal = false" class=" mt-5">
|
||||
Non
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, ref} from 'vue';
|
||||
import {time_ago} from "@/internal_time_ago.js";
|
||||
import MessageList from "@/views/messages/MessageList.vue";
|
||||
import PostMessage from "@/views/messages/PostMessage.vue";
|
||||
import DonationButton from "@/views/creators/DonationButton.vue";
|
||||
import YoutubePlayer from '../YoutubePlayer.vue';
|
||||
import ImageViewer from '../ImageViewer.vue';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
const openDeleteConfirmationModal = ref(false);
|
||||
const emits = defineEmits(['content-deleted'])
|
||||
|
||||
const contentId = computed(() => props.content.id)
|
||||
const creatorId = computed(() => props.content.createdBy)
|
||||
const creatorName = computed(() => props.content.createdByName)
|
||||
const creatorLogo = computed(() => props.content.createdByPortraitUrl)
|
||||
const colorMenu = computed(() => props.content.colorMenu)
|
||||
const colorAccent = computed(() => props.content.colorAccent)
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const creatorIsCurrentUser = computed(() => authStore.isAuthenticated && authStore.userId === creatorId.value)
|
||||
|
||||
const hasUrls = computed(() => !!props.content.urls && props.content.urls.length > 0);
|
||||
const messagesVisible = ref(false);
|
||||
const messages = ref([]);
|
||||
const hasMessages = computed(() => messages.value.length > 0)
|
||||
|
||||
function openDeleteConfirmationDialog() {
|
||||
openDeleteConfirmationModal.value = true;
|
||||
}
|
||||
|
||||
function addMessage(newMessage) {
|
||||
messages.value.unshift(newMessage);
|
||||
messagesVisible.value = true;
|
||||
}
|
||||
|
||||
function toggleComments() {
|
||||
messagesVisible.value = !messagesVisible.value;
|
||||
}
|
||||
|
||||
function likeContent() {
|
||||
console.log('Content liked');
|
||||
}
|
||||
|
||||
function dislikeContent() {
|
||||
console.log('Content disliked');
|
||||
}
|
||||
|
||||
function getComponent(url) {
|
||||
if (url.includes('youtube.com') || url.includes('youtu.be')) {
|
||||
return YoutubePlayer;
|
||||
} else if (url.match(/\.(jpeg|jpg|gif|png)$/)) {
|
||||
return ImageViewer;
|
||||
}
|
||||
}
|
||||
|
||||
function editContent() {
|
||||
console.log('Modifier le contenu');
|
||||
}
|
||||
|
||||
async function deleteContent() {
|
||||
const client = useClient()
|
||||
const response = await client.delete(`/api/contents/${contentId.value}`)
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
emits('content-deleted', contentId.value)
|
||||
}
|
||||
}
|
||||
|
||||
function reportContent() {
|
||||
console.log('Reporter le contenu');
|
||||
}
|
||||
|
||||
function redirectToContent() {
|
||||
window.location.href = `/content/${props.content.id}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
border-color: #EAEBEC;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.comment-active .v-icon {
|
||||
color: #D63DAB;
|
||||
}
|
||||
</style>
|
||||
265
src/views/contents/contentcards/SmContentCard.vue
Normal file
265
src/views/contents/contentcards/SmContentCard.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<template>
|
||||
<div class="shadow-md bg-gray-50">
|
||||
<div>
|
||||
<v-card-title>
|
||||
<div class="flex flex-row justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<img
|
||||
:src="props.content.createdByPortraitUrl"
|
||||
alt="Profile Image"
|
||||
class="rounded-full"
|
||||
width="32px"
|
||||
height="32px">
|
||||
<div class="capitalize px-2">
|
||||
{{ props.content.createdByName }}
|
||||
</div>
|
||||
<span class="text-subtitle-2">
|
||||
{{ time_ago(props.content.createdAt) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="plain" v-bind="props">
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item v-if="creatorIsCurrentUser" @click="editContent">
|
||||
<v-list-item-title>Modifier le contenu</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="creatorIsCurrentUser" @click="openDeleteConfirmationDialog">
|
||||
<v-list-item-title>Effacer le contenu</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="reportContent">
|
||||
<v-list-item-title>Reporter le contenu</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
<div class="uppercase">
|
||||
{{ props.content.title }}
|
||||
</div>
|
||||
<div>
|
||||
{{ props.content.description }}
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-carousel
|
||||
hide-delimiters
|
||||
v-if="hasUrls"
|
||||
:show-arrows="props.content.urls.length > 1"
|
||||
:show-indicators="props.content.urls.length > 1"
|
||||
>
|
||||
<v-carousel-item
|
||||
v-for="url in props.content.urls"
|
||||
:key="url"
|
||||
class="image-container"
|
||||
@click="redirectToContent"
|
||||
>
|
||||
<component :is="getComponent(url)" :src="url"></component>
|
||||
</v-carousel-item>
|
||||
</v-carousel>
|
||||
</div>
|
||||
|
||||
<div class="px-1">
|
||||
<div class="flex justify-around ">
|
||||
<v-btn variant="plain" icon @click="likeContent">
|
||||
<v-icon>mdi-thumb-up-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn variant="plain" icon @click="dislikeContent">
|
||||
<v-icon>mdi-thumb-down-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:class="{'comment-active': hasMessages}"
|
||||
variant="plain"
|
||||
icon
|
||||
@click="toggleComments">
|
||||
<v-icon>mdi-comment-outline</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<donation-button :color-border="colorMenu"
|
||||
:color-accent="colorAccent"
|
||||
:creator-id="creatorId"
|
||||
:creator-name="creatorName"
|
||||
:creator-logo="creatorLogo"
|
||||
iconColorClass="text-black"
|
||||
></donation-button>
|
||||
|
||||
</div>
|
||||
|
||||
<div :class="{'hidden': !messagesVisible}">
|
||||
<h2 class="font-sans font-semibold ">Commentaires</h2>
|
||||
<message-list
|
||||
:subject-id="props.content.id"
|
||||
:messages="messages"
|
||||
></message-list>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="py-1">
|
||||
<post-message :subject-id="props.content.id"
|
||||
@message-posted="addMessage"
|
||||
></post-message>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<v-dialog v-model="openDeleteConfirmationModal" max-width="500">
|
||||
<v-form>
|
||||
<v-card class="text-center rounded-xl"
|
||||
:style="{
|
||||
border: `2px solid `
|
||||
}">
|
||||
<div class="flex items-center justify-between py-4 text-2xl font-bold border-b mb-2">
|
||||
<div class="flex-1 text-center">
|
||||
Supprimer ce contenu ?
|
||||
</div>
|
||||
|
||||
<v-btn icon @click="openDeleteConfirmationModal = false" class="ml-auto mr-2" variant="text">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
|
||||
</div>
|
||||
|
||||
<div class=" mr-2">
|
||||
Êtes-vous sûr de vouloir supprimer le contenu ?
|
||||
</div>
|
||||
|
||||
<div class="py-2 space-x-3">
|
||||
<v-btn variant="flat"
|
||||
@click="deleteContent()" class=" mt-5">
|
||||
Oui
|
||||
</v-btn>
|
||||
<v-btn variant="outlined"
|
||||
@click="openDeleteConfirmationModal = false" class=" mt-5">
|
||||
Non
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {computed, ref} from 'vue';
|
||||
import {time_ago} from "@/internal_time_ago.js";
|
||||
import MessageList from "@/views/messages/MessageList.vue";
|
||||
import PostMessage from "@/views/messages/PostMessage.vue";
|
||||
import DonationButton from "@/views/creators/DonationButton.vue";
|
||||
import YoutubePlayer from '../YoutubePlayer.vue';
|
||||
import ImageViewer from '../ImageViewer.vue';
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
const openDeleteConfirmationModal = ref(false);
|
||||
const emits = defineEmits(['content-deleted'])
|
||||
|
||||
const contentId = computed(() => props.content.id)
|
||||
const creatorId = computed(() => props.content.createdBy)
|
||||
const creatorName = computed(() => props.content.createdByName)
|
||||
const creatorLogo = computed(() => props.content.createdByPortraitUrl)
|
||||
const colorMenu = computed(() => props.content.colorMenu)
|
||||
const colorAccent = computed(() => props.content.colorAccent)
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const creatorIsCurrentUser = computed(() => authStore.isAuthenticated && authStore.userId === creatorId.value)
|
||||
|
||||
const hasUrls = computed(() => !!props.content.urls && props.content.urls.length > 0);
|
||||
const messagesVisible = ref(false);
|
||||
const messages = ref([]);
|
||||
const hasMessages = computed(() => messages.value.length > 0)
|
||||
|
||||
function openDeleteConfirmationDialog() {
|
||||
openDeleteConfirmationModal.value = true;
|
||||
}
|
||||
|
||||
function addMessage(newMessage) {
|
||||
messages.value.unshift(newMessage);
|
||||
messagesVisible.value = true;
|
||||
}
|
||||
|
||||
function toggleComments() {
|
||||
messagesVisible.value = !messagesVisible.value;
|
||||
}
|
||||
|
||||
function likeContent() {
|
||||
console.log('Content liked');
|
||||
}
|
||||
|
||||
function dislikeContent() {
|
||||
console.log('Content disliked');
|
||||
}
|
||||
|
||||
function getComponent(url) {
|
||||
if (url.includes('youtube.com') || url.includes('youtu.be')) {
|
||||
return YoutubePlayer;
|
||||
} else if (url.match(/\.(jpeg|jpg|gif|png)$/)) {
|
||||
return ImageViewer;
|
||||
}
|
||||
}
|
||||
|
||||
function editContent() {
|
||||
console.log('Modifier le contenu');
|
||||
}
|
||||
|
||||
async function deleteContent() {
|
||||
const client = useClient()
|
||||
const response = await client.delete(`/api/contents/${contentId.value}`)
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
emits('content-deleted', contentId.value)
|
||||
}
|
||||
}
|
||||
|
||||
function reportContent() {
|
||||
console.log('Reporter le contenu');
|
||||
}
|
||||
|
||||
function redirectToContent() {
|
||||
window.location.href = `/content/${props.content.id}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.custom-border {
|
||||
border-color: #EAEBEC;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.v-carousel-item {
|
||||
padding: 0; /* Supprime tout padding interne */
|
||||
margin: 0; /* Supprime toute marge interne */
|
||||
width: 100vw; /* Assure que chaque item occupe toute la largeur de l'écran */
|
||||
}
|
||||
|
||||
|
||||
.comment-active .v-icon {
|
||||
color: #D63DAB;
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="py-4 px-4 min-h-14 md:rounded-t-2xl"
|
||||
:style="{ backgroundColor: creator.colors.bannerTop || '#6B0065' }">
|
||||
|
||||
<div class="w-full flex justify-end gap-14 ">
|
||||
<div class="w-full flex justify-end gap-6 ">
|
||||
<a
|
||||
v-for="socialNetwork in GetSocialsUrls()"
|
||||
:href="socialNetwork.url"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fallback when user try to access a non-existing creator -->
|
||||
|
||||
<div v-else>
|
||||
<div v-if="loading">
|
||||
<v-progress-linear indeterminate></v-progress-linear>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative w-full">
|
||||
<div class="relative w-full mb-5">
|
||||
<div class="rounded-b-2xl"
|
||||
:style="{ backgroundColor: creator.colors.bannerBottom || '#A30E79' }">
|
||||
<div class="relative z-20">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="relative w-full">
|
||||
<div class="relative w-full mb-5">
|
||||
<div class="rounded-b-2xl"
|
||||
:style="{ backgroundColor: creator.colors.bannerBottom || '#A30E79' }">
|
||||
<div class="relative z-20">
|
||||
|
||||
Reference in New Issue
Block a user