Resolved merge conflicts with upstream/master

pull/5319/head
christianbeeznst 1 year ago
commit 6ea53ae962
  1. 18
      assets/vue/components/StudentViewButton.vue
  2. 85
      assets/vue/components/resource_links/EditLinks.vue
  3. 56
      assets/vue/components/social/Actions.vue
  4. 78
      assets/vue/components/social/GroupInfoCard.vue
  5. 149
      assets/vue/components/social/SocialSideMenu.vue
  6. 42
      assets/vue/components/social/SocialWallCommentForm.vue
  7. 72
      assets/vue/components/social/SocialWallPost.vue
  8. 79
      assets/vue/components/social/SocialWallPostForm.vue
  9. 35
      assets/vue/components/social/SocialWallPostList.vue
  10. 101
      assets/vue/components/social/UserProfileCard.vue
  11. 14
      assets/vue/components/systemannouncement/SystemAnnouncementCardList.vue
  12. 115
      assets/vue/components/usergroup/GroupDiscussionTopics.vue
  13. 55
      assets/vue/components/usergroup/GroupMembers.vue
  14. 11
      assets/vue/composables/useSocialInfo.js
  15. 5
      assets/vue/config/api.js
  16. 23
      assets/vue/constants/entity/userreluser.js
  17. 255
      assets/vue/mixins/ListMixin.js
  18. 10
      assets/vue/services/adminService.js
  19. 61
      assets/vue/services/baseService.js
  20. 33
      assets/vue/services/cToolIntroService.js
  21. 13
      assets/vue/services/courseRelUserService.js
  22. 65
      assets/vue/services/courseService.js
  23. 8
      assets/vue/services/glossaryService.js
  24. 39
      assets/vue/services/linkService.js
  25. 26
      assets/vue/services/messageTagService.js
  26. 4
      assets/vue/services/pageCategoryService.js
  27. 11
      assets/vue/services/permissionService.js
  28. 8
      assets/vue/services/resourceLinkService.js
  29. 13
      assets/vue/services/sessionRelCourseRelUserService.js
  30. 11
      assets/vue/services/sessionRelUserService.js
  31. 12
      assets/vue/services/sessionService.js
  32. 138
      assets/vue/services/socialService.js
  33. 50
      assets/vue/services/userRelUserService.js
  34. 31
      assets/vue/services/userService.js
  35. 60
      assets/vue/services/usergroupService.js
  36. 8
      assets/vue/store/cidReq.js
  37. 10
      assets/vue/store/enrolledStore.js
  38. 18
      assets/vue/store/platformConfig.js
  39. 7
      assets/vue/store/socialStore.js
  40. 85
      assets/vue/views/admin/AdminConfigureColors.vue
  41. 45
      assets/vue/views/ccalendarevent/CCalendarEventList.vue
  42. 32
      assets/vue/views/course/CourseHome.vue
  43. 23
      assets/vue/views/ctoolintro/Create.vue
  44. 17
      assets/vue/views/ctoolintro/Update.vue
  45. 93
      assets/vue/views/documents/CreateFile.vue
  46. 103
      assets/vue/views/documents/DocumentsList.vue
  47. 38
      assets/vue/views/message/MessageCreate.vue
  48. 22
      assets/vue/views/message/MessageShow.vue
  49. 56
      assets/vue/views/user/courses/List.vue
  50. 81
      assets/vue/views/usergroup/Invite.vue
  51. 233
      assets/vue/views/usergroup/List.vue
  52. 59
      assets/vue/views/userreluser/UserRelUserAdd.vue
  53. 89
      assets/vue/views/userreluser/UserRelUserSearch.vue
  54. 34
      var/vue_templates/components/SidebarLogin.vue

@ -15,10 +15,10 @@ import { computed } from "vue"
import { useI18n } from "vue-i18n"
import { useStore } from "vuex"
import { usePlatformConfig } from "../store/platformConfig"
import axios from "axios"
import { storeToRefs } from "pinia"
import { useCidReqStore } from "../store/cidReq"
import { useSecurityStore } from "../store/securityStore"
import permissionService from "../services/permissionService"
const emit = defineEmits(["change"])
@ -30,15 +30,11 @@ const securityStore = useSecurityStore()
const isStudentView = computed({
async set() {
try {
const { data } = await axios.get(`${window.location.origin}/toggle_student_view`)
const studentView = await permissionService.toogleStudentView()
platformConfigStore.studentView = data
platformConfigStore.studentView = studentView
emit("change", data)
} catch (e) {
console.log(e)
}
emit("change", studentView)
},
get() {
return platformConfigStore.isStudentViewActive
@ -52,9 +48,11 @@ const { course, userIsCoach } = storeToRefs(cidReqStore)
const user = computed(() => store.getters["security/getUser"])
const showButton = computed(() => {
return securityStore.isAuthenticated &&
return (
securityStore.isAuthenticated &&
course.value &&
(isCourseAdmin.value || isAdmin.value || userIsCoach.value(user.value.id, 0, false)) &&
"true" === platformConfigStore.getSetting("course.student_view_enabled");
"true" === platformConfigStore.getSetting("course.student_view_enabled")
)
})
</script>

@ -10,13 +10,13 @@
v-if="showShareWithUser"
v-model="selectedUsers"
:internal-search="false"
:limit="3"
:loading="isLoading"
:multiple="true"
:options="users"
:placeholder="$t('Share with User')"
:searchable="true"
label="username"
:limit="3"
track-by="id"
@select="addUser"
@search-change="asyncFind"
@ -27,12 +27,17 @@
<script setup>
import ShowLinks from "../../components/resource_links/ShowLinks.vue"
import { ref } from "vue"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import VueMultiselect from "vue-multiselect"
import isEmpty from "lodash/isEmpty"
import { RESOURCE_LINK_PUBLISHED } from "./visibility.js"
import { useSecurityStore } from "../../store/securityStore"
import userService from "../../services/userService"
import userRelUserService from "../../services/userRelUserService"
import { useCidReqStore } from "../../store/cidReq"
import { storeToRefs } from "pinia"
import sessionRelCourseRelUserService from "../../services/sessionRelCourseRelUserService"
import sessionRelUserService from "../../services/sessionRelUserService"
import courseRelUserService from "../../services/courseRelUserService"
// eslint-disable-next-line vue/require-prop-types
const model = defineModel()
@ -70,6 +75,9 @@ const selectedUsers = ref([])
const isLoading = ref(false)
const securityStore = useSecurityStore()
const cidReqStore = useCidReqStore()
const { course, session } = storeToRefs(cidReqStore)
function addUser(userResult) {
if (isEmpty(model.value[props.linkListName])) {
@ -90,39 +98,17 @@ function addUser(userResult) {
}
function findUsers(query) {
axios
.get(ENTRYPOINT + "users", {
params: {
username: query,
},
})
.then((response) => {
isLoading.value = false
let data = response.data
users.value = data["hydra:member"]
})
.catch(function (error) {
isLoading.value = false
console.log(error)
})
userService
.findByUsername(query)
.then(({ items }) => (users.value = items))
.finally(() => (isLoading.value = false))
}
function findUserRelUsers(query) {
axios
.get(ENTRYPOINT + "user_rel_users", {
params: {
user: securityStore.user["id"],
"friend.username": query,
},
})
.then((response) => {
isLoading.value = false
users.value = response.data["hydra:member"].map((member) => member.friend)
})
.catch(function () {
isLoading.value = false
})
userRelUserService
.searchRelationshipByUsername(securityStore.user["@id"], query)
.then(({ items }) => (users.value = items.map((relationship) => relationship.friend)))
.finally(() => (isLoading.value = false))
}
function findStudentsInCourse(query) {
@ -130,42 +116,35 @@ function findStudentsInCourse(query) {
const cId = parseInt(searchParams.get("cid"))
const sId = parseInt(searchParams.get("sid"))
if (!cId && !sId) {
if (!course.value && !session.value) {
return
}
let endpoint = ENTRYPOINT
let params = {
"user.username": query,
}
if (sId) {
params.session = endpoint + `sessions/${sId}`
if (session.value) {
params.session = session.value["@id"]
}
let service
if (cId) {
params.course = course.value["@id"]
if (sId) {
endpoint += `session_rel_course_rel_users`
params.course = endpoint + `courses/${cId}`
service = sessionRelCourseRelUserService.findAll
} else {
endpoint += `courses/${cId}/users`
service = courseRelUserService.findAll
}
} else {
endpoint += `session_rel_users`
service = sessionRelUserService.findAll
}
axios
.get(endpoint, {
params,
})
.then((response) => {
isLoading.value = false
users.value = response.data["hydra:member"].map((member) => member.user)
})
.catch(function () {
isLoading.value = false
})
service(params)
.then(({ items }) => (users.value = items.map((membership) => membership.user)))
.finally(() => (isLoading.value = false))
}
function asyncFind(query) {

@ -39,9 +39,9 @@
</template>
<script>
import { reactive, ref } from "vue";
import axios from "axios";
import { usePlatformConfig } from "../../store/platformConfig";
import { reactive } from "vue"
import { usePlatformConfig } from "../../store/platformConfig"
import socialService from "../../services/socialService"
export default {
name: "WallActions",
@ -57,53 +57,49 @@ export default {
},
emits: ["post-deleted"],
setup(props, { emit }) {
const platformConfigStore = usePlatformConfig();
const platformConfigStore = usePlatformConfig()
const isLoading = reactive({
like: false,
dislike: false,
delete: false,
});
})
function onLikeComment() {
isLoading.like = true;
isLoading.like = true
axios
.post(props.socialPost["@id"] + "/like", {})
.then(({ data }) => {
props.socialPost.countFeedbackLikes = data.countFeedbackLikes;
props.socialPost.countFeedbackDislikes = data.countFeedbackDislikes;
socialService
.sendPostLike(props.socialPost["@id"])
.then((like) => {
props.socialPost.countFeedbackLikes = like.countFeedbackLikes
props.socialPost.countFeedbackDislikes = like.countFeedbackDislikes
})
.finally(() => (isLoading.like = false));
.finally(() => (isLoading.like = false))
}
function onDisikeComment() {
isLoading.dislike = true;
isLoading.dislike = true
axios
.post(props.socialPost["@id"] + "/dislike", {})
.then(({ data }) => {
props.socialPost.countFeedbackLikes = data.countFeedbackLikes;
props.socialPost.countFeedbackDislikes = data.countFeedbackDislikes;
socialService
.sendPostDislike(props.socialPost["@id"])
.then((like) => {
props.socialPost.countFeedbackLikes = like.countFeedbackLikes
props.socialPost.countFeedbackDislikes = like.countFeedbackDislikes
})
.finally(() => (isLoading.dislike = false));
.finally(() => (isLoading.dislike = false))
}
function onDeleteComment() {
isLoading.delete = true;
isLoading.delete = true
axios
socialService
.delete(props.socialPost["@id"])
.then(() => emit("post-deleted", props.socialPost))
.finally(() => (isLoading.delete = false));
.finally(() => (isLoading.delete = false))
}
const enableFeedback =
"true" ===
platformConfigStore.getSetting("social.social_enable_messages_feedback");
const disableDislike =
"true" ===
platformConfigStore.getSetting("social.disable_dislike_option");
const enableFeedback = "true" === platformConfigStore.getSetting("social.social_enable_messages_feedback")
const disableDislike = "true" === platformConfigStore.getSetting("social.disable_dislike_option")
return {
enableFeedback,
@ -112,7 +108,7 @@ export default {
onLikeComment,
onDisikeComment,
onDeleteComment,
};
}
},
};
}
</script>

@ -3,42 +3,47 @@
<div class="p-4 text-center">
<img
:src="groupInfo.image"
class="mb-4 w-24 h-24 mx-auto rounded-full"
alt="Group picture"
class="mb-4 w-24 h-24 mx-auto rounded-full"
/>
<hr />
<BaseButton
v-if="groupInfo.isModerator"
:label="t('Edit this group')"
type="primary"
class="mt-4"
@click="showEditGroupDialog = true"
icon="edit"
type="primary"
@click="showEditGroupDialog = true"
/>
</div>
</BaseCard>
<Dialog header="Edit Group" v-model:visible="showEditGroupDialog" :modal="true" :closable="true">
<Dialog
v-model:visible="showEditGroupDialog"
:closable="true"
:modal="true"
header="Edit Group"
>
<form @submit.prevent="submitGroupEdit">
<div class="p-fluid">
<BaseInputTextWithVuelidate
v-model="editGroupForm.name"
label="Name*"
:vuelidate-property="v$.editGroupForm.name"
label="Name*"
/>
<BaseInputTextWithVuelidate
v-model="editGroupForm.description"
label="Description"
:vuelidate-property="v$.editGroupForm.description"
as="textarea"
label="Description"
rows="3"
/>
<BaseInputTextWithVuelidate
v-model="editGroupForm.url"
label="URL"
:vuelidate-property="v$.editGroupForm.url"
label="URL"
/>
<BaseFileUpload
@ -50,7 +55,13 @@
<div class="p-field mt-2">
<label for="groupPermissions">Group Permissions</label>
<Dropdown id="groupPermissions" v-model="editGroupForm.permissions" :options="permissionsOptions" optionLabel="label" placeholder="Select Permission" />
<Dropdown
id="groupPermissions"
v-model="editGroupForm.permissions"
:options="permissionsOptions"
option-label="label"
placeholder="Select Permission"
/>
</div>
<div class="p-field-checkbox mt-2">
@ -62,15 +73,24 @@
/>
</div>
</div>
<Button label="Save" icon="pi pi-check" class="p-button-rounded p-button-text" @click="submitGroupEdit" />
<Button label="Close" class="p-button-text" @click="closeEditDialog" />
<Button
class="p-button-rounded p-button-text"
icon="pi pi-check"
label="Save"
@click="submitGroupEdit"
/>
<Button
class="p-button-text"
label="Close"
@click="closeEditDialog"
/>
</form>
</Dialog>
</template>
<script setup>
import { computed, inject, onMounted, ref, watch } from "vue"
import { useStore } from 'vuex'
import { inject, ref } from "vue"
import { useStore } from "vuex"
import BaseCard from "../basecomponents/BaseCard.vue"
import BaseButton from "../basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n"
@ -79,7 +99,7 @@ import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVueli
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import BaseFileUpload from "../basecomponents/BaseFileUpload.vue"
import useVuelidate from "@vuelidate/core"
import { required } from '@vuelidate/validators'
import { required } from "@vuelidate/validators"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
@ -87,33 +107,36 @@ const { t } = useI18n()
const store = useStore()
const route = useRoute()
const router = useRouter()
const groupInfo = inject('group-info')
const isGroup = inject('is-group')
const groupInfo = inject("group-info")
const isGroup = inject("is-group")
const showEditGroupDialog = ref(false)
const selectedFile = ref(null)
const permissionsOptions = [
{ label: 'Open', value: 1 },
{ label: 'Closed', value: 2 },
{ label: "Open", value: 1 },
{ label: "Closed", value: 2 },
]
const editGroupForm = ref({
name: groupInfo.value.title,
description: groupInfo.value.description,
url: groupInfo.value.url,
permissions: permissionsOptions.find(option => option.value === groupInfo.value.visibility),
permissions: permissionsOptions.find((option) => option.value === groupInfo.value.visibility),
allowLeave: Boolean(groupInfo.value.allowMembersToLeaveGroup),
})
const v$ = useVuelidate({
const v$ = useVuelidate(
{
editGroupForm: {
name: { required },
description: {},
url: {},
permissions: { required },
}
}, { editGroupForm })
},
},
{ editGroupForm },
)
const submitGroupEdit = () => {
v$.value.$touch()
@ -126,30 +149,31 @@ const submitGroupEdit = () => {
allowMembersToLeaveGroup: editGroupForm.value.allowLeave ? 1 : 0,
}
axios.put(`${ENTRYPOINT}usergroups/${groupInfo.value.id}`, updatedGroupData, {
axios
.put(`${ENTRYPOINT}usergroups/${groupInfo.value.id}`, updatedGroupData, {
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
})
.then((response) => {
if (selectedFile.value && response.data && response.data.id) {
const formData = new FormData()
formData.append('picture', selectedFile.value)
formData.append("picture", selectedFile.value)
return axios.post(`/social-network/upload-group-picture/${response.data.id}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
"Content-Type": "multipart/form-data",
},
})
}
})
.then(() => {
showEditGroupDialog.value = false
router.push('/dummy').then(() => {
router.push("/dummy").then(() => {
router.go(-1)
})
})
.catch((error) => {
console.error('Error updating group:', error)
console.error("Error updating group:", error)
})
}
}

@ -2,82 +2,140 @@
<BaseCard class="social-side-menu mt-4">
<template #header>
<div class="px-4 py-2 -mb-2 bg-gray-15">
<h2 class="text-h5">{{ t('Social network') }}</h2>
<h2 class="text-h5">{{ t("Social network") }}</h2>
</div>
</template>
<hr class="-mt-2 mb-4 -mx-4">
<ul v-if="isCurrentUser" class="menu-list">
<li :class="['menu-item', { 'active': isActive('/social') }]">
<hr class="-mt-2 mb-4 -mx-4" />
<ul
v-if="isCurrentUser"
class="menu-list"
>
<li :class="['menu-item', { active: isActive('/social') }]">
<router-link to="/social">
<i class="mdi mdi-home" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-home"
></i>
{{ t("Home") }}
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive('/resources/messages') }]">
<li :class="['menu-item', { active: isActive('/resources/messages') }]">
<router-link to="/resources/messages">
<i class="mdi mdi-email" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-email"
></i>
{{ t("Messages") }}
<span class="badge badge-warning" v-if="unreadMessagesCount > 0">{{ unreadMessagesCount }}</span>
<span
v-if="unreadMessagesCount > 0"
class="badge badge-warning"
>{{ unreadMessagesCount }}</span
>
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive('/resources/friends/invitations') }]">
<li :class="['menu-item', { active: isActive('/resources/friends/invitations') }]">
<router-link :to="{ name: 'Invitations' }">
<i class="mdi mdi-mailbox" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-mailbox"
></i>
{{ t("Invitations") }}
<span class="badge badge-warning" v-if="invitationsCount > 0">{{ invitationsCount }}</span>
<span
v-if="invitationsCount > 0"
class="badge badge-warning"
>{{ invitationsCount }}</span
>
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive('/resources/friends') }]">
<li :class="['menu-item', { active: isActive('/resources/friends') }]">
<router-link :to="{ name: 'UserRelUserList' }">
<i class="mdi mdi-handshake" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-handshake"
></i>
{{ t("My friends") }}
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive(groupLink) }]">
<a v-if="isValidGlobalForumsCourse" :href="groupLink" rel="noopener noreferrer">
<i class="mdi mdi-group" aria-hidden="true"></i>
<li :class="['menu-item', { active: isActive(groupLink) }]">
<a
v-if="isValidGlobalForumsCourse"
:href="groupLink"
rel="noopener noreferrer"
>
<i
aria-hidden="true"
class="mdi mdi-group"
></i>
{{ t("Social groups") }}
</a>
<router-link v-else :to="groupLink">
<i class="mdi mdi-group" aria-hidden="true"></i>
<router-link
v-else
:to="groupLink"
>
<i
aria-hidden="true"
class="mdi mdi-group"
></i>
{{ t("Social groups") }}
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive('/social/search') }]">
<li :class="['menu-item', { active: isActive('/social/search') }]">
<router-link to="/social/search">
<i class="mdi mdi-magnify" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-magnify"
></i>
{{ t("Search") }}
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive('/resources/personal_files') }]">
<li :class="['menu-item', { active: isActive('/resources/personal_files') }]">
<router-link :to="{ name: 'PersonalFileList', params: { node: currentNodeId } }">
<i class="mdi mdi-briefcase"></i>
{{ t("My files") }}
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive('/resources/users/personal_data') }]">
<li :class="['menu-item', { active: isActive('/resources/users/personal_data') }]">
<router-link to="/resources/users/personal_data">
<i class="mdi mdi-account" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-account"
></i>
{{ t("Personal data") }}
</router-link>
</li>
<li :class="['menu-item', { 'active': isActive('/social', 'promoted') }]">
<li :class="['menu-item', { active: isActive('/social', 'promoted') }]">
<router-link :to="{ path: '/social', query: { filterType: 'promoted' } }">
<i class="mdi mdi-star" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-star"
></i>
{{ t("Promoted messages") }}
</router-link>
</li>
</ul>
<ul v-else class="menu-list">
<ul
v-else
class="menu-list"
>
<li class="menu-item">
<router-link to="/social">
<i class="mdi mdi-home" aria-hidden="true"></i>
<i
aria-hidden="true"
class="mdi mdi-home"
></i>
{{ t("Home") }}
</router-link>
</li>
<li class="menu-item">
<a href="/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id={{user.id}}" class="ajax" rel="noopener noreferrer">
<i class="mdi mdi-email" aria-hidden="true"></i>
<a
class="ajax"
href="/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id={{user.id}}"
rel="noopener noreferrer"
>
<i
aria-hidden="true"
class="mdi mdi-email"
></i>
{{ t("Send message") }}
</a>
</li>
@ -87,10 +145,10 @@
<script setup>
import BaseCard from "../basecomponents/BaseCard.vue"
import { useRoute } from 'vue-router'
import { useRoute } from "vue-router"
import { useI18n } from "vue-i18n"
import { useMessageRelUserStore } from "../../store/messageRelUserStore"
import { onMounted, computed, ref, inject, watchEffect } from "vue"
import { computed, inject, onMounted, ref, watchEffect } from "vue"
import { useStore } from "vuex"
import { useSecurityStore } from "../../store/securityStore"
import axios from "axios"
@ -105,9 +163,9 @@ const messageRelUserStore = useMessageRelUserStore()
const unreadMessagesCount = computed(() => messageRelUserStore.countUnread)
const invitationsCount = ref(0)
const user = inject('social-user')
const isCurrentUser = inject('is-current-user')
const groupLink = ref({ name: 'UserGroupShow' })
const user = inject("social-user")
const isCurrentUser = inject("is-current-user")
const groupLink = ref({ name: "UserGroupShow" })
const platformConfigStore = usePlatformConfig()
const globalForumsCourse = computed(() => platformConfigStore.getSetting("forum.global_forums_course_id"))
const isValidGlobalForumsCourse = computed(() => {
@ -116,15 +174,15 @@ const isValidGlobalForumsCourse = computed(() => {
})
const getGroupLink = async () => {
try {
const response = await axios.get('/social-network/get-forum-link')
const response = await axios.get("/social-network/get-forum-link")
if (isValidGlobalForumsCourse.value) {
groupLink.value = response.data.go_to
} else {
groupLink.value = { name: 'UserGroupList' }
groupLink.value = { name: "UserGroupList" }
}
} catch (error) {
console.error('Error fetching forum link:', error)
groupLink.value = { name: 'UserGroupList' }
console.error("Error fetching forum link:", error)
groupLink.value = { name: "UserGroupList" }
}
}
@ -134,7 +192,7 @@ const fetchInvitationsCount = async (userId) => {
const { data } = await axios.get(`/social-network/invitations/count/${userId}`)
invitationsCount.value = data.totalInvitationsCount
} catch (error) {
console.error('Error fetching invitations count:', error)
console.error("Error fetching invitations count:", error)
}
}
watchEffect(() => {
@ -152,19 +210,24 @@ watchEffect(() => {
fetchInvitationsCount(user.value.id)
}
} catch (e) {
console.error('Error loading user:', e)
console.error("Error loading user:", e)
}
})
const isActive = (path, filterType = null) => {
if (path === '/resources/friends/invitations' || path === '/social/search') {
if (path === "/resources/friends/invitations" || path === "/social/search") {
return route.path === path
}
const pathMatch = route.path.startsWith(path)
const hasQueryParams = Object.keys(route.query).length > 0
const filterMatch = filterType ? (route.query.filterType === filterType && hasQueryParams) : !hasQueryParams
return pathMatch && filterMatch && !route.path.startsWith('/resources/friends/invitations') && !route.path.startsWith('/social/search')
const filterMatch = filterType ? route.query.filterType === filterType && hasQueryParams : !hasQueryParams
return (
pathMatch &&
filterMatch &&
!route.path.startsWith("/resources/friends/invitations") &&
!route.path.startsWith("/social/search")
)
}
onMounted(async () => {

@ -2,18 +2,18 @@
<form class="flex flex-wrap items-start gap-2">
<BaseInputText
v-model="comment"
class="grow mb-0"
:label="$t('Write new comment')"
:aria-placeholder="$t('Write new comment')"
:is-invalid="error !== ''"
:error-text="error"
:is-invalid="error !== ''"
:label="$t('Write new comment')"
class="grow mb-0"
/>
<BaseButton
:label="$t('Post')"
class="ml-auto"
type="primary"
icon="send"
size="small"
type="primary"
@click="sendComment"
/>
</form>
@ -25,9 +25,9 @@ import {useStore} from "vuex"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import { SOCIAL_TYPE_WALL_COMMENT } from "./constants"
import BaseInputText from "../basecomponents/BaseInputText.vue";
import {useI18n} from "vue-i18n";
import BaseButton from "../basecomponents/BaseButton.vue";
import BaseInputText from "../basecomponents/BaseInputText.vue"
import { useI18n } from "vue-i18n"
import BaseButton from "../basecomponents/BaseButton.vue"
const store = useStore()
const { t } = useI18n()
@ -36,34 +36,34 @@ const props = defineProps({
post: {
type: Object,
required: true,
}
},
})
const emit = defineEmits(['comment-posted'])
const emit = defineEmits(["comment-posted"])
const currentUser = store.getters['security/getUser']
const comment = ref('')
const error = ref('')
const currentUser = store.getters["security/getUser"]
const comment = ref("")
const error = ref("")
const isLoading = ref(false)
function sendComment() {
if (comment.value === '') {
error.value = t('The comment is required')
if (comment.value === "") {
error.value = t("The comment is required")
return
}
isLoading.value = true
axios
.post(ENTRYPOINT + 'social_posts', {
.post(ENTRYPOINT + "social_posts", {
content: comment.value,
type: SOCIAL_TYPE_WALL_COMMENT,
sender: currentUser['@id'],
parent: props.post['@id'],
sender: currentUser["@id"],
parent: props.post["@id"],
})
.then(response => {
emit('comment-posted', response.data)
comment.value = ''
error.value = ''
.then((response) => {
emit("comment-posted", response.data)
comment.value = ""
error.value = ""
})
.finally(() => {
isLoading.value = false

@ -1,12 +1,16 @@
<template>
<BaseCard
class="mb-4"
:class="{ 'border-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE }"
class="mb-4"
plain
>
<div class="flex flex-col">
<div class="flex gap-2 mb-4">
<img class="h-12 w-12 border border-gray-25" :src="post.sender.illustrationUrl" :alt="post.sender.username">
<img
:alt="post.sender.username"
:src="post.sender.illustrationUrl"
class="h-12 w-12 border border-gray-25"
/>
<div class="flex flex-col">
<div v-if="null === post.userReceiver || post.sender['@id'] === post.userReceiver['@id']">
@ -31,36 +35,45 @@
</div>
<WallActions
class="ml-auto"
:is-owner="isOwner"
:social-post="post"
class="ml-auto"
@post-deleted="onPostDeleted(post)"
/>
</div>
<div class="flex flex-col gap-2">
<div v-for="(attachment, index) in computedAttachments" :key="index">
<div
v-for="(attachment, index) in computedAttachments"
:key="index"
>
<img
v-if="isImageAttachment(attachment)"
:src="attachment.path"
:alt="attachment.filename"
>
:src="attachment.path"
/>
<video v-if="isVideoAttachment(attachment)" controls>
<source :src="attachment.path" :type="attachment.mimeType">
<video
v-if="isVideoAttachment(attachment)"
controls
>
<source
:src="attachment.path"
:type="attachment.mimeType"
/>
</video>
</div>
<div v-html="post.content" />
<hr :class="{'text-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE}">
<hr :class="{ 'text-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE }" />
<div
v-if="comments.length"
:class="{ 'text-success': post.type === SOCIAL_TYPE_PROMOTED_MESSAGE }"
class="border-t-0"
>
<div>{{ $t('Comments') }}</div>
<div>{{ $t("Comments") }}</div>
<WallComment
v-for="(comment, index) in comments"
:key="index"
@ -80,7 +93,7 @@
<script setup>
import WallCommentForm from "./SocialWallCommentForm.vue"
import { ref, computed, onMounted, reactive, inject } from "vue"
import { computed, inject, onMounted, reactive, ref } from "vue"
import WallComment from "./SocialWallComment.vue"
import WallActions from "./Actions"
import axios from "axios"
@ -89,25 +102,26 @@ import {useStore} from "vuex"
import BaseCard from "../basecomponents/BaseCard.vue"
import { SOCIAL_TYPE_PROMOTED_MESSAGE } from "./constants"
import { useFormatDate } from "../../composables/formatDate"
import { useSecurityStore } from "../../store/securityStore"
const props = defineProps({
post: {
type: Object,
required: true
}
required: true,
},
})
const emit = defineEmits(["post-deleted"])
const store = useStore()
import { useSecurityStore } from "../../store/securityStore"
const { relativeDatetime } = useFormatDate()
let comments = reactive([])
const attachments = ref([])
const securityStore = useSecurityStore()
const currentUser = inject('social-user')
const isCurrentUser = inject('is-current-user')
const isOwner = computed(() => currentUser['@id'] === props.post.sender['@id'])
const currentUser = inject("social-user")
const isCurrentUser = inject("is-current-user")
const isOwner = computed(() => currentUser["@id"] === props.post.sender["@id"])
onMounted(async () => {
loadComments()
@ -116,6 +130,7 @@ onMounted(async () => {
const computedAttachments = computed(() => {
return attachments.value
})
async function loadAttachments() {
try {
const postIri = props.post["@id"]
@ -129,19 +144,18 @@ async function loadAttachments() {
function loadComments() {
axios
.get(ENTRYPOINT + 'social_posts', {
.get(ENTRYPOINT + "social_posts", {
params: {
parent: props.post['@id'],
'order[sendDate]': 'desc',
parent: props.post["@id"],
"order[sendDate]": "desc",
itemsPerPage: 3,
}
},
})
.then(response => comments.push(...response.data['hydra:member']))
.then((response) => comments.push(...response.data["hydra:member"]))
}
function onCommentDeleted(event) {
const index = comments.findIndex(comment => comment['@id'] === event.comment['@id'])
const index = comments.findIndex((comment) => comment["@id"] === event.comment["@id"])
if (-1 !== index) {
comments.splice(index, 1)
}
@ -152,21 +166,21 @@ function onCommentPosted(newComment) {
}
function onPostDeleted(post) {
emit('post-deleted', post)
emit("post-deleted", post)
}
const isImageAttachment = (attachment) => {
if (attachment.filename) {
const fileExtension = attachment.filename.split('.').pop().toLowerCase()
return ['jpg', 'jpeg', 'png', 'gif'].includes(fileExtension)
const fileExtension = attachment.filename.split(".").pop().toLowerCase()
return ["jpg", "jpeg", "png", "gif"].includes(fileExtension)
}
return false
}
const isVideoAttachment = (attachment) => {
if (attachment.filename) {
const fileExtension = attachment.filename.split('.').pop().toLowerCase()
return ['mp4', 'webm', 'ogg'].includes(fileExtension)
const fileExtension = attachment.filename.split(".").pop().toLowerCase()
return ["mp4", "webm", "ogg"].includes(fileExtension)
}
return false

@ -3,9 +3,9 @@
<form>
<BaseInputTextWithVuelidate
v-model="content"
class="mb-2"
:label="textPlaceholder"
:vuelidate-property="v$.content"
class="mb-2"
/>
<div class="mb-2">
@ -16,7 +16,10 @@
:label="$t('Mark as promoted message')"
name="is-promoted"
/>
<div v-if="isPromotedPage" class="text-info">
<div
v-if="isPromotedPage"
class="text-info"
>
{{ $t("All messages here are automatically marked as promoted.") }}
</div>
</div>
@ -33,9 +36,9 @@
<BaseButton
:label="$t('Post')"
class="ml-auto"
type="primary"
icon="send"
size="small"
type="primary"
@click="sendPost"
/>
</div>
@ -44,7 +47,7 @@
</template>
<script setup>
import {inject, onMounted, reactive, ref, toRefs, watch, computed} from "vue"
import { computed, inject, onMounted, reactive, ref, toRefs, watch } from "vue"
import { useStore } from "vuex"
import { SOCIAL_TYPE_PROMOTED_MESSAGE, SOCIAL_TYPE_WALL_POST } from "./constants"
import useVuelidate from "@vuelidate/core"
@ -56,41 +59,51 @@ import BaseFileUpload from "../basecomponents/BaseFileUpload.vue"
import BaseCheckbox from "../basecomponents/BaseCheckbox.vue"
import BaseInputTextWithVuelidate from "../basecomponents/BaseInputTextWithVuelidate.vue"
import axios from "axios"
import { useRoute } from 'vue-router'
import { useRoute } from "vue-router"
const emit = defineEmits(['post-created'])
const emit = defineEmits(["post-created"])
const store = useStore()
const { t } = useI18n()
const route = useRoute()
const isPromotedPage = computed(() => {
return route.query.filterType === 'promoted'
return route.query.filterType === "promoted"
})
const user = inject('social-user')
const currentUser = store.getters['security/getUser']
const userIsAdmin = store.getters['security/isAdmin']
const user = inject("social-user")
const currentUser = store.getters["security/getUser"]
const userIsAdmin = store.getters["security/isAdmin"]
const selectedFile = ref(null)
const postState = reactive({
content: '',
content: "",
attachment: null,
isPromoted: false,
textPlaceholder: '',
textPlaceholder: "",
})
const { content, attachment, isPromoted, textPlaceholder } = toRefs(postState)
const v$ = useVuelidate({
const v$ = useVuelidate(
{
content: { required },
}, postState)
},
postState,
)
watch(() => user.value, () => {
watch(
() => user.value,
() => {
showTextPlaceholder()
showCheckboxPromoted()
})
},
)
watch(isPromotedPage, (newVal) => {
watch(
isPromotedPage,
(newVal) => {
if (newVal) {
postState.isPromoted = true
}
}, { immediate: true })
},
{ immediate: true },
)
onMounted(() => {
showTextPlaceholder()
@ -98,14 +111,16 @@ onMounted(() => {
})
function showTextPlaceholder() {
postState.textPlaceholder = currentUser['@id'] === user.value['@id']
? t('What are you thinking about?')
: t('Write something to {0}', [user.value.fullName])
postState.textPlaceholder =
currentUser["@id"] === user.value["@id"]
? t("What are you thinking about?")
: t("Write something to {0}", [user.value.fullName])
}
const allowCreatePromoted = ref(false)
function showCheckboxPromoted() {
allowCreatePromoted.value = userIsAdmin && currentUser['@id'] === user.value['@id']
allowCreatePromoted.value = userIsAdmin && currentUser["@id"] === user.value["@id"]
}
async function sendPost() {
@ -123,34 +138,34 @@ async function sendPost() {
}
try {
await store.dispatch('socialpost/create', {
await store.dispatch("socialpost/create", {
content: postState.content,
type: postState.isPromoted ? SOCIAL_TYPE_PROMOTED_MESSAGE : SOCIAL_TYPE_WALL_POST,
sender: currentUser['@id'],
userReceiver: currentUser['@id'] === user.value['@id'] ? null : user.value['@id'],
sender: currentUser["@id"],
userReceiver: currentUser["@id"] === user.value["@id"] ? null : user.value["@id"],
})
if (selectedFile.value) {
const formData = new FormData()
const post = store.state.socialpost.created
let idUrl = post["@id"]
let parts = idUrl.split('/')
let parts = idUrl.split("/")
let socialPostId = parts[parts.length - 1]
formData.append('file', selectedFile.value)
formData.append('messageId', socialPostId)
const endpoint = '/api/social_post_attachments'
formData.append("file", selectedFile.value)
formData.append("messageId", socialPostId)
const endpoint = "/api/social_post_attachments"
const fileUploadResponse = await axios.post(endpoint, formData, {
headers: {
'Content-Type': 'multipart/form-data',
"Content-Type": "multipart/form-data",
},
})
}
postState.content = ''
postState.content = ""
postState.attachment = null
postState.isPromoted = false
postState.isPromoted = isPromotedPage.value
v$.value.$reset()
emit('post-created')
emit("post-created")
} catch (error) {
console.error("There was an error creating the post:", error)
}

@ -17,20 +17,23 @@ import {inject, onMounted, reactive, ref, watch} from "vue"
import Loading from "../Loading"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import { useRoute } from 'vue-router'
import { useRoute } from "vue-router"
import { SOCIAL_TYPE_PROMOTED_MESSAGE } from "./constants"
const user = inject('social-user')
const user = inject("social-user")
const postList = reactive([])
const isLoading = ref(false)
const route = useRoute()
watch([() => user.value, () => route.query.filterType], () => {
watch(
[() => user.value, () => route.query.filterType],
() => {
listPosts()
}, { immediate: true })
},
{ immediate: true },
)
onMounted(listPosts)
function refreshPosts() {
@ -38,28 +41,30 @@ function refreshPosts() {
}
defineExpose({
refreshPosts
refreshPosts,
})
function listPosts() {
postList.splice(0, postList.length)
if (!user.value['@id']) {
if (!user.value["@id"]) {
return
}
const filterType = route.query.filterType
isLoading.value = true
const params = {
socialwall_wallOwner: user.value['id'],
'order[sendDate]': 'desc',
'exists[parent]': false,
socialwall_wallOwner: user.value["id"],
"order[sendDate]": "desc",
"exists[parent]": false,
}
if (filterType === 'promoted') {
if (filterType === "promoted") {
params.type = SOCIAL_TYPE_PROMOTED_MESSAGE
}
axios.get(ENTRYPOINT + 'social_posts', { params })
.then(response => {
postList.push(...response.data['hydra:member'])
axios
.get(ENTRYPOINT + "social_posts", { params })
.then((response) => {
postList.push(...response.data["hydra:member"])
})
.finally(() => {
isLoading.value = false
@ -67,7 +72,7 @@ function listPosts() {
}
function onPostDeleted(event) {
const index = postList.findIndex(post => post['@id'] === event['@id'])
const index = postList.findIndex((post) => post["@id"] === event["@id"])
if (index >= 0) {
postList.splice(index, 1)
}

@ -1,8 +1,17 @@
<template>
<BaseCard plain>
<div class="p-4 text-center user-profile-card">
<img :src="user.illustrationUrl" class="mb-4 w-24 h-24 mx-auto rounded-full" alt="Profile picture" />
<div v-if="visibility.firstname && visibility.lastname" class="text-xl font-bold">{{ user.fullName }}</div>
<img
:src="user.illustrationUrl"
alt="Profile picture"
class="mb-4 w-24 h-24 mx-auto rounded-full"
/>
<div
v-if="visibility.firstname && visibility.lastname"
class="text-xl font-bold"
>
{{ user.fullName }}
</div>
<div v-if="visibility.language && languageInfo">
<template v-if="flagIconExists(languageInfo.code)">
@ -14,34 +23,66 @@
</div>
<div class="mt-4">
<p v-if="showFullProfile" class="flex items-center justify-center mb-2">
<a v-if="visibility.email && user.email" :href="'/resources/messages/new'" class="flex items-center justify-center mb-2">
<p
v-if="showFullProfile"
class="flex items-center justify-center mb-2"
>
<a
v-if="visibility.email && user.email"
:href="'/resources/messages/new'"
class="flex items-center justify-center mb-2"
>
<i class="mdi mdi-email-outline mr-2"></i> {{ user.email }}
</a>
</p>
<p v-if="vCardUserLink" class="flex items-center justify-center mb-2">
<a :href="vCardUserLink" target="_blank" class="flex items-center justify-center">
<i class="mdi mdi-card-account-details-outline mr-2"></i> {{ t('Business card') }}
<p
v-if="vCardUserLink"
class="flex items-center justify-center mb-2"
>
<a
:href="vCardUserLink"
class="flex items-center justify-center"
target="_blank"
>
<i class="mdi mdi-card-account-details-outline mr-2"></i> {{ t("Business card") }}
</a>
</p>
<p v-if="user.skype" class="flex items-center justify-center mb-2">
<p
v-if="user.skype"
class="flex items-center justify-center mb-2"
>
<i class="mdi mdi-skype mr-2"></i> Skype: {{ user.skype }}
</p>
<p v-if="user.linkedin" class="flex items-center justify-center">
<p
v-if="user.linkedin"
class="flex items-center justify-center"
>
<i class="mdi mdi-linkedin mr-2"></i> LinkedIn: {{ user.linkedin }}
</p>
</div>
<hr />
<div v-if="extraInfo && extraInfo.length > 0" class="extra-info-container">
<div
v-if="extraInfo && extraInfo.length > 0"
class="extra-info-container"
>
<dl class="extra-info-list">
<template v-for="item in extraInfo" :key="item.variable">
<template
v-for="item in extraInfo"
:key="item.variable"
>
<div v-if="item.value">
<dt v-if="item.variable !== 'langue_cible'">{{ item.label }}:</dt>
<dd v-if="item.variable !== 'langue_cible'">{{ item.value }}</dd>
<div v-if="item.variable === 'langue_cible'" class="language-target">
<i v-if="flagIconExists(item.value)" :class="`flag-icon flag-icon-${item.value.toLowerCase()}`"></i>
<div
v-if="item.variable === 'langue_cible'"
class="language-target"
>
<i
v-if="flagIconExists(item.value)"
:class="`flag-icon flag-icon-${item.value.toLowerCase()}`"
></i>
</div>
</div>
</template>
@ -50,30 +91,36 @@
<div v-if="chatEnabled && isUserOnline && !userOnlyInChat">
<button @click="chatWith(user.id, user.fullName, user.isOnline, user.illustrationUrl)">
{{ t('Chat') }} ({{ t('Online') }})
{{ t("Chat") }} ({{ t("Online") }})
</button>
</div>
<Divider />
<BaseButton v-if="isCurrentUser || isAdmin" :label="t('Edit profile')" type="primary" class="mt-4" @click="editProfile" icon="edit" />
<BaseButton
v-if="isCurrentUser || isAdmin"
:label="t('Edit profile')"
class="mt-4"
icon="edit"
type="primary"
@click="editProfile"
/>
</div>
</BaseCard>
</template>
<script setup>
import { computed, inject, onMounted, ref, watchEffect } from "vue"
import { useStore } from 'vuex'
import { computed, inject, ref, watchEffect } from "vue"
import { useStore } from "vuex"
import BaseCard from "../basecomponents/BaseCard.vue"
import BaseButton from "../basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n"
import Divider from 'primevue/divider'
import Divider from "primevue/divider"
import axios from "axios"
const { t } = useI18n()
const store = useStore()
const user = inject('social-user')
const isCurrentUser = inject('is-current-user')
const user = inject("social-user")
const isCurrentUser = inject("is-current-user")
const isAdmin = ref(false)
const extraInfo = ref([])
const chatEnabled = ref(true)
@ -81,7 +128,7 @@ const isUserOnline = ref(false)
const userOnlyInChat = ref(false)
const showFullProfile = computed(() => isCurrentUser.value || isAdmin.value)
const languageInfo = ref(null)
const vCardUserLink = ref('')
const vCardUserLink = ref("")
const visibility = ref({})
watchEffect(() => {
if (user.value && user.value.id) {
@ -92,6 +139,7 @@ watchEffect(() => {
const editProfile = () => {
window.location = "/account/edit"
}
async function fetchUserProfile(userId) {
try {
const response = await axios.get(`/social-network/user-profile/${userId}`)
@ -104,17 +152,16 @@ async function fetchUserProfile(userId) {
userOnlyInChat.value = data.userOnlyInChat
chatEnabled.value = data.chatEnabled
} catch (error) {
console.error('Error fetching user profile data:', error)
console.error("Error fetching user profile data:", error)
}
}
function flagIconExists(code) {
const mdiFlagIcons = ['us', 'fr', 'de', 'es', 'it', 'pl']
const mdiFlagIcons = ["us", "fr", "de", "es", "it", "pl"]
return mdiFlagIcons.includes(code.toLowerCase())
}
function chatWith(userId, completeName, isOnline, avatarSmall) {
}
function chatWith(userId, completeName, isOnline, avatarSmall) {}
isAdmin.value = user.value.role === 'admin'
isAdmin.value = user.value.role === "admin"
</script>

@ -1,7 +1,5 @@
<template>
<div
v-if="announcements.length > 0"
>
<div v-if="announcements.length > 0">
<SystemAnnouncementCard
v-for="announcement in announcements"
:key="announcement.id"
@ -14,18 +12,18 @@
import { ref } from "vue"
import axios from "axios"
import SystemAnnouncementCard from './SystemAnnouncementCard.vue';
import SystemAnnouncementCard from "./SystemAnnouncementCard.vue"
const announcements = ref([]);
const announcements = ref([])
axios
.get("/news/list")
.then((response) => {
if (Array.isArray(response.data)) {
announcements.value = response.data;
announcements.value = response.data
}
})
.catch(function (error) {
console.log(error);
});
console.log(error)
})
</script>

@ -1,42 +1,76 @@
<template>
<div class="social-group-show group-info text-center">
<div class="group-header">
<h1 class="group-title">{{ groupInfo?.title || '...' }}</h1>
<h1 class="group-title">{{ groupInfo?.title || "..." }}</h1>
<p class="group-description">{{ groupInfo?.description }}</p>
</div>
</div>
<div v-if="!isLoading" class="discussion">
<BaseButton @click="goBack" class="back-button mb-8" icon="back" type="button" :label="t('Back to the list')" />
<h2>{{ firstMessageTitle || 'Discussion Thread' }}</h2>
<div
v-if="!isLoading"
class="discussion"
>
<BaseButton
:label="t('Back to the list')"
class="back-button mb-8"
icon="back"
type="button"
@click="goBack"
/>
<h2>{{ firstMessageTitle || "Discussion Thread" }}</h2>
<div class="message-list mt-8">
<MessageItem
v-for="message in messages"
:key="message.id"
:message="message"
:currentUser="user"
:current-user="user"
:indentation="0"
:isMainMessage="message.parentId === null || message.parentId === 0"
:isModerator="groupInfo.isModerator"
@replyMessage="openDialogForReply"
@editMessage="openDialogForEdit"
@deleteMessage="deleteMessage"
:is-main-message="message.parentId === null || message.parentId === 0"
:is-moderator="groupInfo.isModerator"
:message="message"
@delete-message="deleteMessage"
@edit-message="openDialogForEdit"
@reply-message="openDialogForReply"
/>
</div>
</div>
<Dialog header="Reply/Edit Message" v-model:visible="showMessageDialog" modal closable>
<Dialog
v-model:visible="showMessageDialog"
closable
header="Reply/Edit Message"
modal
>
<form @submit.prevent="handleSubmit">
<BaseInputText v-if="isEditMode" id="title" :label="t('Title')" v-model="messageTitle" :isInvalid="titleError" />
<BaseTinyEditor v-model="messageContent" editor-id="messageEditor" title="Message" />
<BaseFileUploadMultiple v-model="files" :label="t('Add files')" accept="image/png, image/jpeg" />
<BaseButton type="button" :label="t('Send message')" icon="save" @click="handleSubmit" class="mt-8" />
<BaseInputText
v-if="isEditMode"
id="title"
v-model="messageTitle"
:is-invalid="titleError"
:label="t('Title')"
/>
<BaseTinyEditor
v-model="messageContent"
editor-id="messageEditor"
title="Message"
/>
<BaseFileUploadMultiple
v-model="files"
:label="t('Add files')"
accept="image/png, image/jpeg"
/>
<BaseButton
:label="t('Send message')"
class="mt-8"
icon="save"
type="button"
@click="handleSubmit"
/>
</form>
</Dialog>
</template>
<script setup>
import { onMounted, reactive, computed, ref, toRefs } from "vue"
import { useRouter, useRoute } from "vue-router"
import { computed, onMounted, reactive, ref, toRefs } from "vue"
import { useRoute, useRouter } from "vue-router"
import { useSocialInfo } from "../../composables/useSocialInfo"
import axios from "axios"
import MessageItem from "./MessageItem.vue"
@ -60,18 +94,18 @@ const showMessageDialog = ref(false)
const isEditMode = ref(false)
const currentMessageId = ref(null)
const state = reactive({
messageTitle: '',
messageContent: '',
messageTitle: "",
messageContent: "",
files: [],
titleError: false,
})
const { messageTitle, messageContent, files, titleError } = toRefs(state)
const getIndentation = (message) => {
let indent = 0
let parent = messages.value.find(m => m.id === message.parentId)
let parent = messages.value.find((m) => m.id === message.parentId)
while (parent) {
indent += 30
parent = messages.value.find(m => m.id === parent.parentId)
parent = messages.value.find((m) => m.id === parent.parentId)
}
return `${indent}px`
}
@ -82,9 +116,10 @@ const fetchMessages = async () => {
const response = await axios.get(`/social-network/group/${groupId}/discussion/${discussionId}/messages`)
messages.value = response.data
} catch (error) {
console.error('Error fetching messages:', error)
console.error("Error fetching messages:", error)
}
}
function openDialogForReply(message) {
isEditMode.value = false
currentMessageId.value = message.id
@ -100,33 +135,33 @@ function openDialogForEdit(message) {
}
async function handleSubmit() {
if (isEditMode.value && title.value.trim() === '') {
if (isEditMode.value && title.value.trim() === "") {
titleError.value = true
return
}
const filesArray = files.value
const formData = new FormData()
formData.append('action', isEditMode.value ? 'edit_message_group' : 'reply_message_group')
formData.append('title', messageTitle.value)
formData.append('content', messageContent.value)
formData.append("action", isEditMode.value ? "edit_message_group" : "reply_message_group")
formData.append("title", messageTitle.value)
formData.append("content", messageContent.value)
if (isEditMode.value) {
formData.append('messageId', currentMessageId.value)
formData.append("messageId", currentMessageId.value)
} else {
formData.append('parentId', currentMessageId.value)
formData.append("parentId", currentMessageId.value)
}
formData.append('userId', user.value.id)
formData.append('groupId', groupInfo.value.id)
formData.append("userId", user.value.id)
formData.append("groupId", groupInfo.value.id)
for (let i = 0; i < filesArray.length; i++) {
formData.append('files[]', filesArray[i])
formData.append("files[]", filesArray[i])
}
try {
await axios.post('/social-network/group-action', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
await axios.post("/social-network/group-action", formData, {
headers: { "Content-Type": "multipart/form-data" },
})
showMessageDialog.value = false
await fetchMessages()
} catch (error) {
console.error('Error submitting the form:', error)
console.error("Error submitting the form:", error)
}
}
@ -137,15 +172,15 @@ const deleteMessage = async (message) => {
return
}
const data = {
action: 'delete_message_group',
action: "delete_message_group",
messageId: message.id,
userId: user.value.id,
groupId: groupInfo.value.id
groupId: groupInfo.value.id,
}
await axios.post('/social-network/group-action', data)
await router.push({ name: 'UserGroupShow', params: { group_id: groupInfo.value.id } })
await axios.post("/social-network/group-action", data)
await router.push({ name: "UserGroupShow", params: { group_id: groupInfo.value.id } })
} catch (error) {
console.error('Error deleting the message:', error)
console.error("Error deleting the message:", error)
}
}
onMounted(() => {

@ -1,33 +1,55 @@
<template>
<div class="group-members">
<div v-if="groupInfo.isModerator" class="edit-members">
<div
v-if="groupInfo.isModerator"
class="edit-members"
>
<BaseButton
label="Edit members list"
type="primary"
class="edit-members-btn"
icon="pi pi-plus"
label="Edit members list"
type="primary"
@click="editMembers"
/>
</div>
<div class="members-grid">
<div class="member-card" v-for="member in members" :key="member.id">
<div
v-for="member in members"
:key="member.id"
class="member-card"
>
<div class="member-avatar">
<img v-if="member.avatar" :src="member.avatar" alt="Member avatar">
<i v-else class="mdi mdi-account-circle-outline"></i>
<img
v-if="member.avatar"
:src="member.avatar"
alt="Member avatar"
/>
<i
v-else
class="mdi mdi-account-circle-outline"
></i>
</div>
<div class="member-name">
{{ member.name }}
<i v-if="member.isAdmin" class="mdi mdi-star-outline admin-icon"></i>
<i
v-if="member.isAdmin"
class="mdi mdi-star-outline admin-icon"
></i>
</div>
<div
v-if="member.role"
class="member-role"
>
{{ member.role }}
</div>
<div class="member-role" v-if="member.role">{{ member.role }}</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { onMounted, ref } from "vue"
import { useRoute } from "vue-router"
import BaseButton from "../basecomponents/BaseButton.vue"
import axios from "axios"
import { useSocialInfo } from "../../composables/useSocialInfo"
@ -40,24 +62,23 @@ const fetchMembers = async (groupId) => {
if (groupId.value) {
try {
const response = await axios.get(`/api/usergroups/${groupId.value}/members`)
members.value = response.data['hydra:member'].map(member => ({
members.value = response.data["hydra:member"].map((member) => ({
id: member.id,
name: member.username,
role: member.relationType === 1 ? 'Admin' : 'Member',
role: member.relationType === 1 ? "Admin" : "Member",
avatar: member.pictureUri,
isAdmin: member.relationType === 1
isAdmin: member.relationType === 1,
}))
} catch (error) {
console.error('Error fetching group members:', error)
console.error("Error fetching group members:", error)
members.value = []
}
}
}
const editMembers = () => {
}
const editMembers = () => {}
onMounted(() => {
if (groupId) {
if (groupId.value) {
fetchMembers(groupId)
}
})

@ -1,7 +1,8 @@
import { ref, readonly, onMounted } from "vue"
import { onMounted, readonly, ref } from "vue"
import { useStore } from "vuex"
import { useRoute } from "vue-router"
import axios from "axios"
export function useSocialInfo() {
const store = useStore()
const route = useRoute()
@ -9,9 +10,9 @@ export function useSocialInfo() {
const isCurrentUser = ref(true)
const groupInfo = ref({
isMember: false,
title: '',
description: '',
role: ''
title: "",
description: "",
role: "",
})
const isGroup = ref(false)
const isLoading = ref(true)
@ -40,7 +41,7 @@ export function useSocialInfo() {
const loadUser = async () => {
try {
if (route.query.id) {
user.value = await store.dispatch("user/load", '/api/users/' + route.query.id)
user.value = await store.dispatch("user/load", "/api/users/" + route.query.id)
isCurrentUser.value = false
} else {
user.value = store.getters["security/getUser"]

@ -1,9 +1,12 @@
import axios from "axios"
/**
* @type {axios.AxiosInstance}
*/
const instance = axios.create({
baseURL: window.location.origin,
headers: {
'Accept': 'application/ld+json',
Accept: "application/ld+json",
},
})

@ -0,0 +1,23 @@
export const USER_UNKNOWN = 0
// export const USER_RELATION_TYPE_UNKNOWN = 1;
// export const USER_RELATION_TYPE_PARENT = 2;
export const USER_RELATION_TYPE_FRIEND = 3
export const USER_RELATION_TYPE_GOODFRIEND = 4
// should be deprecated is useless
// export const USER_RELATION_TYPE_ENEMY = 5; // should be deprecated is useless
export const USER_RELATION_TYPE_DELETED = 6
export const USER_RELATION_TYPE_RRHH = 7
export const USER_RELATION_TYPE_BOSS = 8
export const USER_RELATION_TYPE_HRM_REQUEST = 9
export const USER_RELATION_TYPE_FRIEND_REQUEST = 10

@ -1,19 +1,18 @@
import isEmpty from 'lodash/isEmpty';
import isString from 'lodash/isString';
import isBoolean from 'lodash/isBoolean';
import toInteger from 'lodash/toInteger';
import isEmpty from "lodash/isEmpty"
import isString from "lodash/isString"
import isBoolean from "lodash/isBoolean"
import toInteger from "lodash/toInteger"
import { formatDateTime } from '../utils/dates';
import NotificationMixin from './NotificationMixin';
import {ENTRYPOINT} from "../config/entrypoint";
import axios from "axios";
import { formatDateTime } from "../utils/dates"
import NotificationMixin from "./NotificationMixin"
import axios from "axios"
export default {
mixins: [NotificationMixin],
data() {
return {
pagination: {
sortBy: 'resourceNode.title',
sortBy: "resourceNode.title",
descending: false,
page: 1, // page to be displayed
rowsPerPage: 10, // maximum displayed rows
@ -25,90 +24,91 @@ export default {
expandedFilter: false,
options: {
page: 1,
itemsPerPage: 10
itemsPerPage: 10,
},
};
}
},
watch: {
$route() {
// react to route changes...
this.resetList = true;
let nodeId = this.$route.params['node'];
this.resetList = true
let nodeId = this.$route.params["node"]
if (!isEmpty(nodeId)) {
let cid = toInteger(this.$route.query.cid);
let sid = toInteger(this.$route.query.sid);
let gid = toInteger(this.$route.query.gid);
let id = '/api/resource_nodes/'+ nodeId;
const params = {id, cid, sid, gid};
this.findResourceNode(params);
let cid = toInteger(this.$route.query.cid)
let sid = toInteger(this.$route.query.sid)
let gid = toInteger(this.$route.query.gid)
let id = "/api/resource_nodes/" + nodeId
const params = { id, cid, sid, gid }
this.findResourceNode(params)
}
this.onUpdateOptions(this.options);
this.onUpdateOptions(this.options)
},
deletedItem(item) {
this.showMessage(`${item['@id']} deleted.`);
this.showMessage(`${item["@id"]} deleted.`)
},
deletedResource(item) {
const message = this.$i18n && this.$i18n.t
? this.$t('{resource} created', {'resource': item['resourceNode'].title})
: `${item['resourceNode'].title} created`;
this.showMessage(message);
this.onUpdateOptions(this.options);
const message =
this.$i18n && this.$i18n.t
? this.$t("{resource} created", { resource: item["resourceNode"].title })
: `${item["resourceNode"].title} created`
this.showMessage(message)
this.onUpdateOptions(this.options)
},
error(message) {
message && this.showError(message);
message && this.showError(message)
},
items() {
this.options.totalItems = this.totalItems;
}
this.options.totalItems = this.totalItems
},
},
methods: {
onRequest(props) {
console.log('onRequest');
console.log(props);
const { page, rowsPerPage: itemsPerPage, sortBy, descending } = props.pagination;
const filter = props.filter;
console.log("onRequest")
console.log(props)
const { page, rowsPerPage: itemsPerPage, sortBy, descending } = props.pagination
const filter = props.filter
this.nextPage = page;
this.nextPage = page
if (isEmpty(this.nextPage)) {
this.nextPage = 1;
this.nextPage = 1
}
let params = {};
let params = {}
if (itemsPerPage > 0) {
params = { ...params, itemsPerPage, page };
params = { ...params, itemsPerPage, page }
}
if (sortBy) {
params[`order[${sortBy}]`] = descending ? "desc" : "asc";
params[`order[${sortBy}]`] = descending ? "desc" : "asc"
}
if (this.$route.params.node) {
params[`resourceNode.parent`] = this.$route.params.node;
params[`resourceNode.parent`] = this.$route.params.node
}
this.resetList = true;
this.resetList = true
this.getPage(params).then(() => {
this.pagination.sortBy = sortBy;
this.pagination.descending = descending;
this.pagination.rowsPerPage = itemsPerPage;
});
this.pagination.sortBy = sortBy
this.pagination.descending = descending
this.pagination.rowsPerPage = itemsPerPage
})
},
onUpdateOptions({ page, itemsPerPage, sortBy, sortDesc, totalItems } = {}) {
console.log('ListMixin.js: onUpdateOptions');
console.log("ListMixin.js: onUpdateOptions")
this.resetList = true;
this.resetList = true
let params = {
...this.filters
...this.filters,
}
if (1 === this.filters['loadNode']) {
params[`resourceNode.parent`] = this.$route.params.node;
if (1 === this.filters["loadNode"]) {
params[`resourceNode.parent`] = this.$route.params.node
}
/*if (this.$route.params.node) {
@ -116,170 +116,165 @@ export default {
}*/
if (itemsPerPage > 0) {
params = { ...params, itemsPerPage, page };
params = { ...params, itemsPerPage, page }
}
// prime
if (!isEmpty(sortBy)) {
params[`order[${sortBy}]`] = sortDesc ? 'desc' : 'asc'
params[`order[${sortBy}]`] = sortDesc ? "desc" : "asc"
}
let cid = toInteger(this.$route.query.cid);
let sid = toInteger(this.$route.query.sid);
let gid = toInteger(this.$route.query.gid);
let type = this.$route.query.type;
let cid = toInteger(this.$route.query.cid)
let sid = toInteger(this.$route.query.sid)
let gid = toInteger(this.$route.query.gid)
let type = this.$route.query.type
params = { ...params, cid, sid, gid, type };
params = { ...params, cid, sid, gid, type }
/*if (!isEmpty(sortBy) && !isEmpty(sortDesc)) {
params[`order[${sortBy[0]}]`] = sortDesc[0] ? 'desc' : 'asc'
}*/
this.getPage(params).then(() => {
this.options.sortBy = sortBy;
this.options.sortDesc = sortDesc;
this.options.itemsPerPage = itemsPerPage;
this.options.totalItems = totalItems;
});
this.options.sortBy = sortBy
this.options.sortDesc = sortDesc
this.options.itemsPerPage = itemsPerPage
this.options.totalItems = totalItems
})
},
fetchNewItems({ page, itemsPerPage, sortBy, sortDesc, totalItems } = {}) {
console.log('fetchNewItems');
console.log("fetchNewItems")
let params = {
...this.filters
};
...this.filters,
}
if (itemsPerPage > 0) {
params = { ...params, itemsPerPage, page };
params = { ...params, itemsPerPage, page }
}
if (this.$route.params.node) {
params[`resourceNode.parent`] = this.$route.params.node;
params[`resourceNode.parent`] = this.$route.params.node
}
if (isString(sortBy) && isBoolean(sortDesc)) {
//params[`order[${sortBy[0]}]`] = sortDesc[0] ? 'desc' : 'asc'
params[`order[${sortBy}]`] = sortDesc ? 'desc' : 'asc'
params[`order[${sortBy}]`] = sortDesc ? "desc" : "asc"
}
this.options.sortBy = sortBy;
this.options.sortDesc = sortDesc;
this.options.itemsPerPage = itemsPerPage;
this.options.totalItems = totalItems;
this.options.sortBy = sortBy
this.options.sortDesc = sortDesc
this.options.itemsPerPage = itemsPerPage
this.options.totalItems = totalItems
},
onSendFilter() {
console.log('onSendFilter');
this.resetList = true;
this.pagination.page = 1;
console.log("onSendFilter")
this.resetList = true
this.pagination.page = 1
this.onRequest({ pagination: this.pagination })
},
resetFilter() {
console.log('resetFilter');
this.filters = {};
this.pagination.page = 1;
console.log("resetFilter")
this.filters = {}
this.pagination.page = 1
this.onRequest({ pagination: this.pagination })
},
addHandler() {
console.log('addHandler');
let folderParams = this.$route.query;
this.$router.push({name: `${this.$options.servicePrefix}Create`, query: folderParams});
console.log("addHandler")
let folderParams = this.$route.query
this.$router.push({ name: `${this.$options.servicePrefix}Create`, query: folderParams })
},
addDocumentHandler() {
let folderParams = this.$route.query;
this.$router.push({ name: `${this.$options.servicePrefix}CreateFile` , query: folderParams});
let folderParams = this.$route.query
this.$router.push({ name: `${this.$options.servicePrefix}CreateFile`, query: folderParams })
},
uploadDocumentHandler() {
let folderParams = this.$route.query;
this.$router.push({ name: `${this.$options.servicePrefix}UploadFile` , query: folderParams});
let folderParams = this.$route.query
this.$router.push({ name: `${this.$options.servicePrefix}UploadFile`, query: folderParams })
},
sharedDocumentHandler() {
let folderParams = this.$route.query;
this.filters['shared'] = 1;
this.filters['loadNode'] = 0;
delete this.filters['resourceNode.parent'];
this.resetList = true;
this.$router.push({ name: `${this.$options.servicePrefix}Shared` , query: folderParams});
let folderParams = this.$route.query
this.filters["shared"] = 1
this.filters["loadNode"] = 0
delete this.filters["resourceNode.parent"]
this.resetList = true
this.$router.push({ name: `${this.$options.servicePrefix}Shared`, query: folderParams })
},
showHandler(item) {
console.log('listmixin showHandler');
let folderParams = this.$route.query;
console.log(item);
console.log("listmixin showHandler")
let folderParams = this.$route.query
console.log(item)
if (item) {
folderParams['id'] = item['@id'];
folderParams["id"] = item["@id"]
}
this.$router.push({
name: `${this.$options.servicePrefix}Show`,
params: folderParams,
query: folderParams
});
query: folderParams,
})
},
handleClick(item) {
let folderParams = this.$route.query;
this.resetList = true;
let resourceId = item['resourceNode']['id'];
this.$route.params.node = resourceId;
let folderParams = this.$route.query
this.resetList = true
let resourceId = item["resourceNode"]["id"]
this.$route.params.node = resourceId
this.filters[`resourceNode.parent`] = resourceId;
this.filters[`resourceNode.parent`] = resourceId
this.$router.push({
name: `${this.$options.servicePrefix}List`,
params: { node: resourceId },
query: folderParams,
});
})
},
changeVisibilityHandler(item, slotProps) {
let folderParams = this.$route.query;
folderParams['id'] = item['@id'];
axios
.put(item['@id'] + '/toggle_visibility', {
})
.then(response => {
let data = response.data;
item['resourceLinkListFromEntity'] = data['resourceLinkListFromEntity'];
let folderParams = this.$route.query
folderParams["id"] = item["@id"]
axios.put(item["@id"] + "/toggle_visibility", {}).then((response) => {
let data = response.data
item["resourceLinkListFromEntity"] = data["resourceLinkListFromEntity"]
})
;
},
editHandler(item) {
let folderParams = this.$route.query;
folderParams['id'] = item['@id'];
let folderParams = this.$route.query
folderParams["id"] = item["@id"]
if ('folder' === item.filetype || isEmpty(item.filetype)) {
if ("folder" === item.filetype || isEmpty(item.filetype)) {
this.$router.push({
name: `${this.$options.servicePrefix}Update`,
params: { id: item['@id'] },
query: folderParams
});
params: { id: item["@id"] },
query: folderParams,
})
}
if ('file' === item.filetype) {
folderParams['getFile'] = true;
if (item.resourceNode.resourceFile &&
if ("file" === item.filetype) {
folderParams["getFile"] = true
if (
item.resourceNode.resourceFile &&
item.resourceNode.resourceFile.mimeType &&
'text/html' === item.resourceNode.resourceFile.mimeType
"text/html" === item.resourceNode.resourceFile.mimeType
) {
//folderParams['getFile'] = true;
}
this.$router.push({
name: `${this.$options.servicePrefix}UpdateFile`,
params: { id: item['@id'] },
query: folderParams
});
params: { id: item["@id"] },
query: folderParams,
})
}
},
deleteHandler(item) {
this.pagination.page = 1;
this.deleteItem(item).then(() =>
this.onRequest({pagination: this.pagination})
);
this.pagination.page = 1
this.deleteItem(item).then(() => this.onRequest({ pagination: this.pagination }))
},
formatDateTime,
},
formatDateTime
}
};

@ -1,4 +1,4 @@
import axios from "axios"
import api from "../config/api"
export default {
/**
@ -6,7 +6,7 @@ export default {
* @returns {Promise<void>}
*/
registerCampus: async (doNotListCampus) => {
await axios.post("/admin/register-campus", {
await api.post("/admin/register-campus", {
donotlistcampus: doNotListCampus,
})
},
@ -15,7 +15,7 @@ export default {
* @returns {Promise<string>}
*/
findAnnouncements: async () => {
const { data } = await axios.get("/main/inc/ajax/admin.ajax.php?a=get_latest_news")
const { data } = await api.get("/main/inc/ajax/admin.ajax.php?a=get_latest_news")
return data
},
@ -24,7 +24,7 @@ export default {
* @returns {Promise<string>}
*/
findVersion: async () => {
const { data } = await axios.get("/main/inc/ajax/admin.ajax.php?a=version")
const { data } = await api.get("/main/inc/ajax/admin.ajax.php?a=version")
return data
},
@ -42,7 +42,7 @@ export default {
* @returns {Promise<Object>}
*/
findBlocks: async () => {
const { data } = await axios.get("/admin/index")
const { data } = await api.get("/admin/index")
return data
},

@ -1,15 +1,62 @@
import api from "../config/api"
export default {
/**
* @param {string} iri
* @returns {Promise<axios.AxiosResponse<any>>}
* @param {Object} [params]
* @returns {Promise<Object>}
*/
async function find(iri) {
return await api.get(iri)
}
async get(iri, params = {}) {
const { data } = await api.get(iri, {
params,
})
return data
},
/**
* @param {string} endpoint
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async getCollection(endpoint, searchParams = {}) {
const { data } = await api.get(endpoint, {
params: searchParams,
})
async function post(params) {
return await api.post("/api/resource_links", params)
return {
totalItems: data.totalItems,
items: data["hydra:member"],
}
},
/**
* @param {string} endpoint
* @param {Object} [params]
* @returns {Promise<Object>}
*/
async post(endpoint, params) {
const { data } = await api.post(endpoint, params)
return data
},
export { find, post }
/**
* @param {string} endpoint
* @param {Object} params
* @returns {Promise<Object>}
*/
async postForm(endpoint, params) {
const { data } = await api.postForm(endpoint, params)
return data
},
/**
* @param {string} iri
* @returns {Promise<void>}
*/
async delete(iri) {
await api.delete(iri)
},
}

@ -0,0 +1,33 @@
import baseService from "./baseService"
/**
* @param {number} cId
* @param {Object} params
* @returns {Promise<Object>}
*/
async function findCourseHomeInro(cId, params) {
return await baseService.get(`/course/${cId}/getToolIntro`, params)
}
/**
* @param {number} cId
* @param {Object} params
* @returns {Promise<Object>}
*/
async function addToolIntro(cId, params) {
return baseService.post(`/course/${cId}/addToolIntro`, params)
}
/**
* @param {number} toolIntroId
* @returns {Promise<Object>}
*/
async function findById(toolIntroId) {
return await baseService.get(`/api/c_tool_intros/${toolIntroId}`)
}
export default {
findCourseHomeInro,
addToolIntro,
findById,
}

@ -0,0 +1,13 @@
import baseService from "./baseService"
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams) {
return await baseService.getCollection("/api/course_rel_users", searchParams)
}
export default {
findAll,
}

@ -1,27 +1,28 @@
import { ENTRYPOINT } from "../config/entrypoint"
import axios from "axios"
import api from "../config/api"
import baseService from "./baseService"
export default {
find: baseService.get,
const API_URL = '/course';
const courseService = {
/**
* @param {number} courseId
* @param {number=} sessionId
* @returns {Promise<Object>}
*/
loadTools: async (courseId, sessionId = 0) => {
const { data } = await axios.get(ENTRYPOINT + `../course/${courseId}/home.json?sid=${sessionId}`)
const { data } = await api.get(`/course/${courseId}/home.json?sid=${sessionId}`)
return data
},
loadCTools: async (courseId, sessionId = 0) => {
const { data } = await axios.get(ENTRYPOINT + 'c_tools', {
const { data } = await api.get("/api/c_tools", {
params: {
cid: courseId,
sid: sessionId,
order: {
position: 'asc'
}
position: "asc",
},
},
})
@ -36,13 +37,10 @@ const courseService = {
* @returns {Promise<Object>}
*/
updateToolOrder: async (tool, newIndex, courseId, sessionId = 0) => {
const { data } = await axios.post(
ENTRYPOINT + `../course/${courseId}/home.json?sid=${sessionId}`,
{
const { data } = await api.post(`/course/${courseId}/home.json?sid=${sessionId}`, {
index: newIndex,
toolItem: tool,
}
)
})
return data
},
@ -53,14 +51,11 @@ const courseService = {
* @returns {Promise<{Object}>}
*/
loadHomeIntro: async (courseId, sessionId = 0) => {
const { data } = await axios.get(
ENTRYPOINT + `../course/${courseId}/getToolIntro`,
{
const { data } = await api.get(`/course/${courseId}/getToolIntro`, {
params: {
sid: sessionId,
},
}
)
})
return data
},
@ -71,14 +66,11 @@ const courseService = {
* @returns {Promise<Object>}
*/
checkLegal: async (courseId, sessionId = 0) => {
const { data } = await axios.get(
`${API_URL}/${courseId}/checkLegal.json`,
{
const { data } = await api.get(`/course/${courseId}/checkLegal.json`, {
params: {
sid: sessionId,
},
}
)
})
return data
},
@ -89,9 +81,10 @@ const courseService = {
* @returns {Promise<Object>} The server response after creating the course.
*/
createCourse: async (courseData) => {
const response = await axios.post(`${API_URL}/create`, courseData);
console.log('response create ::', response);
return response.data;
const response = await api.post(`/course/create`, courseData)
console.log("response create ::", response)
return response.data
},
/**
@ -99,8 +92,9 @@ const courseService = {
* @returns {Promise<Array>} A list of available categories.
*/
getCategories: async () => {
const response = await axios.get(`${API_URL}/categories`);
return response.data;
const response = await api.get(`/course/categories`)
return response.data
},
/**
@ -109,14 +103,13 @@ const courseService = {
* @returns {Promise<Array>} A list of templates matching the search term.
*/
searchTemplates: async (searchTerm) => {
const response = await axios.get(`${API_URL}/search_templates`, {
params: { search: searchTerm }
});
return response.data.items.map(item => ({
const response = await api.get(`/course/search_templates`, {
params: { search: searchTerm },
})
return response.data.items.map((item) => ({
name: item.name,
value: item.id
}));
value: item.id,
}))
},
}
export default courseService

@ -7,6 +7,7 @@ export default {
*/
getGlossaryTerms: async (params) => {
const response = await axios.get(ENTRYPOINT + "glossaries", { params })
return response.data
},
@ -15,6 +16,7 @@ export default {
*/
getGlossaryTerm: async (termId) => {
const response = await axios.get(ENTRYPOINT + `glossaries/${termId}`)
return response.data
},
@ -23,6 +25,7 @@ export default {
*/
createGlossaryTerm: async (data) => {
const response = await axios.post(ENTRYPOINT + `glossaries`, data)
return response.data
},
@ -32,6 +35,7 @@ export default {
*/
updateGlossaryTerm: async (termId, data) => {
const response = await axios.put(ENTRYPOINT + `glossaries/${termId}`, data)
return response.data
},
@ -41,6 +45,7 @@ export default {
export: async (formData) => {
const endpoint = `${ENTRYPOINT}glossaries/export`
const response = await axios.post(endpoint, formData, { responsetype: "blob " })
return response.data
},
@ -54,6 +59,7 @@ export default {
"Content-Type": "multipart/form-data",
},
})
return response.data
},
@ -63,6 +69,7 @@ export default {
exportToDocuments: async (data) => {
const endpoint = `${ENTRYPOINT}glossaries/export_to_documents`
const response = await axios.post(endpoint, data)
return response.data
},
@ -72,6 +79,7 @@ export default {
deleteTerm: async (termId) => {
const endpoint = `${ENTRYPOINT}glossaries/${termId}`
const response = await axios.delete(endpoint)
return response.data
},
}

@ -1,13 +1,13 @@
import {ENTRYPOINT} from "../config/entrypoint";
import axios from "axios";
import { ENTRYPOINT } from "../config/entrypoint"
import axios from "axios"
export default {
/**
* @param {Object} params
*/
getLinks: async (params) => {
const response = await axios.get(ENTRYPOINT + 'links/', {params})
const response = await axios.get(ENTRYPOINT + "links/", { params })
return response.data
},
@ -15,7 +15,8 @@ export default {
* @param {Number|String} linkId
*/
getLink: async (linkId) => {
const response = await axios.get(ENTRYPOINT + 'links/' + linkId + '/details/')
const response = await axios.get(ENTRYPOINT + "links/" + linkId + "/details/")
return response.data
},
@ -26,6 +27,7 @@ export default {
const endpoint = `${ENTRYPOINT}links`
const response = await axios.post(endpoint, data)
return response.data
},
@ -35,9 +37,10 @@ export default {
*/
updateLink: async (linkId, data) => {
const endpoint = `${ENTRYPOINT}links/${linkId}`
data.id = linkId;
data.id = linkId
const response = await axios.put(endpoint, data)
return response.data
},
@ -48,6 +51,7 @@ export default {
toggleLinkVisibility: async (linkId, visible) => {
const endpoint = `${ENTRYPOINT}links/${linkId}/toggle_visibility`
const response = await axios.put(endpoint, { visible })
return response.data
},
@ -56,8 +60,9 @@ export default {
* @param {Number} position
*/
moveLink: async (linkId, position) => {
const endpoint = `${ENTRYPOINT}links/${linkId}/move`;
const endpoint = `${ENTRYPOINT}links/${linkId}/move`
const response = await axios.put(endpoint, { position })
return response.data
},
@ -67,19 +72,22 @@ export default {
deleteLink: async (linkId) => {
const endpoint = `${ENTRYPOINT}links/${linkId}`
const response = await axios.delete(endpoint)
return response.data
},
getCategories: async (parentId) => {
const response = await axios.get(`${ENTRYPOINT}link_categories?resourceNode.parent=${parentId}`)
return response.data['hydra:member']
return response.data["hydra:member"]
},
/**
* @param {Number|String} categoryId
*/
getCategory: async (categoryId) => {
const response = await axios.get(ENTRYPOINT + 'link_categories/' + categoryId)
const response = await axios.get(ENTRYPOINT + "link_categories/" + categoryId)
return response.data
},
@ -89,6 +97,7 @@ export default {
createCategory: async (data) => {
const endpoint = `${ENTRYPOINT}link_categories`
const response = await axios.post(endpoint, data)
return response.data
},
@ -99,6 +108,7 @@ export default {
updateCategory: async (categoryId, data) => {
const endpoint = `${ENTRYPOINT}link_categories/${categoryId}`
const response = await axios.put(endpoint, data)
return response.data
},
@ -108,6 +118,7 @@ export default {
deleteCategory: async (categoryId) => {
const endpoint = `${ENTRYPOINT}link_categories/${categoryId}`
const response = await axios.delete(endpoint)
return response.data
},
@ -116,8 +127,9 @@ export default {
* @param {Boolean} visible
*/
toggleCategoryVisibility: async (categoryId, visible) => {
const endpoint = `${ENTRYPOINT}link_categories/${categoryId}/toggle_visibility`;
const endpoint = `${ENTRYPOINT}link_categories/${categoryId}/toggle_visibility`
const response = await axios.put(endpoint, { visible })
return response.data
},
@ -127,8 +139,9 @@ export default {
* @param linkId
*/
checkLink: async (url, linkId) => {
const endpoint = `${ENTRYPOINT}links/${linkId}/check`;
const response = await axios.get(endpoint, { params: { url } });
return response.data;
const endpoint = `${ENTRYPOINT}links/${linkId}/check`
const response = await axios.get(endpoint, { params: { url } })
return response.data
},
}

@ -0,0 +1,26 @@
import baseService from "./baseService"
/**
* @param {Object} params
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(params) {
return await baseService.getCollection("/api/message_tags", params)
}
/**
* @param {string} userIri
* @param {string} searchTerm
* @returns {Promise<{totalItems, items}>}
*/
async function searchUserTags(userIri, searchTerm) {
return await findAll({
user: userIri,
tag: searchTerm,
})
}
export default {
findAll,
searchUserTags,
}

@ -3,8 +3,8 @@ import axios from "axios"
export default {
findAll: async () => {
const response = await axios.get(ENTRYPOINT + 'page_categories')
const response = await axios.get(ENTRYPOINT + "page_categories")
return response.data['hydra:member']
return response.data["hydra:member"]
},
}

@ -0,0 +1,11 @@
import api from "../config/api"
async function toogleStudentView() {
const { data } = await api.get("/toggle_student_view")
return data
}
export default {
toogleStudentView,
}

@ -1,5 +1,9 @@
import { post } from "./baseService"
import api from "../config/api"
export default {
post,
async create(params) {
const { data } = await api.post("/api/resource_links", params)
return data
},
}

@ -0,0 +1,13 @@
import baseService from "./baseService"
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams) {
return await baseService.getCollection("/api/session_rel_course_rel_users", searchParams)
}
export default {
findAll,
}

@ -1,8 +1,9 @@
import { ENTRYPOINT } from "../config/entrypoint"
import axios from "axios"
import baseService from "./baseService"
const sessionRelUserService = {
findAll: (params) => axios.get(ENTRYPOINT + "session_rel_users", { params }).then((response) => response.data),
async function findAll(params) {
return await baseService.getCollection("/api/session_rel_users", params)
}
export default sessionRelUserService
export default {
findAll,
}

@ -1,5 +1,13 @@
import { find } from "./baseService"
import api from "../config/api"
export default {
find
/**
* @param {string} iri
* @returns {Promise<Object>}
*/
async find(iri) {
const { data } = await api.get(iri)
return data
},
}

@ -1,35 +1,55 @@
import axios from 'axios';
import axios from "axios"
import baseService from "./baseService"
const API_URL = '/social-network';
const API_URL = "/social-network"
/**
* @param {string} postIri
* @returns {Promise<Object>}
*/
async function sendPostLike(postIri) {
return baseService.post(`${postIri}/like`)
}
/**
* @param {string} postIri
* @returns {Promise<Object>}
*/
async function sendPostDislike(postIri) {
return baseService.post(`${postIri}/dislike`)
}
export default {
async fetchPersonalData(userId) {
try {
const response = await axios.get(`${API_URL}/personal-data/${userId}`);
return response.data.personalData;
const response = await axios.get(`${API_URL}/personal-data/${userId}`)
return response.data.personalData
} catch (error) {
console.error('Error fetching personal data:', error);
throw error;
console.error("Error fetching personal data:", error)
throw error
}
},
async fetchTermsAndConditions(userId) {
try {
const response = await axios.get(`${API_URL}/terms-and-conditions/${userId}`);
return response.data.terms;
const response = await axios.get(`${API_URL}/terms-and-conditions/${userId}`)
return response.data.terms
} catch (error) {
console.error('Error fetching terms and conditions:', error);
throw error;
console.error("Error fetching terms and conditions:", error)
throw error
}
},
async fetchLegalStatus(userId) {
try {
const response = await axios.get(`${API_URL}/legal-status/${userId}`);
return response.data;
const response = await axios.get(`${API_URL}/legal-status/${userId}`)
return response.data
} catch (error) {
console.error('Error fetching legal status:', error);
throw error;
console.error("Error fetching legal status:", error)
throw error
}
},
@ -39,11 +59,12 @@ export default {
explanation,
userId,
requestType,
});
return response.data;
})
return response.data
} catch (error) {
console.error('Error submitting privacy request:', error);
throw error;
console.error("Error submitting privacy request:", error)
throw error
}
},
@ -51,21 +72,23 @@ export default {
try {
const response = await axios.post(`${API_URL}/send-legal-term`, {
userId,
});
return response.data;
})
return response.data
} catch (error) {
console.error('Error accepting the term:', error);
throw error;
console.error("Error accepting the term:", error)
throw error
}
},
async fetchInvitations(userId) {
try {
const response = await axios.get(`${API_URL}/invitations/${userId}`);
return response.data;
const response = await axios.get(`${API_URL}/invitations/${userId}`)
return response.data
} catch (error) {
console.error('Error fetching invitations:', error);
throw error;
console.error("Error fetching invitations:", error)
throw error
}
},
@ -74,13 +97,14 @@ export default {
const response = await axios.post(`${API_URL}/user-action`, {
userId,
targetUserId,
action: 'add_friend',
action: "add_friend",
is_my_friend: true,
});
return response.data;
})
return response.data
} catch (error) {
console.error('Error accepting invitation:', error);
throw error;
console.error("Error accepting invitation:", error)
throw error
}
},
@ -89,12 +113,13 @@ export default {
const response = await axios.post(`${API_URL}/user-action`, {
userId,
targetUserId,
action: 'deny_friend',
});
return response.data;
action: "deny_friend",
})
return response.data
} catch (error) {
console.error('Error denying invitation:', error);
throw error;
console.error("Error denying invitation:", error)
throw error
}
},
@ -103,12 +128,13 @@ export default {
const response = await axios.post(`${API_URL}/group-action`, {
userId,
groupId,
action: 'accept',
});
return response.data;
action: "accept",
})
return response.data
} catch (error) {
console.error('Error accepting group invitation:', error);
throw error;
console.error("Error accepting group invitation:", error)
throw error
}
},
@ -117,12 +143,13 @@ export default {
const response = await axios.post(`${API_URL}/group-action`, {
userId,
groupId,
action: 'deny',
});
return response.data;
action: "deny",
})
return response.data
} catch (error) {
console.error('Error denying group invitation:', error);
throw error;
console.error("Error denying group invitation:", error)
throw error
}
},
@ -131,12 +158,19 @@ export default {
const response = await axios.post(`${API_URL}/group-action`, {
userId,
groupId,
action: 'join',
});
return response.data;
action: "join",
})
return response.data
} catch (error) {
console.error('Error joining the group:', error);
throw error;
console.error("Error joining the group:", error)
throw error
}
},
};
sendPostLike,
sendPostDislike,
delete: baseService.delete,
}

@ -0,0 +1,50 @@
import baseService from "./baseService"
import { USER_RELATION_TYPE_FRIEND, USER_RELATION_TYPE_FRIEND_REQUEST } from "../constants/entity/userreluser"
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams = {}) {
return await baseService.getCollection("/api/user_rel_users", searchParams)
}
/**
* @param {string} userIri
* @returns {Promise<Array<Object>>}
*/
async function getFriendList(userIri) {
const { items } = await findAll({
user: userIri,
relationType: [USER_RELATION_TYPE_FRIEND, USER_RELATION_TYPE_FRIEND_REQUEST],
})
return items
}
async function sendFriendRequest(userIri, friendIri) {
return await baseService.post("/api/user_rel_users", {
user: userIri,
friend: friendIri,
relationType: USER_RELATION_TYPE_FRIEND_REQUEST,
})
}
/**
* @param {string} userIri
* @param {string} searchTerm
* @returns {Promise<{totalItems, items}>}
*/
async function searchRelationshipByUsername(userIri, searchTerm) {
return await findAll({
user: userIri,
"friend.username": searchTerm,
})
}
export default {
findAll,
getFriendList,
sendFriendRequest,
searchRelationshipByUsername,
}

@ -0,0 +1,31 @@
import baseService from "./baseService"
/**
* @param {string} userIri
* @returns {Promise<Object>}
*/
async function find(userIri) {
return await baseService.get(userIri)
}
/**
* @param {Object} searchParams
* @returns {Promise<{totalItems, items}>}
*/
async function findAll(searchParams) {
return await baseService.getCollection("/api/users", searchParams)
}
/**
* @param {string} username
* @returns {Promise<{totalItems, items}>}
*/
async function findByUsername(username) {
return await baseService.getCollection("/api/users", { username })
}
export default {
find,
findAll,
findByUsername,
}

@ -1,25 +1,57 @@
import axios from "axios"
import baseService from "./baseService"
export default {
/**
* @param {string} searchTerm
* @returns {Promise<Object>} { totalItems, items }
* @returns {Promise<{totalItems, items}>}
*/
search: async (searchTerm) => {
const response = {}
try {
const { data } = await axios.get("/api/usergroups/search", {
params: { search: searchTerm },
return await baseService.getCollection("/api/usergroups/search", {
search: searchTerm,
})
},
response.totalItems = data["hydra:totalItems"]
response.items = data["hydra:member"]
} catch {
response.totalItems = 0
response.items = []
}
/**
* @param {Object} params
* @returns {Promise<Object>}
*/
async createGroup(params) {
return await baseService.post("/api/usergroups", params)
},
/**
* @param {number} groupId
* @param {Object} params
* @returns {Promise<Object>}
*/
async uploadPicture(groupId, params) {
return await baseService.postForm(`/social-network/upload-group-picture/${groupId}`, params)
},
/**
* @returns {Promise<Array>}
*/
async listNewest() {
const { items } = await baseService.getCollection("/api/usergroup/list/newest")
return items
},
/**
* @returns {Promise<Array>}
*/
async listPopular() {
const { items } = await baseService.getCollection("/api/usergroup/list/popular")
return items
},
/**
* @returns {Promise<Array>}
*/
async listMine() {
const { items } = await baseService.getCollection("/api/usergroup/list/my")
return response
return items
},
}

@ -1,6 +1,6 @@
import { defineStore } from "pinia"
import { usePlatformConfig } from "./platformConfig"
import courseService from "../services/course"
import courseService from "../services/courseService"
import sessionService from "../services/sessionService"
import { computed, ref } from "vue"
@ -56,7 +56,7 @@ export const useCidReqStore = defineStore("cidReq", () => {
return
}
course.value = await courseService.find(iri, { sid }).then((response) => response.json())
course.value = await courseService.find(iri, { sid })
}
const setSessionByIri = async (iri) => {
@ -64,9 +64,7 @@ export const useCidReqStore = defineStore("cidReq", () => {
return
}
const { data } = await sessionService.find(iri);
session.value = data
session.value = await sessionService.find(iri)
}
const setCourseAndSessionByIri = async (courseIri, sId = 0) => {

@ -1,6 +1,6 @@
import { defineStore } from "pinia"
import axios from "axios"
import { computed, ref } from "vue"
import { ref } from "vue"
export const useEnrolledStore = defineStore("enrolledStore", () => {
// Reactive state to track if the user is enrolled in courses or sessions
@ -11,7 +11,7 @@ export const useEnrolledStore = defineStore("enrolledStore", () => {
async function checkEnrollments() {
try {
const { data } = await axios.get("/course/check-enrollments")
console.log('Check enrollments data:', data)
console.log("Check enrollments data:", data)
isEnrolledInCourses.value = data.isEnrolledInCourses
isEnrolledInSessions.value = data.isEnrolledInSessions
} catch (error) {
@ -28,6 +28,6 @@ export const useEnrolledStore = defineStore("enrolledStore", () => {
// Computed properties for reactivity
isEnrolledInCourses,
isEnrolledInSessions,
initialize
};
});
initialize,
}
})

@ -1,15 +1,15 @@
import { defineStore } from "pinia";
import axios from "axios";
import { defineStore } from "pinia"
import axios from "axios"
import { computed, ref } from "vue"
export const usePlatformConfig = defineStore("platformConfig", () => {
const isLoading = ref(false);
const settings = ref([]);
const studentView = ref('teacherview');
const isLoading = ref(false)
const settings = ref([])
const studentView = ref("teacherview")
const plugins = ref([])
async function findSettingsRequest() {
isLoading.value = true;
isLoading.value = true
try {
const { data } = await axios.get("/platform-config/list")
@ -27,7 +27,7 @@ export const usePlatformConfig = defineStore("platformConfig", () => {
}
async function initialize() {
await findSettingsRequest();
await findSettingsRequest()
}
const getSetting = computed(
@ -44,5 +44,5 @@ export const usePlatformConfig = defineStore("platformConfig", () => {
initialize,
getSetting,
isStudentViewActive,
};
});
}
})

@ -1,8 +1,7 @@
import { defineStore } from 'pinia'
import { defineStore } from "pinia"
import axios from "axios"
export const useSocialStore = defineStore('social', {
export const useSocialStore = defineStore("social", {
state: () => ({
showFullProfile: false,
}),
@ -16,7 +15,7 @@ export const useSocialStore = defineStore('social', {
console.error("Error checking user relation:", error)
this.showFullProfile = false
}
}
},
},
getters: {

@ -14,13 +14,13 @@
/>
<BaseColorPicker
v-model="colorPrimaryButtonText"
:label="t('Primary color button text')"
:error="colorPrimaryButtonTextError"
:label="t('Primary color button text')"
/>
<BaseColorPicker
v-model="colorPrimaryButtonAlternativeText"
:label="t('Primary color button alternative text')"
:error="colorPrimaryButtonAlternativeTextError"
:label="t('Primary color button alternative text')"
/>
</div>
@ -35,8 +35,8 @@
/>
<BaseColorPicker
v-model="colorSecondaryButtonText"
:label="t('Secondary color button text')"
:error="colorSecondaryButtonTextError"
:label="t('Secondary color button text')"
/>
</div>
@ -62,8 +62,8 @@
/>
<BaseColorPicker
v-model="colorSuccessButtonText"
:label="t('Success color button text')"
:error="colorSuccessButtonTextError"
:label="t('Success color button text')"
/>
</div>
@ -78,8 +78,8 @@
/>
<BaseColorPicker
v-model="colorInfoButtonText"
:label="t('Info color button text')"
:error="colorInfoButtonTextError"
:label="t('Info color button text')"
/>
</div>
@ -94,8 +94,8 @@
/>
<BaseColorPicker
v-model="colorWarningButtonText"
:label="t('Warning color button text')"
:error="colorWarningButtonTextError"
:label="t('Warning color button text')"
/>
</div>
@ -164,15 +164,15 @@
<div class="flex flex-wrap mb-4 gap-3">
<BaseButton
type="primary"
icon="send"
:label="t('Save')"
icon="send"
type="primary"
@click="saveColors"
/>
<BaseButton
type="black"
icon="cog"
:label="isAdvancedMode ? t('Hide advanced mode') : t('Show advanced mode')"
icon="cog"
type="black"
@click="isAdvancedMode = !isAdvancedMode"
/>
</div>
@ -184,69 +184,69 @@
<p class="mb-3 text-lg">{{ t("Buttons") }}</p>
<div class="flex flex-row flex-wrap mb-3">
<BaseButton
class="mr-2 mb-2"
:label="t('Primary')"
type="primary"
class="mr-2 mb-2"
icon="eye-on"
type="primary"
/>
<BaseButton
class="mr-2 mb-2"
:label="t('Primary alternative')"
type="primary-alternative"
class="mr-2 mb-2"
icon="eye-on"
type="primary-alternative"
/>
<BaseButton
class="mr-2 mb-2"
:label="t('Secondary')"
type="secondary"
class="mr-2 mb-2"
icon="eye-on"
type="secondary"
/>
<BaseButton
class="mr-2 mb-2"
:label="t('Tertiary')"
type="black"
class="mr-2 mb-2"
icon="eye-on"
type="black"
/>
</div>
<div class="flex flex-row flex-wrap mb-3">
<BaseButton
class="mr-2 mb-2"
:label="t('Success')"
type="success"
class="mr-2 mb-2"
icon="send"
type="success"
/>
<BaseButton
class="mr-2 mb-2"
:label="t('Info')"
type="info"
class="mr-2 mb-2"
icon="send"
type="info"
/>
<BaseButton
class="mr-2 mb-2"
:label="t('Warning')"
type="warning"
class="mr-2 mb-2"
icon="send"
type="warning"
/>
<BaseButton
class="mr-2 mb-2"
:label="t('Danger')"
type="danger"
class="mr-2 mb-2"
icon="delete"
type="danger"
/>
</div>
<div class="flex flex-row flex-wrap mb-3">
<BaseButton
class="mr-2 mb-2"
:label="t('Disabled')"
type="primary"
icon="eye-on"
class="mr-2 mb-2"
disabled
icon="eye-on"
type="primary"
/>
<BaseButton
class="mr-2 mb-2"
type="primary"
icon="cog"
only-icon
type="primary"
/>
</div>
</div>
@ -256,10 +256,10 @@
<div class="flex flex-row gap-3">
<BaseButton
class="mr-3 mb-2"
type="primary"
icon="cog"
popup-identifier="menu"
only-icon
popup-identifier="menu"
type="primary"
@click="toggle"
/>
<BaseMenu
@ -269,10 +269,6 @@
/>
<BaseDropdown
v-model="dropdown"
class="w-36"
input-id="dropdown"
option-label="label"
option-value="value"
:label="t('Dropdown')"
:options="[
{
@ -288,7 +284,11 @@
value: 'option_3',
},
]"
class="w-36"
input-id="dropdown"
name="dropdown"
option-label="label"
option-value="value"
/>
</div>
</div>
@ -310,8 +310,8 @@
<div class="mb-2"></div>
<BaseRadioButtons
v-model="radioValue"
:options="radioButtons"
:initial-value="radioValue"
:options="radioButtons"
name="radio"
/>
</div>
@ -337,8 +337,8 @@
:model-value="null"
/>
<BaseInputText
:label="t('This is a form with an error')"
:is-invalid="true"
:label="t('This is a form with an error')"
:model-value="null"
/>
<BaseInputDate
@ -352,13 +352,13 @@
<p class="mb-3 text-lg">{{ t("Dialogs") }}</p>
<BaseButton
:label="t('Show dialog')"
type="black"
icon="eye-on"
type="black"
@click="isDialogVisible = true"
/>
<BaseDialogConfirmCancel
:title="t('Dialog example')"
:is-visible="isDialogVisible"
:title="t('Dialog example')"
@confirm-clicked="isDialogVisible = false"
@cancel-clicked="isDialogVisible = false"
/>
@ -368,8 +368,8 @@
<div class="course-tool cursor-pointer">
<div class="course-tool__link hover:primary-gradient hover:bg-primary-gradient/10">
<span
class="course-tool__icon mdi mdi-bookshelf"
aria-hidden="true"
class="course-tool__icon mdi mdi-bookshelf"
/>
</div>
<p class="course-tool__title">{{ t("Documents") }}</p>
@ -436,7 +436,8 @@ const saveColors = async () => {
let colors = getColors()
try {
await axios.post("/api/color_themes", {
title: themeTitle.value,variables: colors,
title: themeTitle.value,
variables: colors,
})
showSuccessNotification(t("Colors updated"))
} catch (error) {

@ -1,8 +1,6 @@
<template>
<div class="flex flex-col gap-4">
<CalendarSectionHeader
@add-click="showAddEventDialog"
/>
<CalendarSectionHeader @add-click="showAddEventDialog" />
<FullCalendar
ref="cal"
@ -82,8 +80,9 @@
v-if="allowToEdit && showEditButton"
:label="t('Edit')"
type="secondary"
icon="delete"
@click="dialog = true"
icon="delete"/>
/>
</template>
</Dialog>
@ -176,7 +175,8 @@ const currentUser = computed(() => store.getters["security/getUser"])
const { t } = useI18n()
const { appLocale } = useLocale()
const route = useRoute()
const isGlobal = ref(route.query.type === 'global')
const isGlobal = ref(route.query.type === "global")
const selectedEventNotifications = ref([])
let currentEvent = null
@ -244,14 +244,14 @@ async function getCalendarEvents({ startStr, endStr }) {
params.gid = group.value.id
}
if (route.query?.type === 'global') {
params.type = 'global'
if (route.query?.type === "global") {
params.type = "global"
}
const calendarEvents = await cCalendarEventService.findAll({ params }).then((response) => response.json())
return calendarEvents["hydra:member"].map((event) => {
let color = event.color || '#007BFF'
let color = event.color || "#007BFF"
return {
...event,
@ -342,23 +342,23 @@ const calendarOptions = ref({
})
const currentContext = computed(() => {
if (route.query.type === 'global') {
return 'global'
if (route.query.type === "global") {
return "global"
} else if (course.value) {
return 'course'
return "course"
} else if (session.value) {
return 'session'
return "session"
} else {
return 'personal'
return "personal"
}
})
const allowAction = (eventType) => {
const contextRules = {
global: ['global'],
course: ['course'],
session: ['session'],
personal: ['personal']
global: ["global"],
course: ["course"],
session: ["session"],
personal: ["personal"],
}
return contextRules[currentContext.value].includes(eventType)
@ -411,7 +411,7 @@ function confirmDelete() {
async function subscribeToEvent() {
try {
await resourceLinkService.post({
await resourceLinkService.create({
resourceNode: item.value.resourceNode["@id"],
user: currentUser.value["@id"],
visibility: RESOURCE_LINK_PUBLISHED,
@ -518,10 +518,13 @@ async function sendNotifications(eventId, notifications) {
await Promise.allSettled(promises)
}
watch(() => route.query.type, (newType) => {
isGlobal.value = newType === 'global'
watch(
() => route.query.type,
(newType) => {
isGlobal.value = newType === "global"
reFetch()
})
},
)
watch(
() => store.state.ccalendarevent.created,

@ -180,11 +180,11 @@
<CourseTool
v-for="(tool, index) in tools"
:key="'tool-' + index.toString()"
:is-allowed-to-edit="isAllowedToEdit"
:change-visibility="changeVisibility"
:data-index="index"
:data-tool="tool.title"
:go-to-setting-course-tool="goToSettingCourseTool"
:is-allowed-to-edit="isAllowedToEdit"
:tool="tool"
/>
@ -249,9 +249,8 @@ const courseItems = ref([])
const routerTools = ["document", "link", "glossary", "agenda", "student_publication", "course_homepage"]
courseService.loadCTools(course.value.id, session.value?.id)
.then((cTools) => {
tools.value = cTools.map(element => {
courseService.loadCTools(course.value.id, session.value?.id).then((cTools) => {
tools.value = cTools.map((element) => {
if (routerTools.includes(element.title)) {
element.to = element.url
}
@ -271,7 +270,7 @@ courseService.loadCTools(course.value.id, session.value?.id)
return false
})
.map(adminTool => ({
.map((adminTool) => ({
label: adminTool.tool.titleToShow,
url: adminTool.url,
}))
@ -313,7 +312,15 @@ const setToolVisibility = (tool, visibility) => {
function changeVisibility(tool) {
axios
.post(ENTRYPOINT + "../r/course_tool/links/" + tool.resourceNode.id + "/change_visibility?cid=" + course.value.id + "&sid=" + session.value?.id)
.post(
ENTRYPOINT +
"../r/course_tool/links/" +
tool.resourceNode.id +
"/change_visibility?cid=" +
course.value.id +
"&sid=" +
session.value?.id,
)
.then((response) => setToolVisibility(tool, response.data.visibility))
.catch((error) => console.log(error))
}
@ -360,7 +367,7 @@ async function updateDisplayOrder(htmlItem, newIndex) {
const tool = htmlItem.dataset.tool
let toolItem = null
if (typeof tools !== "undefined" && Array.isArray(tools.value)) {
if (typeof tools.value !== "undefined" && Array.isArray(tools.value)) {
const toolList = tools.value
toolItem = toolList.find((element) => element.title === tool)
} else {
@ -371,12 +378,7 @@ async function updateDisplayOrder(htmlItem, newIndex) {
console.log(toolItem, newIndex)
// Send the updated values to the server
await courseService.updateToolOrder(
toolItem,
newIndex,
course.value.id,
session.value?.id
)
await courseService.updateToolOrder(toolItem, newIndex, course.value.id, session.value?.id)
}
const isAllowedToEdit = ref(false)
@ -395,8 +397,6 @@ const onStudentViewChanged = async () => {
const allowEditToolVisibilityInSession = computed(() => {
const isInASession = session.value?.id
return isInASession
? "true" === getSetting.value("course.allow_edit_tool_visibility_in_session")
: true
return isInASession ? "true" === getSetting.value("course.allow_edit_tool_visibility_in_session") : true
})
</script>

@ -21,8 +21,8 @@ import useVuelidate from "@vuelidate/core"
import { useRoute, useRouter } from "vue-router"
import isEmpty from "lodash/isEmpty"
import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility.js"
import axios from "axios"
import { useCidReq } from "../../composables/cidReq"
import cToolIntroService from "../../services/cToolIntroService"
const servicePrefix = "ctoolintro"
@ -59,23 +59,16 @@ export default {
let ctoolId = route.params.courseTool
async function getIntro() {
axios
.get("/course/" + courseId + "/getToolIntro", {
params: {
cToolIntroService
.findCourseHomeInro(courseId, {
cid: courseId,
sid: sessionId,
},
})
.then((response) => {
if (response.data) {
if (response.data.introText) {
item.value["introText"] = response.data.introText
}
.then((intro) => {
if (intro.introText) {
item.value.introText = intro.introText
}
})
.catch(function (error) {
console.log(error)
})
}
item.value["parentResourceNodeId"] = Number(route.query.parentResourceNodeId)
@ -93,8 +86,8 @@ export default {
function onCreated(item) {
//showNotification(t("Updated"))
axios
.post("/course/" + cid + "/addToolIntro", {
cToolIntroService
.addToolIntro(cid, {
iid: item.iid,
cid: route.query.cid,
sid: route.query.sid,

@ -19,10 +19,9 @@ import UpdateMixin from "../../mixins/UpdateMixin"
import { ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import useVuelidate from "@vuelidate/core"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import { RESOURCE_LINK_PUBLISHED } from "../../components/resource_links/visibility"
import { useCidReq } from "../../composables/cidReq"
import cToolIntroService from "../../services/cToolIntroService"
const servicePrefix = "ctoolintro"
@ -46,15 +45,15 @@ export default {
let ctoolintroId = route.query.ctoolintroIid
// Get the current intro text.
axios.get(ENTRYPOINT + "c_tool_intros/" + ctoolintroId)
.then((response) => {
let data = response.data;
item.value["introText"] = data.introText;
item.value["parentResourceNodeId"] = Number(route.query.parentResourceNodeId);
cToolIntroService
.findById(ctoolintroId)
.then((toolIntroInfo) => {
item.value["introText"] = toolIntroInfo.introText
item.value["parentResourceNodeId"] = Number(route.query.parentResourceNodeId)
})
.catch(function (error) {
console.error(error);
});
console.error(error)
})
item.value["courseTool"] = "/api/c_tools/" + toolId
item.value["resourceLinkList"] = [

@ -1,8 +1,8 @@
<template>
<Toolbar
:handle-back="handleBack"
:handle-reset="resetForm"
:handle-submit="onSendFormData"
:handle-back="handleBack"
/>
<div class="documents-layout">
@ -20,7 +20,11 @@
/>
<Panel
v-if="$route.query.filetype === 'certificate'"
:header="$t('Create your certificate copy-pasting the following tags. They will be replaced in the document by their student-specific value:')"
:header="
$t(
'Create your certificate copy-pasting the following tags. They will be replaced in the document by their student-specific value:',
)
"
>
<div v-html="finalTags" />
</Panel>
@ -56,12 +60,12 @@ export default {
Loading,
Toolbar,
DocumentsForm,
Panel
Panel,
},
mixins: [CreateMixin],
data() {
const filetype = this.$route.query.filetype === 'certificate' ? 'certificate' : 'file';
const finalTags = this.getCertificateTags();
const filetype = this.$route.query.filetype === "certificate" ? "certificate" : "file"
const finalTags = this.getCertificateTags()
return {
item: {
newDocument: true, // Used in FormNewDocument.vue to show the editor
@ -72,7 +76,7 @@ export default {
},
templates: [],
finalTags,
};
}
},
computed: {
...mapFields(["error", "isLoading", "created", "violations"]),
@ -91,58 +95,59 @@ export default {
methods: {
handleBack() {
this.$router.back();
this.$router.back()
},
addTemplateToEditor(templateContent) {
this.item.contentFile = templateContent;
this.item.contentFile = templateContent
},
fetchTemplates() {
const courseId = this.$route.query.cid;
axios.get(`/template/all-templates/${courseId}`)
.then(response => {
this.templates = response.data;
console.log('Templates fetched successfully:', this.templates);
const courseId = this.$route.query.cid
axios
.get(`/template/all-templates/${courseId}`)
.then((response) => {
this.templates = response.data
console.log("Templates fetched successfully:", this.templates)
})
.catch((error) => {
console.error("Error fetching templates:", error)
})
.catch(error => {
console.error('Error fetching templates:', error);
});
},
getCertificateTags() {
let finalTags = "";
let finalTags = ""
let tags = [
'((user_firstname))',
'((user_lastname))',
'((user_username))',
'((gradebook_institution))',
'((gradebook_sitename))',
'((teacher_firstname))',
'((teacher_lastname))',
'((official_code))',
'((date_certificate))',
'((date_certificate_no_time))',
'((course_code))',
'((course_title))',
'((gradebook_grade))',
'((certificate_link))',
'((certificate_link_html))',
'((certificate_barcode))',
'((external_style))',
'((time_in_course))',
'((time_in_course_in_all_sessions))',
'((start_date_and_end_date))',
'((course_objectives))',
];
"((user_firstname))",
"((user_lastname))",
"((user_username))",
"((gradebook_institution))",
"((gradebook_sitename))",
"((teacher_firstname))",
"((teacher_lastname))",
"((official_code))",
"((date_certificate))",
"((date_certificate_no_time))",
"((course_code))",
"((course_title))",
"((gradebook_grade))",
"((certificate_link))",
"((certificate_link_html))",
"((certificate_barcode))",
"((external_style))",
"((time_in_course))",
"((time_in_course_in_all_sessions))",
"((start_date_and_end_date))",
"((course_objectives))",
]
for (const tag of tags) {
finalTags += "<p class=\"m-0\">"+tag+"</p>"
finalTags += '<p class="m-0">' + tag + "</p>"
}
return finalTags;
return finalTags
},
...mapActions('documents', ['createWithFormData', 'reset'])
...mapActions("documents", ["createWithFormData", "reset"]),
},
mounted() {
this.fetchTemplates();
this.fetchTemplates()
},
};
}
</script>

@ -181,8 +181,8 @@
/>
<BaseButton
v-if="isCertificateMode"
:class="{ selected: slotProps.data.iid === defaultCertificateId }"
:icon="slotProps.data.iid === defaultCertificateId ? 'certificate-selected' : 'certificate-not-selected'"
:class="{ 'selected': slotProps.data.iid === defaultCertificateId }"
size="small"
type="slotProps.data.iid === defaultCertificateId ? 'success' : 'black'"
@click="selectAsDefaultCertificate(slotProps.data)"
@ -340,9 +340,9 @@
id="post-file"
:label="t('File upload')"
accept="image"
model-value=""
size="small"
@file-selected="selectedFile = $event"
model-value=""
/>
</form>
</BaseDialogConfirmCancel>
@ -371,7 +371,6 @@ import DocumentAudioRecorder from "../../components/documents/DocumentAudioRecor
import { useNotification } from "../../composables/notification"
import { useSecurityStore } from "../../store/securityStore"
import prettyBytes from "pretty-bytes"
import { ENTRYPOINT } from "../../config/entrypoint"
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
const store = useStore()
@ -383,7 +382,7 @@ const { t } = useI18n()
const { filters, options, onUpdateOptions, deleteItem } = useDatatableList("Documents")
const notification = useNotification()
const { cid, sid, gid } = useCidReq()
const { isImage, isHtml } = useFileUtils();
const { isImage, isHtml } = useFileUtils()
const { relativeDatetime } = useFormatDate()
@ -416,13 +415,13 @@ const hasImageInDocumentEntries = computed(() => {
})
const isCertificateMode = computed(() => {
return route.query.filetype === 'certificate';
return route.query.filetype === "certificate"
})
const defaultCertificateId = ref(null);
const defaultCertificateId = ref(null)
const isHtmlFile = (fileData) => {
return isHtml(fileData);
return isHtml(fileData)
}
onMounted(() => {
@ -685,111 +684,111 @@ function recordedAudioNotSaved(error) {
async function selectAsDefaultCertificate(certificate) {
try {
const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`);
const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`)
if (response.status === 200) {
loadDefaultCertificate()
onUpdateOptions(options.value)
notification.showSuccessNotification(t('Certificate set as default successfully'));
notification.showSuccessNotification(t("Certificate set as default successfully"))
}
} catch (error) {
notification.showErrorNotification(t('Error setting certificate as default'));
notification.showErrorNotification(t("Error setting certificate as default"))
}
}
async function loadDefaultCertificate() {
try {
const response = await axios.get(`/gradebook/default_certificate/${cid}`);
defaultCertificateId.value = response.data.certificateId;
const response = await axios.get(`/gradebook/default_certificate/${cid}`)
defaultCertificateId.value = response.data.certificateId
} catch (error) {
if (error.response && error.response.status === 404) {
console.error('Default certificate not found.');
defaultCertificateId.value = null;
console.error("Default certificate not found.")
defaultCertificateId.value = null
} else {
console.error('Error loading the certificate', error);
console.error("Error loading the certificate", error)
}
}
}
const showTemplateFormModal = ref(false);
const selectedFile = ref(null);
const showTemplateFormModal = ref(false)
const selectedFile = ref(null)
const templateFormData = ref({
title: '',
title: "",
thumbnail: null,
})
const currentDocumentId = ref(null);
const currentDocumentId = ref(null)
const isDocumentTemplate = async (documentId) => {
try {
const response = await axios.get(`/template/document-templates/${documentId}/is-template`);
return response.data.isTemplate;
const response = await axios.get(`/template/document-templates/${documentId}/is-template`)
return response.data.isTemplate
} catch (error) {
console.error('Error verifying the template status:', error);
console.error("Error verifying the template status:", error)
return false
}
}
const deleteDocumentTemplate = async (documentId) => {
try {
await axios.post(`/template/document-templates/${documentId}/delete`);
onUpdateOptions(options.value);
notification.showSuccessNotification(t('Template successfully deteled.'));
await axios.post(`/template/document-templates/${documentId}/delete`)
onUpdateOptions(options.value)
notification.showSuccessNotification(t("Template successfully deteled."))
} catch (error) {
console.error('Error deleting the template:', error);
notification.showErrorNotification(t('Error deleting the template.'));
console.error("Error deleting the template:", error)
notification.showErrorNotification(t("Error deleting the template."))
}
}
const getTemplateIcon = (documentId) => {
const document = items.value.find((doc) => doc.iid === documentId);
return document && document.template ? 'template-selected' : 'template-not-selected';
const document = items.value.find((doc) => doc.iid === documentId)
return document && document.template ? "template-selected" : "template-not-selected"
}
const openTemplateForm = async (documentId) => {
const isTemplate = await isDocumentTemplate(documentId);
const isTemplate = await isDocumentTemplate(documentId)
if (isTemplate) {
await deleteDocumentTemplate(documentId)
onUpdateOptions(listaoptions.value);
onUpdateOptions(options.value)
} else {
currentDocumentId.value = documentId;
showTemplateFormModal.value = true;
currentDocumentId.value = documentId
showTemplateFormModal.value = true
}
}
const submitTemplateForm = async () => {
submitted.value = true;
submitted.value = true
if (!templateFormData.value.title || !selectedFile.value) {
notification.showErrorNotification(t('The title and thumbnail are required.'));
return;
notification.showErrorNotification(t("The title and thumbnail are required."))
return
}
try {
const formData = new FormData()
formData.append('title', templateFormData.value.title);
formData.append('thumbnail', selectedFile.value);
formData.append('refDoc', currentDocumentId.value);
formData.append('cid', cid);
formData.append("title", templateFormData.value.title)
formData.append("thumbnail", selectedFile.value)
formData.append("refDoc", currentDocumentId.value)
formData.append("cid", cid)
const response = await axios.post('/template/document-templates/create', formData, {
const response = await axios.post("/template/document-templates/create", formData, {
headers: {
'Content-Type': 'multipart/form-data',
"Content-Type": "multipart/form-data",
},
});
})
if (response.status === 200 || response.status === 201) {
notification.showSuccessNotification(t('Template created successfully.'));
templateFormData.value.title = '';
selectedFile.value = null;
showTemplateFormModal.value = false;
onUpdateOptions(options.value);
notification.showSuccessNotification(t("Template created successfully."))
templateFormData.value.title = ""
selectedFile.value = null
showTemplateFormModal.value = false
onUpdateOptions(options.value)
} else {
notification.showErrorNotification(t('Error creating the template.'));
notification.showErrorNotification(t("Error creating the template."))
}
} catch (error) {
console.error('Error submitting the form:', error)
notification.showErrorNotification(t('Error submitting the form.'));
console.error("Error submitting the form:", error)
notification.showErrorNotification(t("Error submitting the form."))
}
}
};
</script>

@ -33,7 +33,11 @@
/>
<div class="field">
<BaseTinyEditor v-model="item.content" editor-id="message" required />
<BaseTinyEditor
v-model="item.content"
editor-id="message"
required
/>
</div>
<BaseButton
@ -51,14 +55,12 @@ import { useStore } from "vuex"
import MessageForm from "../../components/message/Form.vue"
import Loading from "../../components/Loading.vue"
import { computed, ref } from "vue"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import BaseAutocomplete from "../../components/basecomponents/BaseAutocomplete.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { useI18n } from "vue-i18n"
import { useRoute, useRouter } from "vue-router"
import { MESSAGE_TYPE_INBOX } from "../../components/message/constants"
import userService from "../../services/user"
import userService from "../../services/userService"
import BaseUserAvatar from "../../components/basecomponents/BaseUserAvatar.vue"
import { useNotification } from "../../composables/notification"
import { capitalize } from "lodash"
@ -71,26 +73,13 @@ const { t } = useI18n()
const notification = useNotification()
const asyncFind = (query) => {
return axios
.get(ENTRYPOINT + "users", {
params: {
username: query,
},
})
.then((response) => {
let data = response.data
const asyncFind = async (query) => {
const { items } = await userService.findByUsername(query)
return (
data["hydra:member"]?.map((member) => ({
return items.map((member) => ({
name: member.fullName,
value: member["@id"],
})) ?? []
)
})
.catch(function (error) {
console.log(error)
})
}))
}
const currentUser = computed(() => store.getters["security/getUser"])
@ -201,7 +190,6 @@ if (route.query.send_to_user) {
userService
.find("/api/users/" + parseInt(route.query.send_to_user))
.then((response) => response.json())
.then((user) => {
sendToUser.value = user
@ -214,7 +202,11 @@ if (route.query.send_to_user) {
const prefill = capitalize(route.query.prefill)
item.value.title = t(prefill + "Title")
item.value.content = t(prefill + "Content", [user.firstname, currentUser.value.firstname, currentUser.value.firstname])
item.value.content = t(prefill + "Content", [
user.firstname,
currentUser.value.firstname,
currentUser.value.firstname,
])
}
})
.catch((e) => notification.showErrorNotification(e))

@ -144,8 +144,6 @@ import { useStore } from "vuex"
import Loading from "../../components/Loading.vue"
import { computed, ref } from "vue"
import isEmpty from "lodash/isEmpty"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import { useRoute, useRouter } from "vue-router"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { useConfirm } from "primevue/useconfirm"
@ -154,6 +152,7 @@ import BaseChip from "../../components/basecomponents/BaseChip.vue"
import BaseAutocomplete from "../../components/basecomponents/BaseAutocomplete.vue"
import { useFormatDate } from "../../composables/formatDate"
import { useMessageRelUserStore } from "../../store/messageRelUserStore"
import messageTagService from "../../services/messageTagService"
const confirm = useConfirm()
const { t } = useI18n()
@ -250,25 +249,14 @@ function createEvent() {
const foundTag = ref("")
function onSearchTags(query) {
async function onSearchTags(query) {
isLoadingSelect.value = true
return axios
.get(ENTRYPOINT + "message_tags", {
params: {
user: user["@id"],
tag: query,
},
})
.then((response) => {
isLoadingSelect.value = false
const { items } = await messageTagService.searchUserTags(user["@id"], query)
return response.data["hydra:member"]
})
.catch(function (error) {
isLoadingSelect.value = false
console.log(error)
})
return items
}
async function onItemSelect({ value }) {

@ -1,13 +1,25 @@
<template>
<StickyCourses />
<hr>
<hr />
<div v-if="isLoading" class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<div
v-if="isLoading"
class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
>
<Skeleton height="16rem" />
<Skeleton class="hidden md:block" height="16rem" />
<Skeleton class="hidden lg:block" height="16rem" />
<Skeleton class="hidden xl:block" height="16rem" />
<Skeleton
class="hidden md:block"
height="16rem"
/>
<Skeleton
class="hidden lg:block"
height="16rem"
/>
<Skeleton
class="hidden xl:block"
height="16rem"
/>
</div>
<div
@ -25,30 +37,30 @@
</template>
<script setup>
import { onMounted, computed } from "vue";
import { useStore } from "vuex";
import { useQuery } from "@vue/apollo-composable";
import { useI18n } from "vue-i18n";
import { GET_COURSE_REL_USER } from "../../../graphql/queries/CourseRelUser.js";
import Skeleton from "primevue/skeleton";
import StickyCourses from "../../../views/user/courses/StickyCourses.vue";
import CourseCardList from "../../../components/course/CourseCardList.vue";
import EmptyState from "../../../components/EmptyState";
import { onMounted, computed } from "vue"
import { useStore } from "vuex"
import { useQuery } from "@vue/apollo-composable"
import { useI18n } from "vue-i18n"
import { GET_COURSE_REL_USER } from "../../../graphql/queries/CourseRelUser.js"
import Skeleton from "primevue/skeleton"
import StickyCourses from "../../../views/user/courses/StickyCourses.vue"
import CourseCardList from "../../../components/course/CourseCardList.vue"
import EmptyState from "../../../components/EmptyState"
const store = useStore();
const { t } = useI18n();
const store = useStore()
const { t } = useI18n()
let user = computed(() => store.getters["security/getUser"]);
let user = computed(() => store.getters["security/getUser"])
const { result, loading, refetch } = useQuery(GET_COURSE_REL_USER, () => ({
user: user.value["@id"],
}));
}))
const isLoading = computed(() => loading.value);
const isLoading = computed(() => loading.value)
const courses = computed(() => result.value?.courseRelUsers.edges.map(({ node }) => node.course) ?? []);
const courses = computed(() => result.value?.courseRelUsers.edges.map(({ node }) => node.course) ?? [])
onMounted(() => {
refetch();
});
refetch()
})
</script>

@ -1,48 +1,87 @@
<template>
<div class="invite-friends-container invite-friends">
<div class="invite-friends-header">
<h2>{{ t('Invite Friends to Group') }}</h2>
<h2>{{ t("Invite Friends to Group") }}</h2>
</div>
<div class="invite-friends-body">
<div class="friends-list">
<div class="list-header">
<h3>{{ t('Available Friends') }}</h3>
<h3>{{ t("Available Friends") }}</h3>
</div>
<div class="list-content">
<div class="friend-entry" v-for="friend in availableFriends" :key="friend.id">
<div
v-for="friend in availableFriends"
:key="friend.id"
class="friend-entry"
>
<div class="friend-info">
<img :src="friend.avatar" alt="avatar" class="friend-avatar" />
<img
:src="friend.avatar"
alt="avatar"
class="friend-avatar"
/>
<span class="friend-name">{{ friend.name }}</span>
</div>
<button @click="selectFriend(friend)" class="invite-btn">+</button>
<button
class="invite-btn"
@click="selectFriend(friend)"
>
+
</button>
</div>
</div>
</div>
<div class="selected-friends-list">
<div class="list-header">
<h3>{{ t('Selected Friends') }}</h3>
<h3>{{ t("Selected Friends") }}</h3>
</div>
<div class="list-content">
<div class="friend-entry" v-for="friend in selectedFriends" :key="friend.id">
<div
v-for="friend in selectedFriends"
:key="friend.id"
class="friend-entry"
>
<div class="friend-info">
<img :src="friend.avatar" alt="avatar" class="friend-avatar" />
<img
:src="friend.avatar"
alt="avatar"
class="friend-avatar"
/>
<span class="friend-name">{{ friend.name }}</span>
</div>
<button @click="removeFriend(friend)" class="remove-btn">-</button>
<button
class="remove-btn"
@click="removeFriend(friend)"
>
-
</button>
</div>
</div>
</div>
</div>
<div class="invite-friends-footer">
<button @click="sendInvitations" class="send-invites-btn">{{ t('Send Invitations') }}</button>
<button
class="send-invites-btn"
@click="sendInvitations"
>
{{ t("Send Invitations") }}
</button>
</div>
<div class="invited-friends-list mt-4">
<div class="list-header">
<h3>{{ t('Users Already Invited') }}</h3>
<h3>{{ t("Users Already Invited") }}</h3>
</div>
<div class="invited-users-grid mt-4">
<div class="user-card" v-for="user in invitedFriends" :key="user.id">
<img :src="user.avatar" alt="avatar" class="user-avatar" />
<div
v-for="user in invitedFriends"
:key="user.id"
class="user-card"
>
<img
:src="user.avatar"
alt="avatar"
class="user-avatar"
/>
<span class="user-name">{{ user.name }}</span>
</div>
</div>
@ -51,9 +90,9 @@
</template>
<script setup>
import { inject, onMounted, ref, computed} from "vue"
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { computed, onMounted, ref } from "vue"
import { useI18n } from "vue-i18n"
import { useRoute } from "vue-router"
import { useStore } from "vuex"
import axios from "axios"
@ -79,7 +118,7 @@ const loadAvailableFriends = async () => {
const response = await axios.get(`/social-network/invite-friends/${userId}/${groupId}`)
availableFriends.value = response.data.friends
} catch (error) {
console.error('Error loading available friends:', error)
console.error("Error loading available friends:", error)
}
}
onMounted(() => {
@ -88,16 +127,16 @@ onMounted(() => {
})
const sendInvitations = async () => {
const groupId = route.params.group_id
const userIds = selectedFriends.value.map(friend => friend.id)
const userIds = selectedFriends.value.map((friend) => friend.id)
try {
await axios.post(`/social-network/add-users-to-group/${groupId}`, {
userIds,
})
console.log('Users added to group successfully!')
console.log("Users added to group successfully!")
selectedFriends.value = []
loadInvitedFriends()
} catch (error) {
console.error('Error adding users to group:', error)
console.error("Error adding users to group:", error)
}
}
const loadInvitedFriends = async () => {
@ -106,7 +145,7 @@ const loadInvitedFriends = async () => {
const response = await axios.get(`/social-network/group/${groupId}/invited-users`)
invitedFriends.value = response.data.invitedUsers
} catch (error) {
console.error('Error loading invited friends:', error)
console.error("Error loading invited friends:", error)
}
}
</script>

@ -3,47 +3,113 @@
<div class="p-col-12">
<div class="p-d-flex p-jc-between p-ai-center p-mb-4">
<h1>Social groups</h1>
<Button label="Create a social group" icon="pi pi-plus" class="create-group-button" @click="showCreateGroupDialog = true" />
<Button
class="create-group-button"
icon="pi pi-plus"
label="Create a social group"
@click="showCreateGroupDialog = true"
/>
</div>
<TabView class="social-group-tabs">
<TabPanel header="Newest" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'Newest' }">
<TabPanel
:class="{ 'active-tab': activeTab === 'Newest' }"
header="Newest"
header-class="tab-header"
>
<div class="group-list">
<div class="group-item" v-for="group in newestGroups" :key="group['@id']">
<img v-if="group.pictureUrl" :src="group.pictureUrl" class="group-image" alt="Group Image" />
<i v-else class="mdi mdi-account-group-outline group-icon"></i>
<div
v-for="group in newestGroups"
:key="group['@id']"
class="group-item"
>
<img
v-if="group.pictureUrl"
:src="group.pictureUrl"
alt="Group Image"
class="group-image"
/>
<i
v-else
class="mdi mdi-account-group-outline group-icon"
></i>
<div class="group-details">
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a>
<a
:href="`/resources/usergroups/show/${extractGroupId(group)}`"
class="group-title"
>{{ group.title }}</a
>
<div class="group-info">
<span class="group-member-count">{{ group.memberCount }} {{ t('Member') }}</span>
<span class="group-member-count">{{ group.memberCount }} {{ t("Member") }}</span>
<span class="group-description">{{ group.description }}</span>
</div>
</div>
</div>
</div>
</TabPanel>
<TabPanel header="Popular" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'Popular' }">
<TabPanel
:class="{ 'active-tab': activeTab === 'Popular' }"
header="Popular"
header-class="tab-header"
>
<div class="group-list">
<div class="group-item" v-for="group in popularGroups" :key="group['@id']">
<img v-if="group.pictureUrl" :src="group.pictureUrl" class="group-image" alt="Group Image" />
<i v-else class="mdi mdi-account-group-outline group-icon"></i>
<div
v-for="group in popularGroups"
:key="group['@id']"
class="group-item"
>
<img
v-if="group.pictureUrl"
:src="group.pictureUrl"
alt="Group Image"
class="group-image"
/>
<i
v-else
class="mdi mdi-account-group-outline group-icon"
></i>
<div class="group-details">
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a>
<a
:href="`/resources/usergroups/show/${extractGroupId(group)}`"
class="group-title"
>{{ group.title }}</a
>
<div class="group-info">
<span class="group-member-count">{{ group.memberCount }} {{ t('Member') }}</span>
<span class="group-member-count">{{ group.memberCount }} {{ t("Member") }}</span>
<span class="group-description">{{ group.description }}</span>
</div>
</div>
</div>
</div> </TabPanel>
<TabPanel header="My groups" headerClass="tab-header" :class="{ 'active-tab': activeTab === 'My groups' }">
</div>
</TabPanel>
<TabPanel
:class="{ 'active-tab': activeTab === 'My groups' }"
header="My groups"
header-class="tab-header"
>
<div class="group-list">
<div class="group-item" v-for="group in myGroups" :key="group['@id']">
<img v-if="group.pictureUrl" :src="group.pictureUrl" class="group-image" alt="Group Image" />
<i v-else class="mdi mdi-account-group-outline group-icon"></i>
<div
v-for="group in myGroups"
:key="group['@id']"
class="group-item"
>
<img
v-if="group.pictureUrl"
:src="group.pictureUrl"
alt="Group Image"
class="group-image"
/>
<i
v-else
class="mdi mdi-account-group-outline group-icon"
></i>
<div class="group-details">
<a :href="`/resources/usergroups/show/${extractGroupId(group)}`" class="group-title">{{ group.title }}</a>
<a
:href="`/resources/usergroups/show/${extractGroupId(group)}`"
class="group-title"
>{{ group.title }}</a
>
<div class="group-info">
<span class="group-member-count">{{ group.memberCount }} {{ t('Member') }}</span>
<span class="group-member-count">{{ group.memberCount }} {{ t("Member") }}</span>
<span class="group-description">{{ group.description }}</span>
</div>
</div>
@ -54,27 +120,32 @@
</div>
</div>
<Dialog header="Add" v-model:visible="showCreateGroupDialog" modal="true" closable="true">
<Dialog
v-model:visible="showCreateGroupDialog"
closable="true"
header="Add"
modal="true"
>
<form @submit.prevent="createGroup">
<div class="p-fluid">
<BaseInputTextWithVuelidate
v-model="groupForm.name"
label="Name*"
:vuelidate-property="v$.groupForm.name"
label="Name*"
/>
<BaseInputTextWithVuelidate
v-model="groupForm.description"
label="Description"
:vuelidate-property="v$.groupForm.description"
as="textarea"
label="Description"
rows="3"
/>
<BaseInputTextWithVuelidate
v-model="groupForm.url"
label="URL"
:vuelidate-property="v$.groupForm.url"
label="URL"
/>
<BaseFileUpload
:label="t('Add a picture')"
@ -84,7 +155,13 @@
/>
<div class="p-field mt-2">
<label for="groupPermissions">Group Permissions</label>
<Dropdown id="groupPermissions" v-model="groupForm.permissions" :options="permissionsOptions" optionLabel="label" placeholder="Select Permission" />
<Dropdown
id="groupPermissions"
v-model="groupForm.permissions"
:options="permissionsOptions"
option-label="label"
placeholder="Select Permission"
/>
</div>
<div class="p-field-checkbox mt-2">
<BaseCheckbox
@ -94,52 +171,62 @@
name="leaveGroup"
/>
</div>
</div>
<Button label="Add" icon="pi pi-check" class="p-button-rounded p-button-text" @click="createGroup" />
<Button label="Close" class="p-button-text" @click="showCreateGroupDialog = false" />
<Button
class="p-button-rounded p-button-text"
icon="pi pi-check"
label="Add"
@click="createGroup"
/>
<Button
class="p-button-text"
label="Close"
@click="showCreateGroupDialog = false"
/>
</form>
</Dialog>
</template>
<script setup>
import Button from 'primevue/button'
import TabView from 'primevue/tabview'
import TabPanel from 'primevue/tabpanel'
import { ref, onMounted } from 'vue'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import Button from "primevue/button"
import TabView from "primevue/tabview"
import TabPanel from "primevue/tabpanel"
import { onMounted, ref } from "vue"
import useVuelidate from "@vuelidate/core"
import { required } from "@vuelidate/validators"
import BaseInputTextWithVuelidate from "../../components/basecomponents/BaseInputTextWithVuelidate.vue"
import BaseFileUpload from "../../components/basecomponents/BaseFileUpload.vue"
import BaseCheckbox from "../../components/basecomponents/BaseCheckbox.vue"
import { useI18n } from "vue-i18n"
import axios from "axios"
import { ENTRYPOINT } from "../../config/entrypoint"
import usergroupService from "../../services/usergroupService"
const { t } = useI18n()
const newestGroups = ref([])
const popularGroups = ref([])
const myGroups = ref([])
const activeTab = ref('Newest')
const activeTab = ref("Newest")
const showCreateGroupDialog = ref(false)
const selectedFile = ref(null)
const groupForm = ref({
name: '',
description: '',
url: '',
name: "",
description: "",
url: "",
picture: null,
})
const v$ = useVuelidate({
const v$ = useVuelidate(
{
groupForm: {
name: { required },
description: {},
url: {},
}
}, { groupForm })
},
},
{ groupForm },
)
const permissionsOptions = [
{ label: 'Open', value: '1' },
{ label: 'Closed', value: '2' },
{ label: "Open", value: "1" },
{ label: "Closed", value: "2" },
]
const createGroup = async () => {
v$.value.$touch()
@ -153,60 +240,38 @@ const createGroup = async () => {
groupType: 1,
}
try {
const response = await axios.post(ENTRYPOINT + 'usergroups', groupData, {
headers: {
'Content-Type': 'application/json',
},
})
const newGroup = await usergroupService.createGroup(groupData)
if (selectedFile.value && response.data && response.data.id) {
const formData = new FormData()
formData.append('picture', selectedFile.value)
await axios.post(`/social-network/upload-group-picture/${response.data.id}`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
if (selectedFile.value && newGroup && newGroup.id) {
await usergroupService.uploadPicture(newGroup.id, {
picture: selectedFile.value,
})
}
showCreateGroupDialog.value = false
resetForm()
await updateGroupsList()
updateGroupsList()
} catch (error) {
console.error('Failed to create group or upload picture:', error.response.data)
}
console.error("Failed to create group or upload picture:", error.response.data)
}
}
const fetchGroups = async (endpoint) => {
try {
const response = await fetch(ENTRYPOINT + `${endpoint}`)
if (!response.ok) {
throw new Error('Failed to fetch groups')
}
const data = await response.json()
console.log('hidra menber ::: ', data['hydra:member'])
return data['hydra:member']
} catch (error) {
console.error(error)
return []
}
}
const updateGroupsList = async () => {
newestGroups.value = await fetchGroups('usergroup/list/newest')
popularGroups.value = await fetchGroups('usergroup/list/popular')
myGroups.value = await fetchGroups('usergroup/list/my')
const updateGroupsList = () => {
usergroupService.listNewest().then((newest) => (newestGroups.value = newest))
usergroupService.listPopular().then((popular) => (popularGroups.value = popular))
usergroupService.listMine().then((mine) => (myGroups.value = mine))
}
const extractGroupId = (group) => {
const match = group['@id'].match(/\/api\/usergroup\/(\d+)/)
const match = group["@id"].match(/\/api\/usergroup\/(\d+)/)
return match ? match[1] : null
}
const redirectToGroupDetails = (groupId) => {
router.push({ name: 'UserGroupShow', params: { group_id: groupId } })
router.push({ name: "UserGroupShow", params: { group_id: groupId } })
}
onMounted(async () => {
await updateGroupsList()
updateGroupsList()
})
const closeDialog = () => {
@ -214,11 +279,11 @@ const closeDialog = () => {
}
const resetForm = () => {
groupForm.value = {
name: '',
description: '',
url: '',
name: "",
description: "",
url: "",
picture: null,
permissions: '',
permissions: "",
allowLeave: false,
}
selectedFile.value = null

@ -1,5 +1,8 @@
<template>
<h2 v-t="'Add friends'" class="mr-auto" />
<h2
v-t="'Add friends'"
class="mr-auto"
/>
<hr />
<BaseToolbar>
<BaseButton
@ -33,42 +36,37 @@
</div>
</div>
</template>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
<script setup>
import { computed, ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n'
import { useNotification } from '../../composables/notification'
import VueMultiselect from 'vue-multiselect'
import { onMounted, ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
import { useNotification } from "../../composables/notification"
import VueMultiselect from "vue-multiselect"
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { ENTRYPOINT } from '../../config/entrypoint'
import axios from "axios"
import userService from "../../services/userService"
import userRelUserService from "../../services/userRelUserService"
const store = useStore()
const router = useRouter()
const route = useRoute()
const { t } = useI18n()
const { showSuccessNotification, showErrorNotification } = useNotification()
const user = store.getters['security/getUser']
const isAuthenticated = computed(() => store.getters['security/isAuthenticated'])
const isAdmin = computed(() => store.getters['security/isAdmin'])
const currentUser = computed(() => store.getters['security/getUser'])
const user = store.getters["security/getUser"]
const users = ref([])
const isLoadingSelect = ref(false)
const searchQuery = ref('')
const searchQuery = ref("")
const asyncFind = (query) => {
if (query.toString().length < 3) return
isLoadingSelect.value = true
axios.get(`${ENTRYPOINT}users`, { params: { username: query } })
.then((response) => {
users.value = response.data['hydra:member']
})
userService
.findByUsername(query)
.then(({ items }) => (users.value = items))
.catch((error) => {
console.error('Error fetching users:', error)
console.error("Error fetching users:", error)
})
.finally(() => {
isLoadingSelect.value = false
@ -77,17 +75,15 @@ const asyncFind = (query) => {
const addFriend = (friend) => {
isLoadingSelect.value = true
axios.post(`${ENTRYPOINT}user_rel_users`, {
user: user['@id'],
friend: friend['@id'],
relationType: 10,
})
userRelUserService
.sendFriendRequest(user["@id"], friend["@id"])
.then(() => {
showSuccessNotification(t('Friend request sent successfully'))
showSuccessNotification(t("Friend request sent successfully"))
})
.catch((error) => {
showErrorNotification(t('Failed to send friend request'))
console.error('Error adding friend:', error)
showErrorNotification(t("Failed to send friend request"))
console.error("Error adding friend:", error)
})
.finally(() => {
isLoadingSelect.value = false
@ -95,8 +91,9 @@ const addFriend = (friend) => {
}
const goToBack = () => {
router.push({ name: 'UserRelUserList' })
router.push({ name: "UserRelUserList" })
}
// Lifecycle hooks
onMounted(() => {
if (route.query.search) {
@ -105,3 +102,5 @@ onMounted(() => {
}
})
</script>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

@ -1,5 +1,8 @@
<template>
<h2 v-t="'Search users for friends'" class="mr-auto" />
<h2
v-t="'Search users for friends'"
class="mr-auto"
/>
<hr />
<div class="user-rel-user-search">
<BaseToolbar>
@ -13,18 +16,22 @@
<div class="search-area mb-4">
<input
v-model="searchQuery"
type="text"
placeholder="Search Users"
class="search-input mr-3"
placeholder="Search Users"
type="text"
/>
<BaseButton
:label="t('Search')"
@click="executeSearch"
class="search-button"
icon="search"
type="button"/>
type="button"
@click="executeSearch"
/>
</div>
<div v-if="!loadingResults" class="results-list">
<div
v-if="!loadingResults"
class="results-list"
>
<div
v-for="(user, index) in foundUsers"
:key="index"
@ -32,24 +39,31 @@
>
<div class="user-avatar">
<img
:src="user.illustrationUrl || defaultAvatar"
:alt="`${user.username}'s avatar`"
:src="user.illustrationUrl || defaultAvatar"
class="avatar-image"
/>
</div>
<div class="user-details">
<div class="username">{{ user.username }}</div>
<div class="user-actions">
<button class="action-button invite-button" @click="addFriend(user)">
<button
class="action-button invite-button"
@click="addFriend(user)"
>
Send invitation
</button>
<a
:href="`/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id=${user.id}`"
class="action-button message-button ajax"
data-title="Send message"
title="Send message"
:href="`/main/inc/ajax/user_manager.ajax.php?a=get_user_popup&user_id=${user.id}`"
>
<i class="fa fa-envelope" aria-hidden="true"></i> Send message
<i
aria-hidden="true"
class="fa fa-envelope"
></i>
Send message
</a>
</div>
</div>
@ -58,15 +72,15 @@
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { useI18n } from 'vue-i18n'
import { useNotification } from '../../composables/notification'
import { onMounted, ref } from "vue"
import { useRouter } from "vue-router"
import { useStore } from "vuex"
import { useI18n } from "vue-i18n"
import { useNotification } from "../../composables/notification"
import BaseToolbar from "../../components/basecomponents/BaseToolbar.vue"
import BaseButton from "../../components/basecomponents/BaseButton.vue"
import { ENTRYPOINT } from '../../config/entrypoint'
import axios from "axios"
import userRelUserService from "../../services/userRelUserService"
import userService from "../../services/userService"
const store = useStore()
const router = useRouter()
@ -77,28 +91,28 @@ const loadingResults = ref(false)
const user = store.getters["security/getUser"]
const foundUsers = ref([])
const friendsList = ref([])
const searchQuery = ref('')
const searchQuery = ref("")
const executeSearch = async () => {
if (searchQuery.value.trim().length > 0) {
await router.push({ name: 'UserRelUserSearch', query: { search: searchQuery.value } })
await router.push({ name: "UserRelUserSearch", query: { search: searchQuery.value } })
await fetchFriendsList()
await asyncFind(searchQuery.value)
} else {
showErrorNotification(t('Please enter a search query'))
showErrorNotification(t("Please enter a search query"))
}
}
const isFriend = (user) => {
return friendsList.value.some(friend => friend.id === user.id)
return friendsList.value.some((friend) => friend.id === user.id)
}
async function fetchFriendsList() {
try {
const response = await axios.get(`${ENTRYPOINT}user_rel_users`, {
params: { user: user.id, relationType: [3, 10] }
})
friendsList.value = response.data['hydra:member'].map(friendship => friendship.friend.id).concat(user.id)
const friendshipList = await userRelUserService.getFriendList(user["@id"])
friendsList.value = friendshipList.map((friendship) => friendship.friend.id).concat(user.id)
} catch (error) {
showErrorNotification(t('Error fetching friends list'))
console.error('Error fetching friends list:', error)
showErrorNotification(t("Error fetching friends list"))
console.error("Error fetching friends list:", error)
}
}
@ -106,34 +120,31 @@ const asyncFind = async (query) => {
if (query.length < 3) return
isLoadingSelect.value = true
try {
const { data } = await axios.get(`${ENTRYPOINT}users`, { params: { username: query } })
foundUsers.value = data['hydra:member'].filter(foundUser => !friendsList.value.includes(foundUser.id))
const { items } = await userService.findByUsername(query)
foundUsers.value = items.filter((foundUser) => !friendsList.value.includes(foundUser.id))
} catch (error) {
showErrorNotification(t('Error fetching users'))
showErrorNotification(t("Error fetching users"))
} finally {
isLoadingSelect.value = false
}
}
const addFriend = async (friend) => {
try {
await axios.post(`${ENTRYPOINT}user_rel_users`, {
user: user['@id'],
friend: friend['@id'],
relationType: 10,
})
showSuccessNotification(t('Friend request sent successfully'))
await userRelUserService.sendFriendRequest(user["@id"], friend["@id"])
showSuccessNotification(t("Friend request sent successfully"))
await fetchFriendsList()
const searchQuery = router.currentRoute.value.query.search
if (searchQuery) {
await asyncFind(searchQuery)
}
} catch (error) {
showErrorNotification(t('Failed to send friend request'))
console.error('Error adding friend:', error)
showErrorNotification(t("Failed to send friend request"))
console.error("Error adding friend:", error)
}
}
const goToBack = () => {
router.push({ name: 'UserRelUserList' })
router.push({ name: "UserRelUserList" })
}
onMounted(async () => {
const urlSearchQuery = router.currentRoute.value.query.search

@ -6,46 +6,18 @@ import InputText from "primevue/inputtext"
import Password from "primevue/password"
import Button from "primevue/button"
import InputSwitch from "primevue/inputswitch"
import { useRoute, useRouter } from "vue-router"
import { useSecurityStore } from "../../../assets/vue/store/securityStore"
import { useLogin } from "../../../assets/vue/composables/auth/login"
const route = useRoute()
const router = useRouter()
const store = useStore()
const { t } = useI18n()
const securityStore = useSecurityStore()
const { performLogin } = useLogin()
const login = ref("")
const password = ref("")
const remember = ref(false)
const isLoading = computed(() => store.getters["security/isLoading"])
async function performLogin() {
let payload = {
login: login.value,
password: password.value,
_remember_me: remember.value,
}
let redirect = route.query.redirect
await store.dispatch("security/login", payload)
if (!store.getters["security/hasError"]) {
securityStore.user = store.state["security/user"]
const responseData = await store.dispatch("security/login", payload)
if (typeof redirect !== "undefined") {
await router.push({ path: redirect.toString() })
} else {
if (responseData.load_terms) {
window.location.href = responseData.redirect
} else {
window.location.href = "/home"
}
}
}
}
</script>
<template>

Loading…
Cancel
Save