add avatar on account-settings

This commit is contained in:
yogesh_gohil
2019-11-21 18:45:22 +05:30
parent 4ec34550ed
commit 812afc2dab
18 changed files with 262 additions and 28 deletions

View File

@ -251,6 +251,7 @@ class CompanyController extends Controller
} }
return response()->json([ return response()->json([
'user' => $user,
'success' => true 'success' => true
]); ]);
} }

View File

@ -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) public function adminCompany(CompanyRequest $request)
{ {
$setting = Setting::getSetting('profile_complete'); $setting = Setting::getSetting('profile_complete');

View File

@ -603,6 +603,7 @@ export default {
updated_message: 'Company information updated successfully' updated_message: 'Company information updated successfully'
}, },
account_settings: { account_settings: {
profile_picture: 'Profile Picture',
name: 'Name', name: 'Name',
email: 'Email', email: 'Email',
password: 'Password', password: 'Password',

View File

@ -599,6 +599,7 @@ export default {
updated_message: 'Información de la empresa actualizada con éxito' updated_message: 'Información de la empresa actualizada con éxito'
}, },
account_settings: { account_settings: {
profile_picture: 'Foto de perfil',
name: 'Nombre', name: 'Nombre',
email: 'Email', email: 'Email',
password: 'Contraseña', password: 'Contraseña',

View File

@ -599,6 +599,7 @@ export default {
updated_message: 'Informations sur la société mises à jour avec succès' updated_message: 'Informations sur la société mises à jour avec succès'
}, },
account_settings: { account_settings: {
profile_picture: 'Image de profil',
name: 'Nom', name: 'Nom',
email: 'Email', email: 'Email',
password: 'Mot de passe', password: 'Mot de passe',

View File

@ -12,11 +12,7 @@ export const loadData = ({ commit, dispatch, state }, id) => {
export const editCompany = ({ commit, dispatch, state }, data) => { export const editCompany = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.axios.post('/api/settings/company', data, { window.axios.post('/api/settings/company', data).then((response) => {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((response) => {
// commit(types.UPDATE_ITEM, response.data) // commit(types.UPDATE_ITEM, response.data)
resolve(response) resolve(response)
}).catch((err) => { }).catch((err) => {

View File

@ -1,8 +1,9 @@
// import * as types from './mutation-types' import * as types from './mutation-types'
export const loadData = ({ commit, dispatch, state }, id) => { export const loadData = ({ commit, dispatch, state }, id) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.axios.get(`/api/settings/profile`).then((response) => { window.axios.get(`/api/settings/profile`).then((response) => {
commit(types.SET_USER, response.data)
resolve(response) resolve(response)
}).catch((err) => { }).catch((err) => {
reject(err) reject(err)
@ -13,7 +14,29 @@ export const loadData = ({ commit, dispatch, state }, id) => {
export const editUser = ({ commit, dispatch, state }, data) => { export const editUser = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
window.axios.put('/api/settings/profile', data).then((response) => { 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) resolve(response)
}).catch((err) => { }).catch((err) => {
reject(err) reject(err)

View File

@ -1,2 +1,3 @@
export const SET_USER = 'SET_USER' export const SET_USER = 'SET_USER'
export const UPDATE_USER = 'UPDATE_USER' export const UPDATE_USER = 'UPDATE_USER'
export const UPDATE_USER_AVATAR = 'UPDATE_USER_AVATAR'

View File

@ -2,10 +2,14 @@ import * as types from './mutation-types'
export default { export default {
[types.SET_USER] (state, data) { [types.SET_USER] (state, data) {
state.user = data.user state.user = data
}, },
[types.UPDATE_USER] (state, data) { [types.UPDATE_USER] (state, data) {
state.user = data state.user = data
},
[types.UPDATE_USER_AVATAR] (state, data) {
state.user.avatar = data.avatar
} }
} }

View File

@ -58,7 +58,7 @@
aria-expanded="false" aria-expanded="false"
class="avatar" class="avatar"
> >
<img src="/images/avatar.png" alt="Avatar"> <img :src="ProfilePicture" alt="Avatar">
</a> </a>
<v-dropdown-item> <v-dropdown-item>
<router-link class="dropdown-item" to="/admin/settings"> <router-link class="dropdown-item" to="/admin/settings">
@ -83,7 +83,25 @@
import { mapGetters, mapActions } from 'vuex' import { mapGetters, mapActions } from 'vuex'
export default { 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: { methods: {
...mapActions('userProfile', [
'loadData'
]),
...mapActions({ ...mapActions({
companySelect: 'changeCompany' companySelect: 'changeCompany'
}), }),

View File

@ -12,6 +12,9 @@
<div class="col-md-6"> <div class="col-md-6">
<label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label> <label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label>
<div id="pick-avatar" class="image-upload-box"> <div id="pick-avatar" class="image-upload-box">
<div class="overlay">
<font-awesome-icon class="white-icon" icon="cloud-upload-alt"/>
</div>
<img v-if="previewLogo" :src="previewLogo" class="preview-logo"> <img v-if="previewLogo" :src="previewLogo" class="preview-logo">
<div v-else class="upload-content"> <div v-else class="upload-content">
<font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/> <font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/>
@ -174,7 +177,6 @@ export default {
isFetchingData: false, isFetchingData: false,
formData: { formData: {
name: null, name: null,
logo: '',
email: '', email: '',
phone: '', phone: '',
zip: '', zip: '',
@ -301,17 +303,8 @@ export default {
return true return true
} }
this.isLoading = 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) { if (response.data.success) {
this.isLoading = false this.isLoading = false
if (this.fileObject && this.previewLogo) { if (this.fileObject && this.previewLogo) {

View File

@ -8,6 +8,31 @@
{{ $t('settings.account_settings.section_description') }} {{ $t('settings.account_settings.section_description') }}
</p> </p>
</div> </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-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="row">
<div class="col-md-6 mb-4 form-group"> <div class="col-md-6 mb-4 form-group">
<label class="input-label">{{ $tc('settings.account_settings.name') }}</label> <label class="input-label">{{ $tc('settings.account_settings.name') }}</label>
@ -81,19 +106,33 @@
<script> <script>
import { validationMixin } from 'vuelidate' import { validationMixin } from 'vuelidate'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import AvatarCropper from 'vue-avatar-cropper'
const { required, requiredIf, sameAs, email, minLength } = require('vuelidate/lib/validators') const { required, requiredIf, sameAs, email, minLength } = require('vuelidate/lib/validators')
export default { export default {
components: { AvatarCropper },
mixins: [validationMixin], mixins: [validationMixin],
data () { data () {
return { return {
isLoading: false, cropperOutputOptions: {
width: 150,
height: 150
},
cropperOptions: {
autoCropArea: 1,
viewMode: 0,
movable: true,
zoomable: true
},
formData: { formData: {
name: null, name: null,
email: null, email: null,
password: null, password: null,
confirm_password: null confirm_password: null
} },
isLoading: false,
previewAvatar: null,
fileObject: null
} }
}, },
validations: { validations: {
@ -128,12 +167,23 @@ export default {
methods: { methods: {
...mapActions('userProfile', [ ...mapActions('userProfile', [
'loadData', '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 () { async setInitialData () {
let response = await this.loadData() let response = await this.loadData()
this.formData.name = response.data.name this.formData.name = response.data.name
this.formData.email = response.data.email this.formData.email = response.data.email
this.previewAvatar = response.data.avatar
}, },
async updateUserData () { async updateUserData () {
this.$v.formData.$touch() this.$v.formData.$touch()
@ -151,6 +201,14 @@ export default {
let response = await this.editUser(data) let response = await this.editUser(data)
if (response.data.success) { if (response.data.success) {
this.isLoading = false 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')) window.toastr['success'](this.$t('settings.account_settings.updated_message'))
return true return true
} }

View File

@ -7,6 +7,9 @@
<div class="col-md-6"> <div class="col-md-6">
<label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label> <label class="input-label">{{ $tc('settings.company_info.company_logo') }}</label>
<div id="pick-avatar" class="image-upload-box"> <div id="pick-avatar" class="image-upload-box">
<div class="overlay">
<font-awesome-icon class="white-icon" icon="cloud-upload-alt"/>
</div>
<img v-if="previewLogo" :src="previewLogo" class="preview-logo"> <img v-if="previewLogo" :src="previewLogo" class="preview-logo">
<div v-else class="upload-content"> <div v-else class="upload-content">
<font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/> <font-awesome-icon class="upload-icon" icon="cloud-upload-alt"/>

View File

@ -3,6 +3,31 @@
<form action="" @submit.prevent="next()"> <form action="" @submit.prevent="next()">
<p class="form-title">{{ $t('wizard.account_info') }}</p> <p class="form-title">{{ $t('wizard.account_info') }}</p>
<p class="form-desc">{{ $t('wizard.account_info_desc') }}</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="row">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">{{ $t('wizard.name') }}</label><span class="text-danger"> *</span> <label class="form-label">{{ $t('wizard.name') }}</label><span class="text-danger"> *</span>
@ -75,24 +100,37 @@
</div> </div>
</template> </template>
<script> <script>
import MultiSelect from 'vue-multiselect' import AvatarCropper from 'vue-avatar-cropper'
import { validationMixin } from 'vuelidate' import { validationMixin } from 'vuelidate'
import { mapActions } from 'vuex'
const { required, requiredIf, sameAs, minLength, email } = require('vuelidate/lib/validators') const { required, requiredIf, sameAs, minLength, email } = require('vuelidate/lib/validators')
export default { export default {
components: { components: {
MultiSelect AvatarCropper
}, },
mixins: [validationMixin], mixins: [validationMixin],
data () { data () {
return { return {
cropperOutputOptions: {
width: 150,
height: 150
},
cropperOptions: {
autoCropArea: 1,
viewMode: 0,
movable: true,
zoomable: true
},
profileData: { profileData: {
name: null, name: null,
email: null, email: null,
password: null, password: null,
confirm_password: null confirm_password: null
}, },
loading: false loading: false,
previewAvatar: null,
fileObject: null
} }
}, },
validations: { validations: {
@ -124,6 +162,18 @@ export default {
} }
}, },
methods: { 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 () { async next () {
this.$v.profileData.$touch() this.$v.profileData.$touch()
if (this.$v.profileData.$invalid) { if (this.$v.profileData.$invalid) {
@ -131,7 +181,20 @@ export default {
} }
this.loading = true this.loading = true
let response = await window.axios.post('/api/admin/onboarding/profile', this.profileData) let response = await window.axios.post('/api/admin/onboarding/profile', this.profileData)
console.log('user_id', response.data.user.id)
if (response.data) { 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.$emit('next')
this.loading = false this.loading = false
} }

View File

@ -53,7 +53,8 @@ import {
faPaperPlane, faPaperPlane,
faEyeSlash, faEyeSlash,
faSyncAlt, faSyncAlt,
faRocket faRocket,
faCamera
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { far } from '@fortawesome/free-regular-svg-icons' import { far } from '@fortawesome/free-regular-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
@ -117,7 +118,8 @@ library.add(
faCopy, faCopy,
faPaperPlane, faPaperPlane,
faSyncAlt, faSyncAlt,
faRocket faRocket,
faCamera
) )
Vue.component('font-awesome-icon', FontAwesomeIcon) Vue.component('font-awesome-icon', FontAwesomeIcon)

View File

@ -35,4 +35,41 @@
margin-bottom: 10px; 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-height: 80% !important;
}
}

View File

@ -62,6 +62,7 @@
.avatar img { .avatar img {
width: 36px; width: 36px;
height: 36px;
border-radius: 2px; border-radius: 2px;
} }

View File

@ -96,6 +96,11 @@ Route::group(['middleware' => 'redirect-if-installed'], function () {
'uses' => 'OnboardingController@adminProfile' 'uses' => 'OnboardingController@adminProfile'
]); ]);
Route::post('/admin/profile/upload-avatar', [
'as' => 'admin.on_boarding.avatar',
'uses' => 'OnboardingController@uploadAdminAvatar'
]);
Route::post('/admin/onboarding/company', [ Route::post('/admin/onboarding/company', [
'as' => 'admin.company', 'as' => 'admin.company',
'uses' => 'OnboardingController@adminCompany' 'uses' => 'OnboardingController@adminCompany'