pull/5171/head
parent
8e2905d6cd
commit
127fa42bfa
@ -0,0 +1,61 @@ |
||||
<template> |
||||
<div class="friends-invitations"> |
||||
<BaseCard plain class="bg-white mt-4"> |
||||
<template #header> |
||||
<div class="px-4 py-2 bg-gray-15"> |
||||
<h2 class="text-h5">{{ title }}</h2> |
||||
</div> |
||||
</template> |
||||
<hr class="my-4"> |
||||
<div v-if="invitations && invitations.length > 0" class="invitation-list"> |
||||
<div v-for="invitation in invitations" :key="invitation.id" class="invitation-item"> |
||||
<div class="invitation-content"> |
||||
<img :src="invitation.itemPicture" class="item-picture" alt="Item picture"> |
||||
<div class="invitation-info"> |
||||
<h4><a :href="'profile.php?u=' + invitation.itemId">{{ invitation.itemName }}</a></h4> |
||||
<p>{{ invitation.content }}</p> |
||||
<span>{{ invitation.date }}</span> |
||||
</div> |
||||
<div class="invitation-actions"> |
||||
<BaseButton |
||||
v-if="invitation.canAccept" |
||||
label="Accept" |
||||
icon="check" |
||||
type="success" |
||||
@click="emitEvent('accept', invitation.id)" |
||||
/> |
||||
<BaseButton |
||||
v-if="invitation.canDeny" |
||||
label="Deny" |
||||
icon="times" |
||||
type="danger" |
||||
@click="emitEvent('deny', invitation.id)" |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div v-else class="no-invitations-message"> |
||||
<p>{{ t("No invitations or records found") }}</p> |
||||
</div> |
||||
</BaseCard> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import BaseCard from "../basecomponents/BaseCard.vue" |
||||
import BaseButton from "../basecomponents/BaseButton.vue" |
||||
import { useI18n } from "vue-i18n" |
||||
import { useFormatDate } from "../../composables/formatDate" |
||||
|
||||
const { t } = useI18n() |
||||
const { relativeDatetime } = useFormatDate() |
||||
const props = defineProps({ |
||||
invitations: Array, |
||||
title: String |
||||
}) |
||||
const emit = defineEmits(['accept', 'deny']) |
||||
function emitEvent(event, id) { |
||||
emit(event, id) |
||||
} |
||||
</script> |
@ -0,0 +1,292 @@ |
||||
<template> |
||||
<div class="social-search p-2"> |
||||
{{ query.value }} |
||||
<BaseCard class="mb-2"> |
||||
<template #header> |
||||
<div class="px-4 py-2 -mb-2 bg-gray-15"> |
||||
<h2 class="text-h5">{{ headerTitle }}</h2> |
||||
</div> |
||||
</template> |
||||
<div class="flex flex-col items-end"> |
||||
<div class="w-full flex justify-between items-center mb-2"> |
||||
<label for="search-query" class="mr-2">{{ t('Users, Groups') }}</label> |
||||
<BaseInputText |
||||
id="search-query" |
||||
v-model="query" |
||||
class="flex-grow" |
||||
label=""/> |
||||
</div> |
||||
<div class="w-full flex justify-between items-center mb-4"> |
||||
<label for="search-type" class="mr-2">{{ t('Type') }}</label> |
||||
<BaseSelect |
||||
id="search-type" |
||||
v-model="searchType" |
||||
:options="searchOptions" |
||||
optionLabel="name" |
||||
optionValue="code" |
||||
class="flex-grow" |
||||
label=""/> |
||||
</div> |
||||
<BaseButton |
||||
label="Search" |
||||
icon="search" |
||||
@click="performSearch" |
||||
type="secondary" |
||||
class="self-end" |
||||
/> |
||||
</div> |
||||
</BaseCard> |
||||
|
||||
|
||||
<BaseCard v-if="users.length" class="mb-2"> |
||||
<template #header> |
||||
<div class="px-4 py-2 -mb-2 bg-gray-15"> |
||||
<h2 class="text-h5">{{ t('Users') }}</h2> |
||||
</div> |
||||
</template> |
||||
<ul> |
||||
<li v-for="user in users" :key="user.id" class="flex items-center justify-between p-2 border-b-2"> |
||||
<div class="flex items-center"> |
||||
<img :src="user.avatar" class="w-16 h-16 rounded-full mr-4"> |
||||
<span>{{ user.name }}</span> |
||||
<span v-if="user.status === 'online'" class="mdi mdi-circle green mx-2" title="Online"></span> |
||||
<span v-else class="mdi mdi-circle gray mx-2" title="Offline"></span> |
||||
<span :class="getRoleIcon(user.role)" class="mx-2"></span> |
||||
</div> |
||||
<div> |
||||
<BaseButton |
||||
v-if="user.showInvitationButton" |
||||
@click="openInvitationModal(user)" |
||||
label="Send invitation" |
||||
icon="account" |
||||
type="secondary" |
||||
class="mr-2" |
||||
/> |
||||
|
||||
<BaseButton |
||||
@click="openMessageModal(user)" |
||||
label="Send message" |
||||
icon="email" |
||||
type="primary" |
||||
/> |
||||
</div> |
||||
</li> |
||||
</ul> |
||||
</BaseCard> |
||||
|
||||
<BaseCard v-if="groups.length" class="mb-2"> |
||||
<template #header> |
||||
<div class="px-4 py-2 -mb-2 bg-gray-15"> |
||||
<h2 class="text-h5">{{ t('Groups') }}</h2> |
||||
</div> |
||||
</template> |
||||
<div class="grid md:grid-cols-2 lg:grid-cols-4 gap-4 p-4"> |
||||
<div v-for="group in groups" :key="group.id" class="group-card"> |
||||
<div class="group-image flex justify-center"> |
||||
<img :src="group.image" class="rounded w-16 h-16" |
||||
</div> |
||||
<div class="group-info text-center"> |
||||
<h3>{{ group.name }}</h3> |
||||
<p>{{ group.description }}</p> |
||||
<a :href="group.url"> |
||||
<BaseButton label="See more" type="secondary" class="mt-2" icon=""/> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</BaseCard> |
||||
|
||||
<!-- Invitation Modal --> |
||||
<div v-if="showInvitationModal" class="invitation-modal-overlay" @click.self="closeInvitationModal"> |
||||
<div class="invitation-modal"> |
||||
<div class="invitation-modal-header"> |
||||
<h3>Send invitation</h3> |
||||
<button class="close-button" @click="closeInvitationModal">✕</button> |
||||
</div> |
||||
<textarea class="invitation-modal-textarea" placeholder="Add a personal message" v-model="invitationMessage"></textarea> |
||||
<button class="invitation-modal-send" @click="sendInvitation">Send message</button> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- Message Modal --> |
||||
<div v-if="showMessageModal" class="message-modal-overlay" @click.self="closeMessageModal"> |
||||
<div class="message-modal"> |
||||
<div class="message-modal-header"> |
||||
<h3>{{ t('Send message') }}</h3> |
||||
<button class="message-modal-close" @click="closeMessageModal">✕</button> |
||||
</div> |
||||
<div class="message-modal-body"> |
||||
<div class="message-user-info"> |
||||
<img :src="selectedUser.avatar" class="message-user-avatar" alt="User avatar"> |
||||
<span class="message-user-name">{{ selectedUser.name }}</span> |
||||
</div> |
||||
<input type="text" class="message-modal-input" placeholder="{{ t('Subject') }}" v-model="messageSubject"> |
||||
<textarea class="message-modal-textarea" placeholder="{{ t('Message') }}" v-model="messageContent"></textarea> |
||||
<button class="message-modal-send" @click="sendMessage">{{ t('Send message') }}</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
|
||||
import { nextTick, ref, computed } from "vue" |
||||
import BaseCard from "../../components/basecomponents/BaseCard.vue" |
||||
import BaseInputText from "../../components/basecomponents/BaseInputText.vue" |
||||
import BaseSelect from "../../components/basecomponents/BaseSelect.vue" |
||||
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||
import { useI18n } from "vue-i18n" |
||||
import { useNotification } from "../../composables/notification" |
||||
import { useSocialInfo } from "../../composables/useSocialInfo" |
||||
|
||||
const query = ref('') |
||||
const searchType = ref('user') |
||||
const searchPerformed = ref(false) |
||||
const { t } = useI18n() |
||||
const notification = useNotification() |
||||
const selectedUser = ref(null) |
||||
const showInvitationModal = ref(false) |
||||
const showMessageModal = ref(false) |
||||
const messageSubject = ref('') |
||||
const messageContent = ref('') |
||||
const invitationMessage = ref('') |
||||
const { user, groupInfo, isGroup, loadGroup, isLoading } = useSocialInfo() |
||||
const searchOptions = [ |
||||
{ name: 'User', code: 'user' }, |
||||
{ name: 'Group', code: 'group' } |
||||
] |
||||
const users = ref([]) |
||||
const groups = ref([]) |
||||
const getRoleIcon = (role) => { |
||||
switch(role) { |
||||
case 'student': |
||||
return 'mdi mdi-school' |
||||
case 'teacher': |
||||
return 'mdi mdi-account-outline' |
||||
case 'admin': |
||||
return 'mdi mdi-briefcase-check' |
||||
default: |
||||
return 'mdi mdi-account' |
||||
} |
||||
} |
||||
const headerTitle = computed(() => { |
||||
return searchPerformed.value ? `${t('Results and feedback')} "${query.value}"` : t('Search') |
||||
}) |
||||
const performSearch = async () => { |
||||
try { |
||||
if (query.value.trim() === '') { |
||||
notification.showWarningNotification('Please enter a search term.') |
||||
return |
||||
} |
||||
searchPerformed.value = true |
||||
await nextTick() |
||||
const response = await fetch(`/social-network/search?query=${query.value}&type=${searchType.value}`) |
||||
const data = await response.json() |
||||
if (!response.ok) { |
||||
throw new Error(data.message || 'Server response error') |
||||
} |
||||
if (searchType.value === 'user') { |
||||
|
||||
users.value = data.results.map(item => ({ |
||||
...item, |
||||
showInvitationButton: ![3, 4].includes(item.relationType) && item.id !== user.value.id |
||||
})) |
||||
groups.value = [] |
||||
} else if (searchType.value === 'group') { |
||||
groups.value = data.results |
||||
users.value = [] |
||||
} |
||||
} catch (error) { |
||||
console.error('There has been a problem with your fetch operation:', error) |
||||
} |
||||
} |
||||
const openMessageModal = (user) => { |
||||
selectedUser.value = user |
||||
showMessageModal.value = true |
||||
} |
||||
const closeMessageModal = () => { |
||||
showMessageModal.value = false |
||||
messageSubject.value = '' |
||||
messageContent.value = '' |
||||
} |
||||
const openInvitationModal = (user) => { |
||||
selectedUser.value = user |
||||
showInvitationModal.value = true |
||||
} |
||||
const closeInvitationModal = () => { |
||||
showInvitationModal.value = false |
||||
invitationMessage.value = '' |
||||
} |
||||
const sendInvitation = async () => { |
||||
if (!selectedUser.value) { |
||||
notification.showErrorNotification('No user selected.') |
||||
return |
||||
} |
||||
|
||||
const invitationData = { |
||||
userId: user.value.id, |
||||
targetUserId: selectedUser.value.id, |
||||
action: 'send_invitation', |
||||
subject: '', |
||||
content: invitationMessage.value |
||||
} |
||||
try { |
||||
const response = await fetch('/social-network/user-action', { |
||||
method: 'POST', |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
}, |
||||
body: JSON.stringify(invitationData) |
||||
}) |
||||
const result = await response.json() |
||||
if (result.success) { |
||||
notification.showSuccessNotification('Invitation sent successfully.') |
||||
users.value = users.value.filter((user) => user.id !== selectedUser.value.id) |
||||
selectedUser.value = null |
||||
} else { |
||||
notification.showErrorNotification('Failed to send invitation.') |
||||
} |
||||
} catch (error) { |
||||
notification.showErrorNotification('An error occurred while sending the invitation.') |
||||
console.error('Error sending invitation:', error) |
||||
} |
||||
|
||||
showInvitationModal.value = false |
||||
invitationMessage.value = '' |
||||
} |
||||
const sendMessage = async () => { |
||||
if (!selectedUser.value) { |
||||
notification.showErrorNotification('No user selected.') |
||||
return |
||||
} |
||||
|
||||
const messageData = { |
||||
userId: user.value.id, |
||||
targetUserId: selectedUser.value.id, |
||||
action: 'send_message', |
||||
subject: messageSubject.value, |
||||
content: messageContent.value |
||||
} |
||||
try { |
||||
const response = await fetch('/social-network/user-action', { |
||||
method: 'POST', |
||||
headers: { |
||||
'Content-Type': 'application/json', |
||||
}, |
||||
body: JSON.stringify(messageData) |
||||
}) |
||||
const result = await response.json() |
||||
if (result.success) { |
||||
notification.showSuccessNotification('Message sent successfully.') |
||||
} else { |
||||
notification.showErrorNotification('Failed to send message.') |
||||
} |
||||
} catch (error) { |
||||
notification.showErrorNotification('An error occurred while sending the message.') |
||||
console.error('Error sending message:', error) |
||||
} |
||||
|
||||
closeMessageModal() |
||||
} |
||||
</script> |
@ -1,40 +1,74 @@ |
||||
<template> |
||||
<div class="social-group-show"> |
||||
<div v-if="!isLoading && groupInfo.isMember" class="social-group-show"> |
||||
<div class="group-header"> |
||||
<h1 class="group-title">mi grupo 0002</h1> |
||||
<p class="group-description">test</p> |
||||
<h1 class="group-title">{{ groupInfo?.title || '...' }}</h1> |
||||
<p class="group-description">{{ groupInfo?.description }}</p> |
||||
</div> |
||||
|
||||
<ul class="tabs"> |
||||
<li :class="{ active: activeTab === 'discussions' }" @click="activeTab = 'discussions'">Discussions</li> |
||||
<li :class="{ active: activeTab === 'members' }" @click="activeTab = 'members'">Members</li> |
||||
<li :class="{ active: activeTab === 'discussions' }" @click="activeTab = 'discussions'">{{ t('Discussions') }}</li> |
||||
<li :class="{ active: activeTab === 'members' }" @click="activeTab = 'members'">{{ t('Members') }}</li> |
||||
</ul> |
||||
|
||||
<div class="tab-content"> |
||||
<GroupDiscussions v-if="activeTab === 'discussions'" :group-id="groupId" /> |
||||
<GroupMembers v-if="activeTab === 'members'" :group-id="groupId" /> |
||||
<GroupDiscussions v-if="activeTab === 'discussions'" :group-id="groupInfo.id" /> |
||||
<GroupMembers v-if="activeTab === 'members'" :group-id="groupInfo.id" /> |
||||
</div> |
||||
</div> |
||||
|
||||
<div v-if="!isLoading && !groupInfo.isMember" class="text-center"> |
||||
<div class="group-header"> |
||||
<h1 class="group-title">{{ groupInfo?.title || '...' }}</h1> |
||||
<p class="group-description">{{ groupInfo?.description }}</p> |
||||
</div> |
||||
<p v-if="groupInfo.visibility === 2">{{ t('This is a closed group.') }}</p> |
||||
<p v-if="groupInfo.role === 3">{{ t('You already sent an invitation') }}</p> |
||||
<p v-else>{{ t('Join this group to see the content.') }}</p> |
||||
<BaseButton |
||||
v-if="groupInfo.visibility === 1 && groupInfo.role !== 3" |
||||
:label="t('Join to group')" |
||||
type="primary" |
||||
class="mt-4" |
||||
@click="joinGroup" |
||||
icon="mdi-account-multiple-plus" |
||||
/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import { ref, onMounted } from 'vue' |
||||
import { useRoute } from 'vue-router' |
||||
import axios from 'axios' |
||||
import GroupDiscussions from "../../components/usergroup/GroupDiscussions.vue" |
||||
import GroupMembers from "../../components/usergroup/GroupMembers.vue" |
||||
import { useI18n } from "vue-i18n" |
||||
import { useSocialInfo } from "../../composables/useSocialInfo" |
||||
import axios from "axios" |
||||
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||
|
||||
const { t } = useI18n() |
||||
const route = useRoute() |
||||
const activeTab = ref('discussions') |
||||
const groupId = ref(route.params.group_id) |
||||
const group = ref(null) |
||||
onMounted(async () => { |
||||
if (groupId.value) { |
||||
try { |
||||
const response = await axios.get(`/api/usergroup/${groupId.value}`) |
||||
group.value = response.data |
||||
} catch (error) { |
||||
console.error('Error fetching group details:', error) |
||||
const { user, groupInfo, isGroup, loadGroup, isLoading } = useSocialInfo(); |
||||
|
||||
const joinGroup = async () => { |
||||
try { |
||||
const response = await axios.post('/social-network/group-action', { |
||||
userId: user.value.id, |
||||
groupId: groupInfo.value.id, |
||||
action: 'join' |
||||
}); |
||||
|
||||
if (response.data.success) { |
||||
await loadGroup(groupInfo.value.id); |
||||
} |
||||
} catch (error) { |
||||
console.error('Error joining the group:', error); |
||||
} |
||||
}; |
||||
|
||||
onMounted(async () => { |
||||
if (route.params.group_id) { |
||||
await loadGroup(route.params.group_id); |
||||
} |
||||
}) |
||||
}); |
||||
</script> |
||||
|
@ -0,0 +1,114 @@ |
||||
<template> |
||||
<BaseButton |
||||
label="Try and find some friends" |
||||
icon="search" |
||||
type="success" |
||||
size="normal" |
||||
@click="goToSearch" |
||||
/> |
||||
<div> |
||||
<InvitationList |
||||
:invitations="receivedInvitations" |
||||
title="Invitations Received" |
||||
@accept="acceptInvitation" |
||||
@deny="denyInvitation" |
||||
/> |
||||
<InvitationList |
||||
:invitations="sentInvitations" |
||||
title="Invitations Sent" |
||||
/> |
||||
<InvitationList |
||||
:invitations="pendingInvitations" |
||||
title="Pending Group Invitations" |
||||
@accept="acceptGroupInvitation" |
||||
@deny="denyGroupInvitation" |
||||
/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script setup> |
||||
import axios from 'axios' |
||||
import { inject, onMounted, ref, watchEffect } from "vue" |
||||
import InvitationList from "../../components/userreluser/InvitationList.vue" |
||||
import BaseButton from "../../components/basecomponents/BaseButton.vue" |
||||
import { useRouter } from "vue-router" |
||||
|
||||
const receivedInvitations = ref([]) |
||||
const sentInvitations = ref([]) |
||||
const pendingInvitations = ref([]) |
||||
const router = useRouter() |
||||
|
||||
const user = inject('social-user') |
||||
const isCurrentUser = inject('is-current-user') |
||||
|
||||
|
||||
watchEffect(() => { |
||||
if (user.value && user.value.id) { |
||||
fetchInvitations(user.value.id) |
||||
} |
||||
}) |
||||
const fetchInvitations = async (userId) => { |
||||
if (!userId) return |
||||
try { |
||||
const response = await axios.get(`/social-network/invitations/${userId}`) |
||||
console.log('Invitations :::', response.data) |
||||
receivedInvitations.value = response.data.receivedInvitations |
||||
sentInvitations.value = response.data.sentInvitations |
||||
pendingInvitations.value = response.data.pendingGroupInvitations |
||||
} catch (error) { |
||||
console.error('Error fetching invitations:', error) |
||||
} |
||||
} |
||||
function goToSearch() { |
||||
router.push({ name: 'SocialSearch' }) |
||||
} |
||||
|
||||
const acceptInvitation = async (invitationId) => { |
||||
const invitation = receivedInvitations.value.find(invite => invite.id === invitationId) |
||||
if (!invitation) return |
||||
console.log('Invitation object:', invitation) |
||||
const data = { |
||||
userId: user.value.id, |
||||
targetUserId: invitation.itemId, |
||||
action: 'add_friend', |
||||
is_my_friend: true, |
||||
} |
||||
try { |
||||
const response = await axios.post('/social-network/user-action', data) |
||||
if (response.data.success) { |
||||
console.log('Invitation accepted successfully') |
||||
fetchInvitations(user.value.id) |
||||
} else { |
||||
console.error('Failed to accept invitation') |
||||
} |
||||
} catch (error) { |
||||
console.error('Error accepting invitation:', error) |
||||
} |
||||
} |
||||
const denyInvitation = async (invitationId) => { |
||||
const invitation = receivedInvitations.value.find(invite => invite.id === invitationId) |
||||
if (!invitation) return |
||||
const data = { |
||||
userId: user.value.id, |
||||
targetUserId: invitation.itemId, |
||||
action: 'deny_friend', |
||||
} |
||||
try { |
||||
const response = await axios.post('/social-network/user-action', data) |
||||
if (response.data.success) { |
||||
console.log('Invitation denied successfully') |
||||
await fetchInvitations(user.value.id) |
||||
} else { |
||||
console.error('Failed to deny invitation') |
||||
} |
||||
} catch (error) { |
||||
console.error('Error denying invitation:', error) |
||||
} |
||||
} |
||||
const acceptGroupInvitation = (groupId) => { |
||||
console.log(`Accepted group invitation with ID: ${groupId}`) |
||||
} |
||||
const denyGroupInvitation = (groupId) => { |
||||
console.log(`Denied group invitation with ID: ${groupId}`) |
||||
} |
||||
</script> |
Loading…
Reference in new issue