feat: added profile page and edit profile details functionality
This commit is contained in:
parent
8197770411
commit
4f25042810
@ -5,7 +5,7 @@ import frappe
|
|||||||
def get_users():
|
def get_users():
|
||||||
users = frappe.qb.get_query(
|
users = frappe.qb.get_query(
|
||||||
"User",
|
"User",
|
||||||
fields=["name", "email", "enabled", "user_image", "full_name", "user_type"],
|
fields=["name", "email", "enabled", "user_image", "first_name", "last_name", "full_name", "user_type"],
|
||||||
order_by="full_name asc",
|
order_by="full_name asc",
|
||||||
distinct=True,
|
distinct=True,
|
||||||
).run(as_dict=1)
|
).run(as_dict=1)
|
||||||
|
|||||||
64
frontend/src/components/Settings/ProfileImageEditor.vue
Normal file
64
frontend/src/components/Settings/ProfileImageEditor.vue
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<FileUploader
|
||||||
|
@success="(file) => setUserImage(file.file_url)"
|
||||||
|
:validateFile="validateFile"
|
||||||
|
>
|
||||||
|
<template v-slot="{ file, progress, error, uploading, openFileSelector }">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<button
|
||||||
|
class="group relative rounded-full border-2"
|
||||||
|
@click="openFileSelector"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 grid place-items-center rounded-full bg-gray-400/20 text-base text-gray-600 transition-opacity"
|
||||||
|
:class="[
|
||||||
|
uploading ? 'opacity-100' : 'opacity-0 group-hover:opacity-100',
|
||||||
|
'drop-shadow-sm',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="inline-block rounded-md bg-gray-900/60 px-2 py-1 text-white"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
uploading
|
||||||
|
? `Uploading ${progress}%`
|
||||||
|
: profile.user_image
|
||||||
|
? 'Change Image'
|
||||||
|
: 'Upload Image'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
v-if="profile.user_image"
|
||||||
|
class="h-64 w-64 rounded-full object-cover"
|
||||||
|
:src="profile.user_image"
|
||||||
|
alt="Profile Photo"
|
||||||
|
/>
|
||||||
|
<div v-else class="h-64 w-64 rounded-full bg-gray-100"></div>
|
||||||
|
</button>
|
||||||
|
<ErrorMessage class="mt-4" :message="error" />
|
||||||
|
<div class="mt-4 flex items-center gap-4">
|
||||||
|
<Button v-if="profile.user_image" @click="setUserImage(null)">
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FileUploader>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { FileUploader } from 'frappe-ui'
|
||||||
|
|
||||||
|
const profile = defineModel()
|
||||||
|
|
||||||
|
function setUserImage(url) {
|
||||||
|
profile.value.user_image = url
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateFile(file) {
|
||||||
|
let extn = file.name.split('.').pop().toLowerCase()
|
||||||
|
if (!['png', 'jpg'].includes(extn)) {
|
||||||
|
return 'Only PNG and JPG images are allowed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,3 +1,100 @@
|
|||||||
<template>
|
<template>
|
||||||
Profile Settings
|
<div v-if="profile" class="flex w-full items-center justify-between">
|
||||||
</template>
|
<div class="flex items-center gap-4">
|
||||||
|
<Avatar
|
||||||
|
class="!size-16"
|
||||||
|
:image="profile.user_image"
|
||||||
|
:label="profile.full_name"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<span class="text-2xl font-semibold">{{ profile.full_name }}</span>
|
||||||
|
<span class="text-base text-gray-700">{{ profile.email }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button :label="__('Edit profile')" @click="showProfileModal = true" />
|
||||||
|
<Dialog
|
||||||
|
:options="{ title: __('Edit Profile') }"
|
||||||
|
v-model="showProfileModal"
|
||||||
|
@after-leave="editingProfilePhoto = false"
|
||||||
|
>
|
||||||
|
<template #body-content>
|
||||||
|
<div v-if="user" class="space-y-4">
|
||||||
|
<ProfileImageEditor v-model="profile" v-if="editingProfilePhoto" />
|
||||||
|
<template v-else>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<Avatar
|
||||||
|
size="lg"
|
||||||
|
:image="profile.user_image"
|
||||||
|
:label="profile.full_name"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
:label="__('Edit Profile Photo')"
|
||||||
|
@click="editingProfilePhoto = true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<FormControl label="First Name" v-model="profile.first_name" />
|
||||||
|
<FormControl label="Last Name" v-model="profile.last_name" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<Button
|
||||||
|
v-if="editingProfilePhoto"
|
||||||
|
class="mb-2 w-full"
|
||||||
|
@click="editingProfilePhoto = false"
|
||||||
|
:label="__('Back')"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="solid"
|
||||||
|
class="w-full"
|
||||||
|
:loading="loading"
|
||||||
|
@click="updateUser"
|
||||||
|
:label="__('Save')"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import ProfileImageEditor from '@/components/Settings/ProfileImageEditor.vue'
|
||||||
|
import { usersStore } from '@/stores/users'
|
||||||
|
import { Dialog, Avatar, createResource } from 'frappe-ui'
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const { getUser, users } = usersStore()
|
||||||
|
|
||||||
|
const user = computed(() => getUser() || {})
|
||||||
|
|
||||||
|
const showProfileModal = ref(false)
|
||||||
|
|
||||||
|
const editingProfilePhoto = ref(false)
|
||||||
|
const profile = ref({})
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
function updateUser() {
|
||||||
|
loading.value = true
|
||||||
|
const fieldname = {
|
||||||
|
first_name: profile.value.first_name,
|
||||||
|
last_name: profile.value.last_name,
|
||||||
|
user_image: profile.value.user_image,
|
||||||
|
}
|
||||||
|
createResource({
|
||||||
|
url: 'frappe.client.set_value',
|
||||||
|
params: {
|
||||||
|
doctype: 'User',
|
||||||
|
name: user.value.name,
|
||||||
|
fieldname,
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
onSuccess: () => {
|
||||||
|
loading.value = false
|
||||||
|
showProfileModal.value = false
|
||||||
|
users.reload()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
profile.value = { ...user.value }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|||||||
@ -40,6 +40,8 @@ export const usersStore = defineStore('crm-users', () => {
|
|||||||
name: email,
|
name: email,
|
||||||
email: email,
|
email: email,
|
||||||
full_name: email.split('@')[0],
|
full_name: email.split('@')[0],
|
||||||
|
first_name: email.split('@')[0],
|
||||||
|
last_name: '',
|
||||||
user_image: null,
|
user_image: null,
|
||||||
role: null,
|
role: null,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user