feat: profile page

This commit is contained in:
Jannat Patel
2024-04-10 11:53:00 +05:30
parent ccb8721674
commit 13d0621881
13 changed files with 600 additions and 2072 deletions

View File

@@ -1 +1,171 @@
<template></template>
<template>
<NoPermission v-if="!$user.data" />
<div v-else-if="profile.data">
<header
class="sticky top-0 z-10 flex flex-col md:flex-row md:items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs class="h-7" :items="breadcrumbs" />
</header>
<div class="relative">
<img
v-if="profile.data.cover_image"
:src="profile.data.cover_image"
class="h-[130px] w-full"
/>
<div
v-else
:class="{ 'bg-gray-50': !profile.data.cover_image }"
class="h-[130px] w-full"
></div>
<div class="absolute top-0 right-0" v-if="isSessionUser()">
<Button variant="outline">
<template #icon>
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
</template>
</Button>
</div>
<div class="mx-auto -mt-4 max-w-4xl translate-x-0 sm:px-5">
<div class="flex items-center">
<div>
<img
v-if="profile.data.user_image"
:src="profile.data.user_image"
class="object-cover h-[100px] w-[100px] rounded-full border-4 border-white object-cover"
/>
<UserAvatar
v-else
:user="profile.data"
class="object-cover h-[100px] w-[100px] rounded-full border-4 border-white object-cover"
/>
</div>
<div class="ml-6">
<h2 class="mt-2 text-3xl font-semibold text-gray-900">
{{ profile.data.full_name }}
</h2>
<div class="mt-2 text-base text-gray-700">
{{ profile.data.headline }}
</div>
</div>
<Button v-if="isSessionUser()" class="ml-auto" @click="editProfile()">
<template #prefix>
<Edit class="w-4 h-4 stroke-1.5 text-gray-700" />
</template>
{{ __('Edit Profile') }}
</Button>
</div>
<div class="mb-4 mt-6">
<TabButtons
class="inline-block"
:buttons="getTabButtons()"
v-model="activeTab"
/>
</div>
<router-view :profile="profile" />
</div>
</div>
</div>
<EditProfile
v-model="showProfileModal"
v-model:reloadProfile="profile"
:profile="profile"
/>
<EditCoverImage />
</template>
<script setup>
import { Breadcrumbs, createResource, Button, TabButtons } from 'frappe-ui'
import { computed, inject, reactive, ref, onMounted, watchEffect } from 'vue'
import { sessionStore } from '@/stores/session'
import { Edit } from 'lucide-vue-next'
import UserAvatar from '@/components/UserAvatar.vue'
import { useRoute, useRouter } from 'vue-router'
import NoPermission from '@/components/NoPermission.vue'
import { convertToTitleCase } from '@/utils'
import EditProfile from '@/components/Modals/EditProfile.vue'
import EditCoverImage from '@/components/Modals/EditCoverImage.vue'
const { user } = sessionStore()
const $user = inject('$user')
const route = useRoute()
const router = useRouter()
const activeTab = ref('')
const showProfileModal = ref(false)
const props = defineProps({
username: {
type: String,
required: true,
},
})
onMounted(() => {
if ($user.data) profile.reload()
setActiveTab()
})
const profile = createResource({
url: 'frappe.client.get',
params: {
doctype: 'User',
filters: {
username: props.username,
},
},
})
const setActiveTab = () => {
let fragments = route.path.split('/')
let sections = ['certificates', 'settings']
sections.forEach((section) => {
if (fragments.includes(section)) {
activeTab.value = convertToTitleCase(section)
}
})
if (!activeTab.value) activeTab.value = 'About'
}
watchEffect(() => {
if (activeTab.value) {
let route = {
About: { name: 'ProfileAbout' },
Certificates: { name: 'ProfileCertificates' },
Settings: { name: 'ProfileSettings' },
}[activeTab.value]
router.push(route)
}
})
const editProfile = () => {
showProfileModal.value = true
}
const isSessionUser = () => {
return $user.data?.email === profile.data?.email
}
const getTabButtons = () => {
let buttons = [{ label: 'About' }, { label: 'Certificates' }]
if ($user.data?.is_moderator) buttons.push({ label: 'Settings' })
return buttons
}
const breadcrumbs = computed(() => {
let crumbs = [
{
label: 'People',
},
{
label: profile.data?.full_name,
route: {
name: 'Profile',
params: {
username: user.doc?.username,
},
},
},
]
return crumbs
})
</script>

View File

@@ -0,0 +1,23 @@
<template>
<div class="mt-7">
<h2 class="mb-3 text-lg font-semibold text-gray-900">
{{ __('About') }}
</h2>
<div
v-if="profile.data.bio"
v-html="profile.data.bio"
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal"
></div>
<div v-else class="text-gray-700 text-sm italic">
{{ __('No introduction') }}
</div>
</div>
</template>
<script setup>
const props = defineProps({
profile: {
type: Object,
required: true,
},
})
</script>

View File

@@ -0,0 +1,46 @@
<template>
<div class="mt-7">
<h2 class="mb-3 text-lg font-semibold text-gray-900">
{{ __('Certificates') }}
</h2>
<div class="grid grid-cols-3 gap-4">
<div
v-for="certificate in certificates.data"
:key="certificate.name"
class="bg-white shadow rounded-lg p-3 cursor-pointer"
>
<div class="font-medium">
{{ certificate.course_title }}
</div>
<div class="mt-2">
<span class="text-xs text-gray-700"> {{ __('issued on') }}: </span>
{{ dayjs(certificate.issue_date).format('DD MMM YYYY') }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { createResource } from 'frappe-ui'
import { inject } from 'vue'
const dayjs = inject('$dayjs')
const props = defineProps({
profile: {
type: Object,
required: true,
},
})
const certificates = createResource({
url: 'frappe.client.get_list',
params: {
doctype: 'LMS Certificate',
fields: ['name', 'course', 'course_title', 'issue_date'],
filters: {
member: props.profile.data.name,
},
},
auto: true,
})
</script>

View File

@@ -0,0 +1 @@
<template></template>