mirror of
				https://github.com/crater-invoice/crater.git
				synced 2025-10-30 21:21:09 -04:00 
			
		
		
		
	Merge branch 'user-avatar' into 'master'
User Avatar See merge request mohit.panjvani/crater-web!67
This commit is contained in:
		| @ -251,6 +251,7 @@ class CompanyController extends Controller | ||||
|         } | ||||
|  | ||||
|         return response()->json([ | ||||
|             'user' => $user, | ||||
|             'success' => true | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
| @ -99,6 +99,32 @@ class OnboardingController extends Controller | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function uploadAdminAvatar(Request $request) | ||||
|     { | ||||
|         $setting = Setting::getSetting('profile_complete'); | ||||
|  | ||||
|         if ($setting == '1' || $setting == 'COMPLETED') { | ||||
|             return response()->json(['error' => 'Profile already created.']); | ||||
|         } | ||||
|         $data = json_decode($request->admin_avatar); | ||||
|  | ||||
|         if($data) { | ||||
|             $user = User::find($data->id); | ||||
|             if($user) { | ||||
|                 $user->clearMediaCollection('admin_avatar'); | ||||
|  | ||||
|                 $user->addMediaFromBase64($data->data) | ||||
|                     ->usingFileName($data->name) | ||||
|                     ->toMediaCollection('admin_avatar'); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return response()->json([ | ||||
|             'user' => $user, | ||||
|             'success' => true | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function adminCompany(CompanyRequest $request) | ||||
|     { | ||||
|         $setting = Setting::getSetting('profile_complete'); | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								public/images/default-avatar.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/images/default-avatar.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 32 KiB | 
| @ -603,6 +603,7 @@ export default { | ||||
|       updated_message: 'Company information updated successfully' | ||||
|     }, | ||||
|     account_settings: { | ||||
|       profile_picture: 'Profile Picture', | ||||
|       name: 'Name', | ||||
|       email: 'Email', | ||||
|       password: 'Password', | ||||
|  | ||||
| @ -599,6 +599,7 @@ export default { | ||||
|       updated_message: 'Información de la empresa actualizada con éxito' | ||||
|     }, | ||||
|     account_settings: { | ||||
|       profile_picture: 'Foto de perfil', | ||||
|       name: 'Nombre', | ||||
|       email: 'Email', | ||||
|       password: 'Contraseña', | ||||
|  | ||||
| @ -599,6 +599,7 @@ export default { | ||||
|       updated_message: 'Informations sur la société mises à jour avec succès' | ||||
|     }, | ||||
|     account_settings: { | ||||
|       profile_picture: 'Image de profil', | ||||
|       name: 'Nom', | ||||
|       email: 'Email', | ||||
|       password: 'Mot de passe', | ||||
|  | ||||
| @ -12,11 +12,7 @@ export const loadData = ({ commit, dispatch, state }, id) => { | ||||
|  | ||||
| export const editCompany = ({ commit, dispatch, state }, data) => { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     window.axios.post('/api/settings/company', data, { | ||||
|       headers: { | ||||
|         'Content-Type': 'multipart/form-data' | ||||
|       } | ||||
|     }).then((response) => { | ||||
|     window.axios.post('/api/settings/company', data).then((response) => { | ||||
|       // commit(types.UPDATE_ITEM, response.data) | ||||
|       resolve(response) | ||||
|     }).catch((err) => { | ||||
|  | ||||
| @ -1,8 +1,9 @@ | ||||
| // import * as types from './mutation-types' | ||||
| import * as types from './mutation-types' | ||||
|  | ||||
| export const loadData = ({ commit, dispatch, state }, id) => { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     window.axios.get(`/api/settings/profile`).then((response) => { | ||||
|       commit(types.SET_USER, response.data) | ||||
|       resolve(response) | ||||
|     }).catch((err) => { | ||||
|       reject(err) | ||||
| @ -13,7 +14,29 @@ export const loadData = ({ commit, dispatch, state }, id) => { | ||||
| export const editUser = ({ commit, dispatch, state }, data) => { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     window.axios.put('/api/settings/profile', data).then((response) => { | ||||
|       // commit(types.UPDATE_USER, response.data) | ||||
|       commit(types.UPDATE_USER, response.data) | ||||
|       resolve(response) | ||||
|     }).catch((err) => { | ||||
|       reject(err) | ||||
|     }) | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export const uploadOnboardAvatar = ({ commit, dispatch, state }, data) => { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     window.axios.post(`/api/admin/profile/upload-avatar`, data).then((response) => { | ||||
|       commit(types.UPDATE_USER, response.data.user) | ||||
|       resolve(response) | ||||
|     }).catch((err) => { | ||||
|       reject(err) | ||||
|     }) | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export const uploadAvatar = ({ commit, dispatch, state }, data) => { | ||||
|   return new Promise((resolve, reject) => { | ||||
|     window.axios.post('/api/settings/profile/upload-avatar', data).then((response) => { | ||||
|       commit(types.UPDATE_USER, response.data.user) | ||||
|       resolve(response) | ||||
|     }).catch((err) => { | ||||
|       reject(err) | ||||
|  | ||||
| @ -1,2 +1,3 @@ | ||||
| export const SET_USER = 'SET_USER' | ||||
| export const UPDATE_USER = 'UPDATE_USER' | ||||
| export const UPDATE_USER_AVATAR = 'UPDATE_USER_AVATAR' | ||||
|  | ||||
| @ -2,10 +2,14 @@ import * as types from './mutation-types' | ||||
|  | ||||
| export default { | ||||
|   [types.SET_USER] (state, data) { | ||||
|     state.user = data.user | ||||
|     state.user = data | ||||
|   }, | ||||
|  | ||||
|   [types.UPDATE_USER] (state, data) { | ||||
|     state.user = data | ||||
|   }, | ||||
|  | ||||
|   [types.UPDATE_USER_AVATAR] (state, data) { | ||||
|     state.user.avatar = data.avatar | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -58,7 +58,7 @@ | ||||
|             aria-expanded="false" | ||||
|             class="avatar" | ||||
|           > | ||||
|             <img src="/images/avatar.png" alt="Avatar"> | ||||
|             <img :src="ProfilePicture" alt="Avatar"> | ||||
|           </a> | ||||
|           <v-dropdown-item> | ||||
|             <router-link class="dropdown-item" to="/admin/settings"> | ||||
| @ -83,7 +83,25 @@ | ||||
| import { mapGetters, mapActions } from 'vuex' | ||||
|  | ||||
| export default { | ||||
|   computed: { | ||||
|     ...mapGetters('userProfile', [ | ||||
|       'user' | ||||
|     ]), | ||||
|     ProfilePicture () { | ||||
|       if (this.user && this.user.avatar !== null) { | ||||
|         return this.user.avatar | ||||
|       } else { | ||||
|         return '/images/default-avatar.jpg' | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   created () { | ||||
|     this.loadData() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('userProfile', [ | ||||
|       'loadData' | ||||
|     ]), | ||||
|     ...mapActions({ | ||||
|       companySelect: 'changeCompany' | ||||
|     }), | ||||
|  | ||||
| @ -12,6 +12,9 @@ | ||||
|           <div class="col-md-6"> | ||||
|             <label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label> | ||||
|             <div id="pick-avatar" class="image-upload-box"> | ||||
|               <div class="overlay"> | ||||
|                 <font-awesome-icon class="white-icon" icon="camera"/> | ||||
|               </div> | ||||
|               <img v-if="previewLogo" :src="previewLogo" class="preview-logo"> | ||||
|               <div v-else class="upload-content"> | ||||
|                 <font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/> | ||||
| @ -174,7 +177,6 @@ export default { | ||||
|       isFetchingData: false, | ||||
|       formData: { | ||||
|         name: null, | ||||
|         logo: '', | ||||
|         email: '', | ||||
|         phone: '', | ||||
|         zip: '', | ||||
| @ -301,17 +303,8 @@ export default { | ||||
|         return true | ||||
|       } | ||||
|       this.isLoading = true | ||||
|       let data = new FormData() | ||||
|       data.append('name', this.formData.name) | ||||
|       data.append('address_street_1', this.formData.address_street_1) | ||||
|       data.append('address_street_2', this.formData.address_street_2) | ||||
|       data.append('city_id', this.formData.city_id) | ||||
|       data.append('state_id', this.formData.state_id) | ||||
|       data.append('country_id', this.formData.country_id) | ||||
|       data.append('zip', this.formData.zip) | ||||
|       data.append('phone', this.formData.phone) | ||||
|  | ||||
|       let response = await this.editCompany(data) | ||||
|       let response = await this.editCompany(this.formData) | ||||
|       if (response.data.success) { | ||||
|         this.isLoading = false | ||||
|         if (this.fileObject && this.previewLogo) { | ||||
|  | ||||
| @ -8,6 +8,31 @@ | ||||
|             {{ $t('settings.account_settings.section_description') }} | ||||
|           </p> | ||||
|         </div> | ||||
|         <div class="row mb-4"> | ||||
|           <div class="col-md-6"> | ||||
|             <label class="input-label">{{ $tc('settings.account_settings.profile_picture') }}</label> | ||||
|             <div id="pick-avatar" class="image-upload-box avatar-upload"> | ||||
|               <div class="overlay"> | ||||
|                 <font-awesome-icon class="white-icon" icon="camera"/> | ||||
|               </div> | ||||
|               <img v-if="previewAvatar" :src="previewAvatar" class="preview-logo"> | ||||
|               <div v-if="!previewAvatar" class="upload-content"> | ||||
|                 <font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/> | ||||
|                 <p class="upload-text"> {{ $tc('general.choose_file') }} </p> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|           <avatar-cropper | ||||
|             :labels="{ submit: 'submit', cancel: 'Cancle'}" | ||||
|             :cropper-options="cropperOptions" | ||||
|             :output-options="cropperOutputOptions" | ||||
|             :output-quality="0.8" | ||||
|             :upload-handler="cropperHandler" | ||||
|             trigger="#pick-avatar" | ||||
|             @changed="setFileObject" | ||||
|             @error="handleUploadError" | ||||
|           /> | ||||
|         </div> | ||||
|         <div class="row"> | ||||
|           <div class="col-md-6 mb-4 form-group"> | ||||
|             <label class="input-label">{{ $tc('settings.account_settings.name') }}</label> | ||||
| @ -81,19 +106,33 @@ | ||||
| <script> | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapActions } from 'vuex' | ||||
| import AvatarCropper from 'vue-avatar-cropper' | ||||
| const { required, requiredIf, sameAs, email, minLength } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { AvatarCropper }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       isLoading: false, | ||||
|       cropperOutputOptions: { | ||||
|         width: 150, | ||||
|         height: 150 | ||||
|       }, | ||||
|       cropperOptions: { | ||||
|         autoCropArea: 1, | ||||
|         viewMode: 0, | ||||
|         movable: true, | ||||
|         zoomable: true | ||||
|       }, | ||||
|       formData: { | ||||
|         name: null, | ||||
|         email: null, | ||||
|         password: null, | ||||
|         confirm_password: null | ||||
|       } | ||||
|       }, | ||||
|       isLoading: false, | ||||
|       previewAvatar: null, | ||||
|       fileObject: null | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
| @ -128,12 +167,27 @@ export default { | ||||
|   methods: { | ||||
|     ...mapActions('userProfile', [ | ||||
|       'loadData', | ||||
|       'editUser' | ||||
|       'editUser', | ||||
|       'uploadAvatar' | ||||
|     ]), | ||||
|     cropperHandler (cropper) { | ||||
|       this.previewAvatar = cropper.getCroppedCanvas().toDataURL(this.cropperOutputMime) | ||||
|     }, | ||||
|     setFileObject (file) { | ||||
|       this.fileObject = file | ||||
|     }, | ||||
|     handleUploadError (message, type, xhr) { | ||||
|       window.toastr['error']('Oops! Something went wrong...') | ||||
|     }, | ||||
|     async setInitialData () { | ||||
|       let response = await this.loadData() | ||||
|       this.formData.name = response.data.name | ||||
|       this.formData.email = response.data.email | ||||
|       if (response.data.avatar) { | ||||
|         this.previewAvatar = response.data.avatar | ||||
|       } else { | ||||
|         this.previewAvatar = '/images/default-avatar.jpg' | ||||
|       } | ||||
|     }, | ||||
|     async updateUserData () { | ||||
|       this.$v.formData.$touch() | ||||
| @ -151,6 +205,14 @@ export default { | ||||
|       let response = await this.editUser(data) | ||||
|       if (response.data.success) { | ||||
|         this.isLoading = false | ||||
|         if (this.fileObject && this.previewAvatar) { | ||||
|           let avatarData = new FormData() | ||||
|           avatarData.append('admin_avatar', JSON.stringify({ | ||||
|             name: this.fileObject.name, | ||||
|             data: this.previewAvatar | ||||
|           })) | ||||
|           this.uploadAvatar(avatarData) | ||||
|         } | ||||
|         window.toastr['success'](this.$t('settings.account_settings.updated_message')) | ||||
|         return true | ||||
|       } | ||||
|  | ||||
| @ -7,6 +7,9 @@ | ||||
|         <div class="col-md-6"> | ||||
|           <label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label> | ||||
|           <div id="pick-avatar" class="image-upload-box"> | ||||
|             <div class="overlay"> | ||||
|               <font-awesome-icon class="white-icon" icon="camera"/> | ||||
|             </div> | ||||
|             <img v-if="previewLogo" :src="previewLogo" class="preview-logo"> | ||||
|             <div v-else class="upload-content"> | ||||
|               <font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/> | ||||
|  | ||||
| @ -3,6 +3,31 @@ | ||||
|     <form action="" @submit.prevent="next()"> | ||||
|       <p class="form-title">{{ $t('wizard.account_info') }}</p> | ||||
|       <p class="form-desc">{{ $t('wizard.account_info_desc') }}</p> | ||||
|       <div class="row mb-4"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="input-label">{{ $tc('settings.account_settings.profile_picture') }}</label> | ||||
|           <div id="pick-avatar" class="image-upload-box avatar-upload"> | ||||
|             <div class="overlay"> | ||||
|               <font-awesome-icon class="white-icon" icon="camera"/> | ||||
|             </div> | ||||
|             <img v-if="previewAvatar" :src="previewAvatar" class="preview-logo"> | ||||
|             <div v-else class="upload-content"> | ||||
|               <font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/> | ||||
|               <p class="upload-text"> {{ $tc('general.choose_file') }} </p> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <avatar-cropper | ||||
|           :labels="{ submit: 'submit', cancel: 'Cancle'}" | ||||
|           :cropper-options="cropperOptions" | ||||
|           :output-options="cropperOutputOptions" | ||||
|           :output-quality="0.8" | ||||
|           :upload-handler="cropperHandler" | ||||
|           trigger="#pick-avatar" | ||||
|           @changed="setFileObject" | ||||
|           @error="handleUploadError" | ||||
|         /> | ||||
|       </div> | ||||
|       <div class="row"> | ||||
|         <div class="col-md-6"> | ||||
|           <label class="form-label">{{ $t('wizard.name') }}</label><span class="text-danger"> *</span> | ||||
| @ -75,24 +100,37 @@ | ||||
|   </div> | ||||
| </template> | ||||
| <script> | ||||
| import MultiSelect from 'vue-multiselect' | ||||
| import AvatarCropper from 'vue-avatar-cropper' | ||||
| import { validationMixin } from 'vuelidate' | ||||
| import { mapActions } from 'vuex' | ||||
| const { required, requiredIf, sameAs, minLength, email } = require('vuelidate/lib/validators') | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     MultiSelect | ||||
|     AvatarCropper | ||||
|   }, | ||||
|   mixins: [validationMixin], | ||||
|   data () { | ||||
|     return { | ||||
|       cropperOutputOptions: { | ||||
|         width: 150, | ||||
|         height: 150 | ||||
|       }, | ||||
|       cropperOptions: { | ||||
|         autoCropArea: 1, | ||||
|         viewMode: 0, | ||||
|         movable: true, | ||||
|         zoomable: true | ||||
|       }, | ||||
|       profileData: { | ||||
|         name: null, | ||||
|         email: null, | ||||
|         password: null, | ||||
|         confirm_password: null | ||||
|       }, | ||||
|       loading: false | ||||
|       loading: false, | ||||
|       previewAvatar: '/images/default-avatar.jpg', | ||||
|       fileObject: null | ||||
|     } | ||||
|   }, | ||||
|   validations: { | ||||
| @ -124,6 +162,18 @@ export default { | ||||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions('userProfile', [ | ||||
|       'uploadOnboardAvatar' | ||||
|     ]), | ||||
|     cropperHandler (cropper) { | ||||
|       this.previewAvatar = cropper.getCroppedCanvas().toDataURL(this.cropperOutputMime) | ||||
|     }, | ||||
|     setFileObject (file) { | ||||
|       this.fileObject = file | ||||
|     }, | ||||
|     handleUploadError (message, type, xhr) { | ||||
|       window.toastr['error']('Oops! Something went wrong...') | ||||
|     }, | ||||
|     async next () { | ||||
|       this.$v.profileData.$touch() | ||||
|       if (this.$v.profileData.$invalid) { | ||||
| @ -131,7 +181,20 @@ export default { | ||||
|       } | ||||
|       this.loading = true | ||||
|       let response = await window.axios.post('/api/admin/onboarding/profile', this.profileData) | ||||
|       console.log('user_id', response.data.user.id) | ||||
|  | ||||
|       if (response.data) { | ||||
|         if (this.fileObject && this.previewAvatar) { | ||||
|           let avatarData = new FormData() | ||||
|           avatarData.append('admin_avatar', JSON.stringify({ | ||||
|             name: this.fileObject.name, | ||||
|             data: this.previewAvatar, | ||||
|             id: response.data.user.id | ||||
|           })) | ||||
|           console.log(avatarData); | ||||
|  | ||||
|           this.uploadOnboardAvatar(avatarData) | ||||
|         } | ||||
|         this.$emit('next') | ||||
|         this.loading = false | ||||
|       } | ||||
|  | ||||
| @ -53,7 +53,8 @@ import { | ||||
|   faPaperPlane, | ||||
|   faEyeSlash, | ||||
|   faSyncAlt, | ||||
|   faRocket | ||||
|   faRocket, | ||||
|   faCamera | ||||
| } from '@fortawesome/free-solid-svg-icons' | ||||
| import { far } from '@fortawesome/free-regular-svg-icons' | ||||
| import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' | ||||
| @ -117,7 +118,8 @@ library.add( | ||||
|   faCopy, | ||||
|   faPaperPlane, | ||||
|   faSyncAlt, | ||||
|   faRocket | ||||
|   faRocket, | ||||
|   faCamera | ||||
| ) | ||||
|  | ||||
| Vue.component('font-awesome-icon', FontAwesomeIcon) | ||||
|  | ||||
| @ -11,8 +11,10 @@ | ||||
|     cursor: pointer; | ||||
|  | ||||
|     .preview-logo { | ||||
|         max-height: 50%; | ||||
|         max-height: 80%; | ||||
|         position: absolute; | ||||
|         opacity: 1; | ||||
|         animation: fadeIn 2s ease; | ||||
|     } | ||||
|  | ||||
|     .upload-content { | ||||
| @ -35,4 +37,51 @@ | ||||
|         margin-bottom: 10px; | ||||
|     } | ||||
|  | ||||
|     .white-icon { | ||||
|         font-size: 30px; | ||||
|         line-height: 23px; | ||||
|         color: $white; | ||||
|         margin-bottom: 10px; | ||||
|     } | ||||
|  | ||||
|     .overlay { | ||||
|         position: absolute; | ||||
|         display: flex; | ||||
|         justify-content: center; | ||||
|         align-items: center; | ||||
|         top: 0; | ||||
|         left: 0; | ||||
|         width: 100%; | ||||
|         height: 100%; | ||||
|         z-index: 10; | ||||
|         transition: .5s ease; | ||||
|         background-color: rgba(0,0,0,0.5); | ||||
|         opacity: 0; | ||||
|     } | ||||
|  | ||||
|     &:hover { | ||||
|         .overlay { | ||||
|             opacity: 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| .avatar-upload { | ||||
|     height: 130px; | ||||
|     width: 130px; | ||||
|     .preview-logo { | ||||
|         max-width: 80% !important; | ||||
|     } | ||||
|  | ||||
|     @keyframes fadeIn{ | ||||
|       0%{ | ||||
|         opacity: 0; | ||||
|       } | ||||
|       100%{ | ||||
|         opacity: 1; | ||||
|       } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										1
									
								
								resources/assets/sass/partials/header.scss
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								resources/assets/sass/partials/header.scss
									
									
									
									
										vendored
									
									
								
							| @ -62,6 +62,7 @@ | ||||
|  | ||||
|     .avatar img { | ||||
|         width: 36px; | ||||
|         height: 36px; | ||||
|         border-radius: 2px; | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -96,6 +96,11 @@ Route::group(['middleware' => 'redirect-if-installed'], function () { | ||||
|         'uses' => 'OnboardingController@adminProfile' | ||||
|     ]); | ||||
|  | ||||
|     Route::post('/admin/profile/upload-avatar', [ | ||||
|         'as' => 'admin.on_boarding.avatar', | ||||
|         'uses' => 'OnboardingController@uploadAdminAvatar' | ||||
|     ]); | ||||
|  | ||||
|     Route::post('/admin/onboarding/company', [ | ||||
|         'as' => 'admin.company', | ||||
|         'uses' => 'OnboardingController@adminCompany' | ||||
|  | ||||
		Reference in New Issue
	
	Block a user