
{{ user.first_name + ' ' + user.last_name }}
{{ user.email }}
EDIT PROFILE
UPDATE PROFILE
Profile Details
Update your profile detail here
{{ branding.name }}
{{ branding.email }}
Save Changes
Name
{{v$.branding.name.required.$message}}
{{v$.branding.email.required.$message}}
{{v$.branding.email.required.$message}}
Profile Image
Upload File
Brand Logo
The logo image aspect ratio should be 7:1
Upload File
Brand Banner
The banner image aspect ratio should be 125:20
Upload File
Website Link
Brand Description
Save Changes
Crop & Save
Cancel
import 'https://js.stripe.com/v3/'
import { GOOGLE_MAP_API_KEY } from '@/constants'
import DeleteConfirmationModal from '@/components/Modal/DeleteConfirmationModal.vue'
import ConfirmationModal from '@/components/Modal/ConfirmationModal.vue'
import { useVuelidate } from '@vuelidate/core'
import { helpers, required, email, requiredIf, sameAs, integer, minValue, minLength } from '@vuelidate/validators'
import { toast } from 'vue-sonner'
import { RepositoryFactory } from '@/repositories'
import Cropper from 'cropperjs'
const Profile = RepositoryFactory.get('profile')
const imageUploader = RepositoryFactory.get('imageUploader')
const Branding = RepositoryFactory.get('branding')
export default {
name: 'UserSettings',
components: {
DeleteConfirmationModal, ConfirmationModal
},
data() {
return {
base_url: import.meta.env.VITE_API_URL,
stripeKey: import.meta.env.VITE_STRIPE_PUBLIC_KEY,
gMapApiKey: GOOGLE_MAP_API_KEY,
pageTitle: "Add New User",
userId: this.$route.params.id ? this.$route.params.id : null,
isEdit: false,
user: {
username: "",
first_name: "",
last_name: "",
email: "",
phone_number: "",
address: "",
country: null,
photo: ""
},
branding: {
name: "",
email: "",
photo: "",
logo: "",
banner: "",
link: "",
description: "",
},
profileImageFile: null,
profileImagePreview: null,
brandingProfileImageFile: null,
brandingProfileImagePreview: null,
brandingLogoImageFile: null,
brandingBannerImageFile: null,
agentImageFile: null,
agentImagePreview: null,
showDeleteModal: false,
isImageError: false,
v$: useVuelidate(),
showCropperModal: false,
cropper: null,
cropperImageType: null, // 'logo' or 'banner'
cropperImageFile: null,
cropperImageUrl: null,
}
},
mounted() {
this.getUserProfile()
this.getBranding()
},
validations() {
return {
branding: {
name: {
required: helpers.withMessage(
'Name is required',
required
)
},
email: {
required: helpers.withMessage(
'Email is required',
required
),
email: helpers.withMessage(
'Please enter a valid email',
)
},
}
}
},
methods: {
getUserProfile() {
Profile.getProfile().then(response => {
if (response.status == 200) {
this.user = response.data.profile
if (this.user.platform === 'web' || this.user.platform === 'android') {
this.setupStripe()
}
}
})
},
getBranding() {
this.setLoader(true)
Branding.getBranding().then(response => {
if (response.status === 200) {
this.branding = response.data.branding
}
this.setLoader(false)
}).catch(response => {
this.setLoader(false)
})
},
async submit() {
this.v$.user.$touch()
if (!this.user.country && this.user.address) {
this.addressError = true
toast.error("Please select address from dropdown")
return
}
if (!this.v$.user.$error) {
let profileImageId = null
try {
[profileImageId] = await Promise.all([
this.profileImageFile ? this.imageUpload(this.profileImageFile) : null
]);
if (profileImageId) {
this.user.photo = profileImageId;
}
Profile.update(this.user).then(response => {
if (response.status == 200) {
toast.success(response.data.message)
let profileData = this.profile
profileData.user.photo = response.data.updated.photo
profileData.user.first_name = response.data.updated.first_name
profileData.user.last_name = response.data.updated.last_name
profileData.user.address = response.data.updated.address
profileData.user.country = response.data.updated.country
this.setProfile(profileData)
this.isEdit = false
this.isImageError = false
}
}).catch(response => {
if (profileImageId) {
imageUploader.DeleteImage(profileImageId)
}
toast.error(response.response.data.message)
})
} catch (error) {
toast.error(error);
}
}
this.setLoader(false)
},
async submitBranding() {
this.v$.branding.$touch()
if (!this.v$.branding.$error) {
let profileImageId = null
let logoImageId = null
let bannerImageId = null
try {
[profileImageId, logoImageId, bannerImageId] = await Promise.all([
this.brandingProfileImageFile ? this.imageUpload(this.brandingProfileImageFile) : null,
this.brandingLogoImageFile ? this.imageUpload(this.brandingLogoImageFile) : null,
this.brandingBannerImageFile ? this.imageUpload(this.brandingBannerImageFile) : null
]);
if (profileImageId) {
this.branding.photo = profileImageId;
}
if (logoImageId) {
this.branding.logo = logoImageId;
}
if (bannerImageId) {
this.branding.banner = bannerImageId;
}
Branding.updateBranding(this.branding).then(response => {
if (response.status === 200) {
toast.success(response.data.message)
let brandingData = this.branding
brandingData.name = response.data.updated.name
brandingData.email = response.data.updated.email
brandingData.photo = response.data.updated.photo
brandingData.logo = response.data.updated.logo
brandingData.banner = response.data.updated.banner
brandingData.link = response.data.updated.link
brandingData.description = response.data.updated.description
}
}).catch(response => {
if (logoImageId) {
imageUploader.DeleteImage(logoImageId)
}
if (bannerImageId) {
imageUploader.DeleteImage(bannerImageId)
}
toast.error(response.response.data.message)
})
} catch (error) {
toast.error(error);
}
}
this.setLoader(false)
},
handleSelectAgent() {
if (!this.agent._id) {
this.resetAgent()
} else {
this.agentImageFile = null;
this.agentImagePreview = null;
this.agent = { ...this.agents.find(agent => agent._id === this.agent._id) }
}
},
handleAgentImageUpload() {
const agentImage = this.$refs.agentImage;
if (agentImage.files.length > 0) {
const selectedFile = agentImage.files[0];
this.agentImageFile = selectedFile;
this.agentImagePreview = selectedFile;
const reader = new FileReader();
reader.onload = () => {
this.agentImagePreview = reader.result;
};
reader.readAsDataURL(selectedFile);
agentImage.value = '';
} else {
this.agentImageFile = null;
this.agentImagePreview = null;
}
},
handleClickAgentImageUpload() {
document.getElementById('upload-agent').click()
},
handleProfileImageUpload() {
const profileImage = this.$refs.profileImage;
if (profileImage.files.length > 0) {
const selectedFile = profileImage.files[0];
this.profileImageFile = selectedFile;
this.profileImagePreview = selectedFile;
const reader = new FileReader();
reader.onload = () => {
this.profileImagePreview = reader.result;
};
reader.readAsDataURL(selectedFile);
profileImage.value = '';
} else {
this.profileImageFile = null;
this.profileImagePreview = null;
}
},
handleClickBrandingProfileImageUpload() {
document.getElementById('upload-branding-profile').click()
},
handleBrandingProfileImageUpload() {
const profileImage = this.$refs.brandingProfileImage;
if (profileImage.files.length > 0) {
const selectedFile = profileImage.files[0];
this.brandingProfileImageFile = selectedFile;
this.brandingProfileImagePreview = selectedFile;
const reader = new FileReader();
reader.onload = () => {
this.brandingProfileImagePreview = reader.result;
};
reader.readAsDataURL(selectedFile);
profileImage.value = '';
} else {
this.brandingProfileImageFile = null;
this.brandingProfileImagePreview = null;
}
},
handleClickBrandingLogoImageUpload() {
document.getElementById('upload-logo').click()
},
handleBrandingLogoImageUpload() {
const logoImage = this.$refs.brandingLogoImage;
if (logoImage.files.length > 0) {
const selectedFile = logoImage.files[0];
this.openCropper(selectedFile, 'logo');
logoImage.value = '';
} else {
this.brandingLogoImageFile = null;
}
},
handleClickBrandingBannerImageUpload() {
document.getElementById('upload-banner').click()
},
handleBrandingBannerImageUpload() {
const bannerImage = this.$refs.brandingBannerImage;
if (bannerImage.files.length > 0) {
const selectedFile = bannerImage.files[0];
this.openCropper(selectedFile, 'banner');
bannerImage.value = '';
} else {
this.brandingBannerImageFile = null;
}
},
openCropper(file, type) {
this.cropperImageType = type;
this.cropperImageFile = file;
this.cropperImageUrl = URL.createObjectURL(file);
this.showCropperModal = true;
this.$nextTick(() => {
const image = this.$refs.cropperImage;
if (!image) {
console.error('Cropper target image element not found (this.$refs.cropperImage).');
this.closeCropper(); // Close modal if image ref is missing
return;
}
if (this.cropper && typeof this.cropper.destroy === 'function') {
this.cropper.destroy();
}
try {
this.cropper = new Cropper(image, {
aspectRatio: type === 'logo' ? 7 / 1 : 125 / 20,
viewMode: 1,
autoCrop: true,
responsive: true,
restore: false,
checkCrossOrigin: false,
ready: () => {
console.log('Cropper.js ready event fired.');
if (this.cropper && typeof this.cropper.getCropperCanvas === 'function') {
console.log('In Cropper ready: getCropperCanvas IS a function.');
} else {
console.error('In Cropper ready: getCropperCanvas IS NOT a function or cropper is null. this.cropper:', this.cropper);
}
}
});
if (this.cropper && typeof this.cropper.getCropperCanvas === 'function') {
console.log('Cropper instance created. getCropperCanvas method exists.');
} else {
console.error('Cropper instance created, but getCropperCanvas method DOES NOT exist or cropper is null. this.cropper:', this.cropper);
}
} catch (error) {
console.error('Error initializing Cropper.js:', error);
toast.error('Failed to initialize image cropper.');
this.closeCropper();
}
});
},
async cropImage() {
if (!this.cropper) {
console.error('Cropper not initialized (this.cropper is null or undefined).');
toast.error('Cropper is not ready.');
return;
}
const selection = this.cropper.getCropperSelection?.();
if (!selection || typeof selection.$toCanvas !== 'function') {
console.error('Cannot crop image: selection or $toCanvas is missing.');
toast.error('Cannot crop image: selection method is missing.');
return;
}
try {
const canvas = await selection.$toCanvas({
maxWidth: 4096,
maxHeight: 4096,
fillColor: '#fff',
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high',
});
if (!canvas) {
console.error('Failed to get cropped canvas');
toast.error('Failed to crop image.');
return;
}
canvas.toBlob(blob => {
if (!blob) {
console.error('Failed to create blob from canvas');
toast.error('Failed to create image blob.');
return;
}
const croppedFile = new File([blob], this.cropperImageFile.name, {
type: this.cropperImageFile.type,
});
if (this.cropperImageType === 'logo') {
this.brandingLogoImageFile = croppedFile;
} else if (this.cropperImageType === 'banner') {
this.brandingBannerImageFile = croppedFile;
}
this.closeCropper();
console.log('Cropped image:', 'image');
}, this.cropperImageFile.type);
} catch (error) {
console.error('Error during crop operation:', error);
toast.error('Failed to crop image.');
}
},
closeCropper(){
if (this.cropper && typeof this.cropper.destroy === 'function') {
this.cropper.destroy();
}
this.cropper = null;
this.showCropperModal = false;
this.cropperImageUrl = null;
this.cropperImageFile = null;
this.cropperImageType = null;
},
async imageUpload(file) {
return new Promise((resolve, reject) => {
imageUploader.Upload(file)
.then(response => {
if (response.status === 200) {
resolve(response.data.document[0]);
} else {
reject('Image upload failed');
}
})
.catch((e) => {
reject('Something went wrong');
});
});
},
toggleDeleteModal(show = false) {
this.showDeleteModal = show;
}
}
}
.cam-icon {
display: none;
}
.photo-container:hover .cam-icon {
display: inline-block;
}
.text-danger {
padding-left: 0;
}
.bg-gray {
background-color: #f5f5f5;
}
.cropper-modal {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
}
.cropper-modal-content {
background: #fff;
padding: 2rem;
border-radius: 8px;
max-width: 90vw;
max-height: 90vh;
overflow: auto;
text-align: center;
}
Подробнее здесь: https://stackoverflow.com/questions/796 ... ot-working
Мобильная версия