mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-29 11:15:58 +00:00
frontend & backend update
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
|
||||
<MobileNavigation />
|
||||
|
||||
<!--Confirm Popup-->
|
||||
<Confirm />
|
||||
|
||||
<!--Share Item setup-->
|
||||
<ShareCreate/>
|
||||
<ShareEdit/>
|
||||
@@ -42,6 +45,7 @@
|
||||
import MobileNavigation from '@/components/Others/MobileNavigation'
|
||||
import MobileMenu from '@/components/FilesView/MobileMenu'
|
||||
import ShareCreate from '@/components/Others/ShareCreate'
|
||||
import Confirm from '@/components/Others/Popup/Confirm'
|
||||
import ShareEdit from '@/components/Others/ShareEdit'
|
||||
import MoveItem from '@/components/Others/MoveItem'
|
||||
import Vignette from '@/components/Others/Vignette'
|
||||
@@ -61,6 +65,7 @@
|
||||
ShareEdit,
|
||||
MoveItem,
|
||||
Vignette,
|
||||
Confirm,
|
||||
MenuBar,
|
||||
Alert,
|
||||
},
|
||||
|
||||
@@ -136,6 +136,7 @@
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
|
||||
.title {
|
||||
@include font-size(22);
|
||||
text-transform: uppercase;
|
||||
|
||||
@@ -110,7 +110,8 @@
|
||||
'homeDirectory',
|
||||
]),
|
||||
hasCapacity() {
|
||||
return this.$store.getters.user.relationships.storage.data.attributes.used <= 100
|
||||
//return this.$store.getters.user.relationships.storage.data.attributes.used <= 100
|
||||
return true
|
||||
},
|
||||
directoryName() {
|
||||
return this.currentFolder ? this.currentFolder.name : this.homeDirectory.name
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
color: $theme;
|
||||
background: rgba($theme, 0.1);
|
||||
}
|
||||
|
||||
&.red {
|
||||
color: $danger;
|
||||
background: rgba($danger, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
<template>
|
||||
<div class="page-tab">
|
||||
<slot></slot>
|
||||
<div id="loader" v-if="isLoading">
|
||||
<Spinner></Spinner>
|
||||
</div>
|
||||
<slot v-if="! isLoading"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from '@/components/FilesView/Spinner'
|
||||
|
||||
export default {
|
||||
name: 'PageTab',
|
||||
props: ['isLoading'],
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</section>
|
||||
<footer class="plan-footer">
|
||||
<b class="price">
|
||||
{{ plan.data.attributes.price }} USD/Mo.
|
||||
{{ plan.data.attributes.price }}/Mo.
|
||||
</b>
|
||||
<ButtonBase @click.native="selectPlan(plan)" type="submit" button-style="secondary" class="sign-in-button">
|
||||
Sign Up
|
||||
@@ -52,12 +52,7 @@
|
||||
|
||||
axios.get('/api/public/pricing')
|
||||
.then(response => {
|
||||
this.plans = response.data.data.filter(plan => {
|
||||
if (this.$store.getters.user.relationships.subscription)
|
||||
return plan.data.attributes.capacity > this.$store.getters.user.relationships.subscription.data.attributes.capacity
|
||||
|
||||
return true
|
||||
})
|
||||
this.plans = response.data
|
||||
this.$emit('load', false)
|
||||
})
|
||||
}
|
||||
@@ -72,6 +67,7 @@
|
||||
text-align: center;
|
||||
flex: 0 0 33%;
|
||||
padding: 0 25px;
|
||||
margin-bottom: 45px;
|
||||
|
||||
.plan-wrapper {
|
||||
box-shadow: 0 7px 20px 5px hsla(220, 36%, 16%, 0.03);
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<PopupWrapper>
|
||||
|
||||
<div class="popup-image">
|
||||
<span class="emoji">{{ emoji }}</span>
|
||||
</div>
|
||||
|
||||
<PopupContent class="content">
|
||||
<h1 v-if="title" class="title">{{ title }}</h1>
|
||||
<p v-if="message" class="message">{{ message }}</p>
|
||||
</PopupContent>
|
||||
|
||||
<PopupActions>
|
||||
<ButtonBase
|
||||
@click.native="closePopup"
|
||||
button-style="secondary"
|
||||
class="popup-button"
|
||||
>Cancel
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
@click.native="confirm"
|
||||
:button-style="buttonColor"
|
||||
class="popup-button"
|
||||
>Yes, I'm sure
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</PopupWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PopupWrapper from '@/components/Others/Popup/PopupWrapper'
|
||||
import PopupActions from '@/components/Others/Popup/PopupActions'
|
||||
import PopupContent from '@/components/Others/Popup/PopupContent'
|
||||
import ButtonBase from '@/components/FilesView/ButtonBase'
|
||||
import {events} from '@/bus'
|
||||
|
||||
export default {
|
||||
name: 'ConfirmPopup',
|
||||
components: {
|
||||
PopupWrapper,
|
||||
PopupActions,
|
||||
PopupContent,
|
||||
ButtonBase,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
confirmationData: [],
|
||||
message: undefined,
|
||||
title: undefined,
|
||||
emoji: undefined,
|
||||
buttonColor: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closePopup() {
|
||||
events.$emit('popup:close')
|
||||
},
|
||||
confirm() {
|
||||
// Close popup
|
||||
events.$emit('popup:close')
|
||||
|
||||
// Confirmation popup
|
||||
events.$emit('action:confirmed', this.confirmationData)
|
||||
|
||||
// Clear confirmation data
|
||||
this.confirmationData = []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Show confirm
|
||||
events.$on('confirm:open', args => {
|
||||
this.title = args.title
|
||||
this.message = args.message
|
||||
this.emoji = '🤔'
|
||||
this.confirmationData = args.action
|
||||
this.buttonColor = 'danger-solid'
|
||||
|
||||
if (args.buttonColor) {
|
||||
this.buttonColor = args.buttonColor
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@assets/vue-file-manager/_variables';
|
||||
@import '@assets/vue-file-manager/_mixins';
|
||||
|
||||
.popup-image {
|
||||
padding-top: 20px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.emoji {
|
||||
@include font-size(56);
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
|
||||
.title {
|
||||
@include font-size(22);
|
||||
text-transform: uppercase;
|
||||
font-weight: 800;
|
||||
color: $text;
|
||||
}
|
||||
|
||||
.message {
|
||||
@include font-size(16);
|
||||
color: #333;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
.content {
|
||||
.title {
|
||||
color: $dark_mode_text_primary;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: $dark_mode_text_secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -35,6 +35,13 @@
|
||||
this.isVisibleWrapper = true
|
||||
})
|
||||
|
||||
// Open called popup
|
||||
events.$on('confirm:open', ({name}) => {
|
||||
|
||||
if (this.name === name)
|
||||
this.isVisibleWrapper = true
|
||||
})
|
||||
|
||||
// Close popup
|
||||
events.$on('popup:close', () => {
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
events.$on('popup:open', () => this.isVisibleVignette = true)
|
||||
events.$on('alert:open', () => this.isVisibleVignette = true)
|
||||
events.$on('success:open', () => this.isVisibleVignette = true)
|
||||
events.$on('confirm:open', () => this.isVisibleVignette = true)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Vendored
+278
-306
@@ -5,313 +5,285 @@ import axios from 'axios'
|
||||
import router from '@/router'
|
||||
|
||||
const Helpers = {
|
||||
install(Vue) {
|
||||
|
||||
Vue.prototype.$updateText = debounce(function (route, name, value) {
|
||||
|
||||
if (value === '') return
|
||||
|
||||
axios.patch(this.$store.getters.api + route, {name, value})
|
||||
.catch(error => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
}, 300)
|
||||
|
||||
Vue.prototype.$updateImage = function (route, name, image) {
|
||||
|
||||
// Create form
|
||||
let formData = new FormData()
|
||||
|
||||
// Add image to form
|
||||
formData.append(name, image)
|
||||
formData.append('_method', 'PATCH')
|
||||
|
||||
axios.post(this.$store.getters.api + route, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Vue.prototype.$openImageOnNewTab = function(source) {
|
||||
let win = window.open(source, '_blank')
|
||||
|
||||
win.focus()
|
||||
}
|
||||
|
||||
Vue.prototype.$createFolder = function(folderName) {
|
||||
this.$store.dispatch('createFolder', folderName)
|
||||
}
|
||||
|
||||
Vue.prototype.$uploadFiles = async function(files) {
|
||||
// Prevent submit empty files
|
||||
if (files && files.length == 0) return
|
||||
|
||||
// Check storage size
|
||||
if (! this.$isThisLocation(['public']) && this.$store.getters.app.storage.percentage >= 100) {
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😬😬😬',
|
||||
title: this.$t('popup_exceed_limit.title'),
|
||||
message: this.$t('popup_exceed_limit.message')
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let fileCount = files ? files.length : 0
|
||||
let fileCountSucceed = 1
|
||||
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: fileCount
|
||||
})
|
||||
|
||||
// Get parent id
|
||||
const rootFolder = this.$store.getters.currentFolder
|
||||
? this.$store.getters.currentFolder.unique_id
|
||||
: 0
|
||||
|
||||
for (var i = files.length - 1; i >= 0; i--) {
|
||||
let formData = new FormData()
|
||||
|
||||
// Append data
|
||||
formData.append('file', files[i])
|
||||
|
||||
// Append form data
|
||||
formData.append('parent_id', rootFolder)
|
||||
|
||||
console.log(i);
|
||||
console.log(files[i]);
|
||||
console.log(formData);
|
||||
|
||||
// Upload data
|
||||
await store.dispatch('uploadFiles', formData)
|
||||
.then(() => {
|
||||
// Progress file log
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: fileCount
|
||||
})
|
||||
|
||||
// Uploading finished
|
||||
if (fileCount === fileCountSucceed) {
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
|
||||
} else {
|
||||
// Add uploaded file
|
||||
fileCountSucceed++
|
||||
}
|
||||
}).catch(error => {
|
||||
|
||||
if (error.response.status === 423) {
|
||||
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😬😬😬',
|
||||
title: this.$t('popup_exceed_limit.title'),
|
||||
message: this.$t('popup_exceed_limit.message')
|
||||
})
|
||||
|
||||
} else if (error.response.status === 413) {
|
||||
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😟',
|
||||
title: this.$t('popup_paylod_error.title'),
|
||||
message: this.$t('popup_paylod_error.message')
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
// Show error message
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$uploadExternalFiles = async function(event, parent_id) {
|
||||
|
||||
// Prevent submit empty files
|
||||
if (event.dataTransfer.items.length == 0) return
|
||||
|
||||
// Get files
|
||||
const files = [...event.dataTransfer.items].map(item => item.getAsFile());
|
||||
|
||||
// Check storage size
|
||||
if (! this.$isThisLocation(['public']) && this.$store.getters.app.storage.percentage >= 100) {
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😬😬😬',
|
||||
title: this.$t('popup_exceed_limit.title'),
|
||||
message: this.$t('popup_exceed_limit.message')
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let fileCountSucceed = 1
|
||||
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: files.length
|
||||
})
|
||||
|
||||
for (var i = files.length - 1; i >= 0; i--) {
|
||||
|
||||
let formData = new FormData()
|
||||
|
||||
// Append data
|
||||
formData.append('file', files[i])
|
||||
|
||||
// Append form data
|
||||
formData.append('parent_id', parent_id)
|
||||
|
||||
// Upload data
|
||||
await store.dispatch('uploadFiles', formData).then(() => {
|
||||
// Progress file log
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: files.length
|
||||
})
|
||||
// Progress file log
|
||||
store.commit('INCREASE_FOLDER_ITEM', parent_id)
|
||||
|
||||
// Uploading finished
|
||||
if (files.length === fileCountSucceed) {
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
|
||||
} else {
|
||||
// Add uploaded file
|
||||
fileCountSucceed++
|
||||
}
|
||||
}).catch(error => {
|
||||
|
||||
if (error.response.status == 423) {
|
||||
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😬😬😬',
|
||||
title: this.$t('popup_exceed_limit.title'),
|
||||
message: this.$t('popup_exceed_limit.message')
|
||||
})
|
||||
|
||||
} else if (error.response.status === 413) {
|
||||
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😟',
|
||||
title: this.$t('popup_paylod_error.title'),
|
||||
message: this.$t('popup_paylod_error.message')
|
||||
})
|
||||
install(Vue) {
|
||||
|
||||
Vue.prototype.$updateText = debounce(function (route, name, value) {
|
||||
|
||||
if (value === '') return
|
||||
|
||||
axios.patch(this.$store.getters.api + route, {name, value})
|
||||
.catch(error => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
}, 300)
|
||||
|
||||
Vue.prototype.$getCreditCardBrand = function (brand) {
|
||||
return `/assets/icons/${brand}.svg`
|
||||
}
|
||||
|
||||
Vue.prototype.$updateImage = function (route, name, image) {
|
||||
|
||||
// Create form
|
||||
let formData = new FormData()
|
||||
|
||||
// Add image to form
|
||||
formData.append(name, image)
|
||||
formData.append('_method', 'PATCH')
|
||||
|
||||
axios.post(this.$store.getters.api + route, formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Vue.prototype.$openImageOnNewTab = function (source) {
|
||||
let win = window.open(source, '_blank')
|
||||
|
||||
win.focus()
|
||||
}
|
||||
|
||||
Vue.prototype.$createFolder = function (folderName) {
|
||||
this.$store.dispatch('createFolder', folderName)
|
||||
}
|
||||
|
||||
Vue.prototype.$uploadFiles = async function (files) {
|
||||
// Prevent submit empty files
|
||||
if (files && files.length == 0) return
|
||||
|
||||
let fileCount = files ? files.length : 0
|
||||
let fileCountSucceed = 1
|
||||
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: fileCount
|
||||
})
|
||||
|
||||
// Get parent id
|
||||
const rootFolder = this.$store.getters.currentFolder
|
||||
? this.$store.getters.currentFolder.unique_id
|
||||
: 0
|
||||
|
||||
for (var i = files.length - 1; i >= 0; i--) {
|
||||
let formData = new FormData()
|
||||
|
||||
// Append data
|
||||
formData.append('file', files[i])
|
||||
|
||||
// Append form data
|
||||
formData.append('parent_id', rootFolder)
|
||||
|
||||
// Upload data
|
||||
await store.dispatch('uploadFiles', formData)
|
||||
.then(() => {
|
||||
// Progress file log
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: fileCount
|
||||
})
|
||||
|
||||
// Uploading finished
|
||||
if (fileCount === fileCountSucceed) {
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
|
||||
} else {
|
||||
// Add uploaded file
|
||||
fileCountSucceed++
|
||||
}
|
||||
}).catch(error => {
|
||||
switch (error.response.status) {
|
||||
case 423:
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😬',
|
||||
title: this.$t('popup_exceed_limit.title'),
|
||||
message: this.$t('popup_exceed_limit.message')
|
||||
})
|
||||
break;
|
||||
case 413:
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😟',
|
||||
title: this.$t('popup_paylod_error.title'),
|
||||
message: this.$t('popup_paylod_error.message')
|
||||
})
|
||||
break;
|
||||
default:
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$uploadExternalFiles = async function (event, parent_id) {
|
||||
|
||||
// Prevent submit empty files
|
||||
if (event.dataTransfer.items.length == 0) return
|
||||
|
||||
// Get files
|
||||
const files = [...event.dataTransfer.items].map(item => item.getAsFile());
|
||||
|
||||
let fileCountSucceed = 1
|
||||
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: files.length
|
||||
})
|
||||
|
||||
for (var i = files.length - 1; i >= 0; i--) {
|
||||
|
||||
let formData = new FormData()
|
||||
|
||||
// Append data
|
||||
formData.append('file', files[i])
|
||||
|
||||
// Append form data
|
||||
formData.append('parent_id', parent_id)
|
||||
|
||||
// Upload data
|
||||
await store.dispatch('uploadFiles', formData).then(() => {
|
||||
// Progress file log
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', {
|
||||
current: fileCountSucceed,
|
||||
total: files.length
|
||||
})
|
||||
// Progress file log
|
||||
store.commit('INCREASE_FOLDER_ITEM', parent_id)
|
||||
|
||||
// Uploading finished
|
||||
if (files.length === fileCountSucceed) {
|
||||
store.commit('UPDATE_FILE_COUNT_PROGRESS', undefined)
|
||||
} else {
|
||||
// Add uploaded file
|
||||
fileCountSucceed++
|
||||
}
|
||||
}).catch(error => {
|
||||
switch (error.response.status) {
|
||||
case 423:
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😬',
|
||||
title: this.$t('popup_exceed_limit.title'),
|
||||
message: this.$t('popup_exceed_limit.message')
|
||||
})
|
||||
break;
|
||||
case 413:
|
||||
events.$emit('alert:open', {
|
||||
emoji: '😟',
|
||||
title: this.$t('popup_paylod_error.title'),
|
||||
message: this.$t('popup_paylod_error.message')
|
||||
})
|
||||
break;
|
||||
default:
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$downloadFile = function (url, filename) {
|
||||
var anchor = document.createElement('a')
|
||||
|
||||
anchor.href = url
|
||||
|
||||
anchor.download = filename
|
||||
|
||||
document.body.appendChild(anchor)
|
||||
|
||||
anchor.click()
|
||||
}
|
||||
|
||||
Vue.prototype.$closePopup = function () {
|
||||
events.$emit('popup:close')
|
||||
}
|
||||
|
||||
Vue.prototype.$isThisRoute = function (route, locations) {
|
||||
|
||||
return includes(locations, route.name)
|
||||
}
|
||||
|
||||
Vue.prototype.$isThisLocation = function (location) {
|
||||
|
||||
// Get current location
|
||||
let currentLocation = store.getters.currentFolder && store.getters.currentFolder.location ? store.getters.currentFolder.location : undefined
|
||||
|
||||
// Check if type is object
|
||||
if (typeof location === 'Object' || location instanceof Object) {
|
||||
return includes(location, currentLocation)
|
||||
|
||||
} else {
|
||||
return currentLocation === location
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$checkPermission = function (type) {
|
||||
|
||||
let currentPermission = store.getters.permission
|
||||
|
||||
// Check if type is object
|
||||
if (typeof type === 'Object' || type instanceof Object) {
|
||||
return includes(type, currentPermission)
|
||||
|
||||
} else {
|
||||
return currentPermission === type
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$isMobile = function () {
|
||||
const toMatch = [
|
||||
/Android/i,
|
||||
/webOS/i,
|
||||
/iPhone/i,
|
||||
/iPad/i,
|
||||
/iPod/i,
|
||||
/BlackBerry/i,
|
||||
/Windows Phone/i
|
||||
]
|
||||
|
||||
return toMatch.some(toMatchItem => {
|
||||
return navigator.userAgent.match(toMatchItem)
|
||||
})
|
||||
}
|
||||
|
||||
Vue.prototype.$isMinimalScale = function () {
|
||||
let sizeType = store.getters.filesViewWidth
|
||||
|
||||
return sizeType === 'minimal-scale'
|
||||
}
|
||||
|
||||
Vue.prototype.$isCompactScale = function () {
|
||||
let sizeType = store.getters.filesViewWidth
|
||||
|
||||
return sizeType === 'compact-scale'
|
||||
}
|
||||
|
||||
Vue.prototype.$isFullScale = function () {
|
||||
let sizeType = store.getters.filesViewWidth
|
||||
|
||||
return sizeType === 'full-scale'
|
||||
}
|
||||
|
||||
Vue.prototype.$isSomethingWrong = function () {
|
||||
|
||||
} else {
|
||||
|
||||
// Show error message
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$downloadFile = function(url, filename) {
|
||||
var anchor = document.createElement('a')
|
||||
|
||||
anchor.href = url
|
||||
|
||||
anchor.download = filename
|
||||
|
||||
document.body.appendChild(anchor)
|
||||
|
||||
anchor.click()
|
||||
}
|
||||
|
||||
Vue.prototype.$closePopup = function() {
|
||||
events.$emit('popup:close')
|
||||
}
|
||||
|
||||
Vue.prototype.$isThisRoute = function(route, locations) {
|
||||
|
||||
return includes(locations, route.name)
|
||||
}
|
||||
|
||||
Vue.prototype.$isThisLocation = function(location) {
|
||||
|
||||
// Get current location
|
||||
let currentLocation = store.getters.currentFolder && store.getters.currentFolder.location ? store.getters.currentFolder.location : undefined
|
||||
|
||||
// Check if type is object
|
||||
if (typeof location === 'Object' || location instanceof Object) {
|
||||
return includes(location, currentLocation)
|
||||
|
||||
} else {
|
||||
return currentLocation === location
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$checkPermission = function(type) {
|
||||
|
||||
let currentPermission = store.getters.permission
|
||||
|
||||
// Check if type is object
|
||||
if (typeof type === 'Object' || type instanceof Object) {
|
||||
return includes(type, currentPermission)
|
||||
|
||||
} else {
|
||||
return currentPermission === type
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$isMobile = function() {
|
||||
const toMatch = [
|
||||
/Android/i,
|
||||
/webOS/i,
|
||||
/iPhone/i,
|
||||
/iPad/i,
|
||||
/iPod/i,
|
||||
/BlackBerry/i,
|
||||
/Windows Phone/i
|
||||
]
|
||||
|
||||
return toMatch.some(toMatchItem => {
|
||||
return navigator.userAgent.match(toMatchItem)
|
||||
})
|
||||
}
|
||||
|
||||
Vue.prototype.$isMinimalScale = function() {
|
||||
let sizeType = store.getters.filesViewWidth
|
||||
|
||||
return sizeType === 'minimal-scale'
|
||||
}
|
||||
|
||||
Vue.prototype.$isCompactScale = function() {
|
||||
let sizeType = store.getters.filesViewWidth
|
||||
|
||||
return sizeType === 'compact-scale'
|
||||
}
|
||||
|
||||
Vue.prototype.$isFullScale = function() {
|
||||
let sizeType = store.getters.filesViewWidth
|
||||
|
||||
return sizeType === 'full-scale'
|
||||
}
|
||||
|
||||
Vue.prototype.$isSomethingWrong = function() {
|
||||
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
}
|
||||
}
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Helpers
|
||||
|
||||
@@ -211,7 +211,7 @@
|
||||
"message": "Something went wrong and we can't continue. Please contact us."
|
||||
},
|
||||
"popup_exceed_limit": {
|
||||
"title": "Whooops, you exceed your storage limit :(",
|
||||
"title": "Whooops, you exceed storage limit :(",
|
||||
"message": "Please contact your administrator to change your limit."
|
||||
},
|
||||
"popup_share_create": {
|
||||
|
||||
Vendored
+10
@@ -15,6 +15,7 @@ import Profile from './views/User/Settings'
|
||||
import Invoice from './views/User/Invoices'
|
||||
import Password from './views/User/Password'
|
||||
import Subscription from './views/User/Subscription'
|
||||
import PaymentCards from './views/User/PaymentCards'
|
||||
|
||||
import Trash from './views/FilePages/Trash'
|
||||
import Files from './views/FilePages/Files'
|
||||
@@ -399,6 +400,15 @@ const routesUser = [
|
||||
title: 'Subscription'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'PaymentCards',
|
||||
path: '/settings/payment-cards',
|
||||
component: PaymentCards,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: 'Payment Cards'
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+15
-11
@@ -11,22 +11,26 @@ const defaultState = {
|
||||
|
||||
const actions = {
|
||||
getAppData: ({commit, getters}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.get(getters.api + '/user')
|
||||
.then((response) => {
|
||||
resolve(response)
|
||||
|
||||
axios
|
||||
.get(getters.api + '/user')
|
||||
.then((response) => {
|
||||
commit('RETRIEVE_USER', response.data)
|
||||
commit('RETRIEVE_USER', response.data)
|
||||
|
||||
}).catch((error) => {
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
|
||||
// Redirect if unauthenticated
|
||||
if ([401, 403].includes(error.response.status)) {
|
||||
// Redirect if unauthenticated
|
||||
if ([401, 403].includes(error.response.status)) {
|
||||
|
||||
commit('SET_AUTHORIZED', false)
|
||||
router.push({name: 'SignIn'})
|
||||
commit('SET_AUTHORIZED', false)
|
||||
router.push({name: 'SignIn'})
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
},
|
||||
logOut: ({getters, commit}) => {
|
||||
axios
|
||||
|
||||
@@ -27,12 +27,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
{{ row.data.attributes.payments_processed }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
{{ row.data.attributes.active_subscribers }}
|
||||
{{ row.data.attributes.payment_processed }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@@ -100,12 +95,7 @@
|
||||
},
|
||||
{
|
||||
label: 'Payments Processed',
|
||||
field: 'data.attributes.payments_processed',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: 'Active Subscribers',
|
||||
field: 'data.attributes.active_subscribers',
|
||||
field: 'data.attributes.payment_processed',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,47 +1,68 @@
|
||||
<template>
|
||||
<PageTab>
|
||||
<PageTabGroup>
|
||||
<DatatableWrapper :paginator="true" :columns="columns" :data="invoices" class="table">
|
||||
<PageTabGroup v-if="transactions.length > 0">
|
||||
<DatatableWrapper :paginator="true" :columns="columns" :data="transactions" class="table">
|
||||
<template scope="{ row }">
|
||||
<tr>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
${{ row.attributes.total }}
|
||||
</span>
|
||||
<a :href="'/invoice/' + row.data.attributes.token" target="_blank" class="cell-item">
|
||||
{{ row.data.attributes.order }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
{{ row.attributes.plan }}
|
||||
</span>
|
||||
<span class="cell-item">
|
||||
${{ row.data.attributes.total }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
{{ row.attributes.created_at_formatted }}
|
||||
</span>
|
||||
<span class="cell-item">
|
||||
{{ row.data.attributes.bag[0].description }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
{{ row.data.attributes.created_at_formatted }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<router-link :to="{name: 'UserInvoices', params: {id: row.relationships.user.data.id}}">
|
||||
<DatatableCellImage
|
||||
image-size="small"
|
||||
:image="row.relationships.user.data.attributes.avatar"
|
||||
:title="row.relationships.user.data.attributes.name"
|
||||
/>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-icons">
|
||||
<download-cloud-icon size="15" class="icon"></download-cloud-icon>
|
||||
<a :href="'/invoice/' + row.data.attributes.token" target="_blank">
|
||||
<external-link-icon size="15" class="icon"></external-link-icon>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</PageTabGroup>
|
||||
<PageTabGroup v-else>
|
||||
You don't have any transactions yet.
|
||||
</PageTabGroup>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatatableCellImage from '@/components/Others/Tables/DatatableCellImage'
|
||||
import DatatableWrapper from '@/components/Others/Tables/DatatableWrapper'
|
||||
import PageTabGroup from '@/components/Others/Layout/PageTabGroup'
|
||||
import PageTab from '@/components/Others/Layout/PageTab'
|
||||
import {DownloadCloudIcon} from "vue-feather-icons";
|
||||
import {ExternalLinkIcon} from "vue-feather-icons";
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'GatewayTransactions',
|
||||
components: {
|
||||
DownloadCloudIcon,
|
||||
DatatableCellImage,
|
||||
ExternalLinkIcon,
|
||||
DatatableWrapper,
|
||||
PageTabGroup,
|
||||
PageTab,
|
||||
@@ -49,97 +70,13 @@
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
invoices: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 9.99,
|
||||
plan: 'Starter Plan',
|
||||
created_at: '30. April. 2020',
|
||||
created_at_formatted: '30. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 9.99,
|
||||
plan: 'Starter Plan',
|
||||
created_at: '30. April. 2020',
|
||||
created_at_formatted: '30. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 49.99,
|
||||
plan: 'Business Plan',
|
||||
created_at: '31. April. 2020',
|
||||
created_at_formatted: '31. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 29.99,
|
||||
plan: 'Professional Plan',
|
||||
created_at: '31. April. 2020',
|
||||
created_at_formatted: '31. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 9.99,
|
||||
plan: 'Starter Plan',
|
||||
created_at: '30. April. 2020',
|
||||
created_at_formatted: '30. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 9.99,
|
||||
plan: 'Starter Plan',
|
||||
created_at: '30. April. 2020',
|
||||
created_at_formatted: '30. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 49.99,
|
||||
plan: 'Business Plan',
|
||||
created_at: '31. April. 2020',
|
||||
created_at_formatted: '31. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
type: 'invoices',
|
||||
attributes: {
|
||||
total: 29.99,
|
||||
plan: 'Professional Plan',
|
||||
created_at: '31. April. 2020',
|
||||
created_at_formatted: '31. April. 2020',
|
||||
download: 'https://vuefilemanager.com/',
|
||||
},
|
||||
},
|
||||
],
|
||||
transactions: [],
|
||||
columns: [
|
||||
{
|
||||
label: 'Invoice Number',
|
||||
field: 'attributes.total',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: 'Total',
|
||||
field: 'attributes.total',
|
||||
@@ -155,6 +92,11 @@
|
||||
field: 'attributes.created_at',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
field: 'relationships.user.data.id',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.action'),
|
||||
field: 'data.action',
|
||||
@@ -164,11 +106,11 @@
|
||||
}
|
||||
},
|
||||
created() {
|
||||
/*axios.get('/api/users/' + this.$route.params.id + '/storage')
|
||||
axios.get('/api/gateways/' + this.$route.params.slug + '/transactions')
|
||||
.then(response => {
|
||||
this.storage = response.data.data
|
||||
this.transactions = response.data.data
|
||||
this.isLoading = false
|
||||
})*/
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -34,15 +34,6 @@
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<!--Price-->
|
||||
<div class="block-wrapper">
|
||||
<label>Price:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Plan price" rules="required" v-slot="{ errors }">
|
||||
<input @input="$updateText('/plans/' + $route.params.id + '/update', 'price', plan.attributes.price)" v-model="plan.attributes.price" placeholder="Plan price" type="number" step="0.01" min="1" max="99999" :class="{'is-error': errors[0]}"/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<!--Storage Capacity-->
|
||||
<div class="block-wrapper">
|
||||
<label>Storage Capacity:</label>
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td v-if="config.isSaaS">
|
||||
<span class="cell-item" v-if="row.relationships.subscription">
|
||||
{{ row.relationships.subscription.data.attributes.name }}
|
||||
<span class="cell-item" v-if="row.data.attributes.subscription">
|
||||
Premium
|
||||
</span>
|
||||
<span class="cell-item" v-else>
|
||||
Free
|
||||
@@ -114,44 +114,7 @@
|
||||
return {
|
||||
isLoading: true,
|
||||
users: [],
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('admin_page_user.table.name'),
|
||||
field: 'data.attributes.name',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.role'),
|
||||
field: 'data.attributes.role',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: 'Subscription Plan',
|
||||
field: 'data.attributes.role',
|
||||
sortable: true,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.storage_used'),
|
||||
field: 'data.attributes.storage.used',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.storage_capacity'),
|
||||
field: 'data.attributes.storage.capacity',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.created_at'),
|
||||
field: 'data.attributes.created_at_formatted',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.action'),
|
||||
field: 'data.action',
|
||||
sortable: false
|
||||
},
|
||||
],
|
||||
columns: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -167,6 +130,45 @@
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.columns = [
|
||||
{
|
||||
label: this.$t('admin_page_user.table.name'),
|
||||
field: 'data.attributes.name',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.role'),
|
||||
field: 'data.attributes.role',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: 'Subscription Plan',
|
||||
field: 'data.attributes.role',
|
||||
sortable: true,
|
||||
hidden: ! this.config.isSaaS,
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.storage_used'),
|
||||
field: 'data.attributes.storage.used',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.storage_capacity'),
|
||||
field: 'data.attributes.storage.capacity',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.created_at'),
|
||||
field: 'data.attributes.created_at_formatted',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.action'),
|
||||
field: 'data.action',
|
||||
sortable: false
|
||||
},
|
||||
]
|
||||
|
||||
axios.get('/api/users')
|
||||
.then(response => {
|
||||
this.users = response.data.data
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="config.isSaaS" class="headline-actions">
|
||||
<router-link :to="{name: 'UpgradePlan'}">
|
||||
<router-link :to="{name: 'UpgradePlan'}" v-if="! user.relationships.subscription || (user.relationships.subscription && ! user.relationships.subscription.data.attributes.is_highest)">
|
||||
<ButtonBase button-style="secondary" type="button">
|
||||
Upgrade Plan
|
||||
</ButtonBase>
|
||||
@@ -56,13 +56,22 @@
|
||||
|
||||
<router-link v-if="config.isSaaS" replace :to="{name: 'Subscription'}" class="menu-list-item link">
|
||||
<div class="icon">
|
||||
<credit-card-icon size="17"></credit-card-icon>
|
||||
<cloud-icon size="17"></cloud-icon>
|
||||
</div>
|
||||
<div class="label">
|
||||
Subscription
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<router-link v-if="config.isSaaS" replace :to="{name: 'PaymentCards'}" class="menu-list-item link">
|
||||
<div class="icon">
|
||||
<credit-card-icon size="17"></credit-card-icon>
|
||||
</div>
|
||||
<div class="label">
|
||||
Payment Cards
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<router-link v-if="config.isSaaS" replace :to="{name: 'Invoice'}" class="menu-list-item link">
|
||||
<div class="icon">
|
||||
<file-text-icon size="17"></file-text-icon>
|
||||
@@ -105,6 +114,7 @@
|
||||
CreditCardIcon,
|
||||
HardDriveIcon,
|
||||
FileTextIcon,
|
||||
CloudIcon,
|
||||
UserIcon,
|
||||
LockIcon,
|
||||
} from 'vue-feather-icons'
|
||||
@@ -112,6 +122,7 @@
|
||||
export default {
|
||||
name: 'Settings',
|
||||
components: {
|
||||
CloudIcon,
|
||||
ButtonBase,
|
||||
CreditCardIcon,
|
||||
UserImageInput,
|
||||
@@ -127,10 +138,10 @@
|
||||
computed: {
|
||||
...mapGetters(['user', 'config']),
|
||||
subscriptionStatus() {
|
||||
return this.user.relationships.subscription ? 'Premium' : 'Free'
|
||||
return this.user.data.attributes.subscription ? 'Premium' : 'Free'
|
||||
},
|
||||
subscriptionColor() {
|
||||
return this.user.relationships.subscription ? 'green' : 'purple'
|
||||
return this.user.data.attributes.subscription ? 'green' : 'purple'
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -13,118 +13,186 @@
|
||||
</div>
|
||||
|
||||
<div class="order">
|
||||
<div class="billing" v-if="billing">
|
||||
<b class="form-group-label">Billing Information:</b>
|
||||
<ValidationObserver ref="order" v-slot="{ invalid }" tag="form" class="form block-form">
|
||||
<div class="form block-form">
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Name:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper" rules="required"
|
||||
name="billing_name" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_name"
|
||||
placeholder="Type your billing name"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div class="steps">
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Address:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper" rules="required"
|
||||
name="billing_address" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_address"
|
||||
placeholder="Type your billing address"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div class="payment-card">
|
||||
<b class="form-group-label">Payment Card:</b>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>State:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper" rules="required"
|
||||
name="billing_state" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_state"
|
||||
placeholder="Type your billing state"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<!-- Pay by new credit card -->
|
||||
<div class="register-card" v-show="! defaultPaymentCard || payByNewCard">
|
||||
<p class="payment-demo-disclaimer">
|
||||
For test your payment please use <b>4242 4242 4242 4242</b> as a card number, <b>11/22</b>
|
||||
as the expiration date and <b>123</b> as CVC number and ZIP <b>12345</b>.
|
||||
</p>
|
||||
|
||||
<div class="wrapper-inline">
|
||||
<div class="block-wrapper">
|
||||
<label>City:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required" name="billing_city" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_city"
|
||||
placeholder="Type your billing city"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div ref="stripeCard" class="stripe-card" :class="{'is-error': isError }"></div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Postal Code:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required" name="billing_postal_code"
|
||||
v-slot="{ errors }">
|
||||
<input v-model="billing.billing_postal_code"
|
||||
placeholder="Type your billing postal code"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Country:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper" rules="required"
|
||||
name="billing_country" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_country"
|
||||
placeholder="Type your billing country"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Phone Number:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper" rules="required"
|
||||
name="billing_phone_number" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_phone_number"
|
||||
placeholder="Type your billing phone number"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
<div class="card-error-message" v-if="isError">
|
||||
<span>{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ValidationObserver>
|
||||
|
||||
<!--User registered payment card-->
|
||||
<div class="registered-cards" v-if="defaultPaymentCard && ! payByNewCard">
|
||||
|
||||
<div class="credit-card" :class="{'is-error': isError}">
|
||||
<div class="card-number">
|
||||
<img class="credit-card-icon"
|
||||
:src="$getCreditCardBrand(defaultPaymentCard.data.attributes.brand)"
|
||||
:alt="defaultPaymentCard.data.attributes.brand">
|
||||
<div class="credit-card-numbers">
|
||||
•••• {{ defaultPaymentCard.data.attributes.last4 }}
|
||||
</div>
|
||||
<ColorLabel color="purple">Default</ColorLabel>
|
||||
</div>
|
||||
<div class="expiration-date">
|
||||
<span>{{ defaultPaymentCard.data.attributes.exp_month }} / {{ defaultPaymentCard.data.attributes.exp_year }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Change payment-->
|
||||
<div class="change-payment" v-if="! isError">
|
||||
<span>Also you can</span>
|
||||
|
||||
<router-link v-if="paymentCards.data.length > 0" :to="{name: 'PaymentCards'}">change your
|
||||
default payment method
|
||||
</router-link>
|
||||
<span v-if="paymentCards.data.length > 0">or</span>
|
||||
|
||||
<a @click="payByNewCardForm">pay by new credit card.</a>
|
||||
</div>
|
||||
|
||||
<!--Card error-->
|
||||
<div class="card-error-message" v-if="isError">
|
||||
<span>{{ errorMessage }}</span>
|
||||
<span @click="payByNewCardForm"
|
||||
class="link">Please pay by another payment card</span>
|
||||
<span> or </span>
|
||||
<router-link :to="{name: 'PaymentCards'}" class="link">Change your default payment
|
||||
method
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="billing" v-if="billing">
|
||||
<b class="form-group-label">Billing Information:</b>
|
||||
<ValidationObserver ref="order" v-slot="{ invalid }" tag="form" class="form block-form">
|
||||
<div class="form block-form">
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Name:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required"
|
||||
name="billing_name" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_name"
|
||||
placeholder="Type your billing name"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Address:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required"
|
||||
name="billing_address" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_address"
|
||||
placeholder="Type your billing address"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>State:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required"
|
||||
name="billing_state" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_state"
|
||||
placeholder="Type your billing state"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-inline">
|
||||
<div class="block-wrapper">
|
||||
<label>City:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required" name="billing_city"
|
||||
v-slot="{ errors }">
|
||||
<input v-model="billing.billing_city"
|
||||
placeholder="Type your billing city"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Postal Code:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required" name="billing_postal_code"
|
||||
v-slot="{ errors }">
|
||||
<input v-model="billing.billing_postal_code"
|
||||
placeholder="Type your billing postal code"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Country:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required"
|
||||
name="billing_country" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_country"
|
||||
placeholder="Type your billing country"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Phone Number:</label>
|
||||
<ValidationProvider tag="div" mode="passive" class="input-wrapper"
|
||||
rules="required"
|
||||
name="billing_phone_number" v-slot="{ errors }">
|
||||
<input v-model="billing.billing_phone_number"
|
||||
placeholder="Type your billing phone number"
|
||||
type="text"
|
||||
:class="{'is-error': errors[0]}"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</div>
|
||||
<div class="summary">
|
||||
<b class="form-group-label">Order Summary:</b>
|
||||
|
||||
<div class="summary-list" v-if="requestedPlan">
|
||||
<div class="summary-list" :class="{'is-error': isError}" v-if="requestedPlan">
|
||||
<div class="row">
|
||||
<div class="cell">
|
||||
<b>{{ requestedPlan.data.attributes.name }}</b>
|
||||
<small>Billed monthly</small>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<b>{{ requestedPlan.data.attributes.price }} USD</b>
|
||||
<b>{{ requestedPlan.data.attributes.price }}</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -132,15 +200,14 @@
|
||||
<b>Total</b>
|
||||
</div>
|
||||
<div class="cell">
|
||||
<b>{{ requestedPlan.data.attributes.price }} USD</b>
|
||||
<b>{{ requestedPlan.data.attributes.price }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ButtonBase :disabled="isSubmitted" :loading="isSubmitted" @click.native="submitOrder"
|
||||
type="submit" button-style="theme-solid" class="next-submit">
|
||||
Pay Order
|
||||
Pay with credit card
|
||||
</ButtonBase>
|
||||
|
||||
<p class="error-message" v-if="isError">{{ errorMessage }}</p>
|
||||
<small class="disclaimer">
|
||||
By submit form, you agree to save the payment method and billing information in your
|
||||
VueFileManager account.
|
||||
@@ -148,7 +215,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="loader" v-if="isLoading">
|
||||
@@ -162,14 +228,19 @@
|
||||
import PlanPricingTables from '@/components/Others/PlanPricingTables'
|
||||
import MobileHeader from '@/components/Mobile/MobileHeader'
|
||||
import ButtonBase from '@/components/FilesView/ButtonBase'
|
||||
import ColorLabel from '@/components/Others/ColorLabel'
|
||||
import PageHeader from '@/components/Others/PageHeader'
|
||||
import Spinner from '@/components/FilesView/Spinner'
|
||||
import {CreditCardIcon} from 'vue-feather-icons'
|
||||
import {required} from 'vee-validate/dist/rules'
|
||||
import { mapGetters } from 'vuex'
|
||||
import {mapGetters} from 'vuex'
|
||||
import {events} from "@/bus"
|
||||
import axios from 'axios'
|
||||
|
||||
let stripe = Stripe(`pk_test_51GsACaCBETHMUxzVsYkeApHtqb85paMuye7G77PDDQ28kXqDJ5HTmqLi13aM6xee81OQK1fhkTZ7vmDiWLStU9160061Yb2MtL`),
|
||||
elements = stripe.elements(),
|
||||
card = undefined;
|
||||
|
||||
export default {
|
||||
name: 'UpgradePlan',
|
||||
components: {
|
||||
@@ -180,20 +251,66 @@
|
||||
MobileHeader,
|
||||
ButtonBase,
|
||||
PageHeader,
|
||||
ColorLabel,
|
||||
required,
|
||||
Spinner,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['requestedPlan']),
|
||||
billing() {
|
||||
return this.$store.getters.user.relationships.settings.data.attributes
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
complete: false,
|
||||
stripeOptions: {
|
||||
hidePostalCode: false
|
||||
},
|
||||
isLoading: true,
|
||||
isSubmitted: false,
|
||||
billing: undefined,
|
||||
paymentCards: undefined,
|
||||
defaultPaymentCard: undefined,
|
||||
|
||||
errorMessage: undefined,
|
||||
isError: false,
|
||||
|
||||
payByNewCard: false,
|
||||
|
||||
clientSecret: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
payByNewCardForm() {
|
||||
this.payByNewCard = true
|
||||
this.isError = false
|
||||
},
|
||||
successOrder() {
|
||||
// Update user data
|
||||
//this.$store.dispatch('getAppData')
|
||||
|
||||
// End loading
|
||||
this.isSubmitted = false
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: 'Your account was successfully upgraded.',
|
||||
})
|
||||
|
||||
// Go to User page
|
||||
//this.$router.push({name: 'Subscription'})
|
||||
},
|
||||
errorOrder(error) {
|
||||
|
||||
if (error.response.status = 402) {
|
||||
this.isError = true
|
||||
this.errorMessage = error.response.data.message
|
||||
}
|
||||
|
||||
// End loading
|
||||
this.isSubmitted = false
|
||||
},
|
||||
async submitOrder() {
|
||||
|
||||
// Validate fields
|
||||
@@ -201,56 +318,89 @@
|
||||
|
||||
if (!isValid) return;
|
||||
|
||||
// Remove error
|
||||
this.isError = false
|
||||
|
||||
// Start loading
|
||||
this.isSubmitted = true
|
||||
|
||||
// Send order request
|
||||
axios
|
||||
.post('/api/subscription/upgrade', {
|
||||
billing: this.billing,
|
||||
plan: this.requestedPlan,
|
||||
// If user don't have credit card, register new
|
||||
if (!this.defaultPaymentCard || this.payByNewCard) {
|
||||
|
||||
console.log('Payment by new card');
|
||||
|
||||
const {setupIntent, error} = await stripe.confirmCardSetup(this.clientSecret, {
|
||||
payment_method: {
|
||||
card: card,
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
// Update user data
|
||||
this.$store.dispatch('getAppData')
|
||||
if (error) {
|
||||
|
||||
// End loading
|
||||
// Set error on
|
||||
this.isError = true
|
||||
|
||||
// End button spinner
|
||||
this.isSubmitted = false
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: 'Your account was successfully upgraded.',
|
||||
// Show error message
|
||||
this.errorMessage = error.message
|
||||
|
||||
} else {
|
||||
|
||||
axios
|
||||
.post('/api/subscription/upgrade', {
|
||||
billing: this.billing,
|
||||
plan: this.requestedPlan,
|
||||
payment: {
|
||||
type: 'stripe',
|
||||
meta: {
|
||||
pm: setupIntent.payment_method,
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => this.successOrder())
|
||||
.catch((error) => this.errorOrder(error))
|
||||
}
|
||||
}
|
||||
|
||||
// if user has credit card
|
||||
if (this.defaultPaymentCard && !this.payByNewCard) {
|
||||
|
||||
console.log('Payment by default card');
|
||||
|
||||
axios
|
||||
.post('/api/subscription/upgrade', {
|
||||
billing: this.billing,
|
||||
plan: this.requestedPlan,
|
||||
payment: {
|
||||
type: 'stripe',
|
||||
}
|
||||
})
|
||||
|
||||
// Go to User page
|
||||
this.$router.push({name: 'Storage'})
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
// End loading
|
||||
this.isSubmitted = false
|
||||
})
|
||||
}
|
||||
.then(() => this.successOrder())
|
||||
.catch((error) => this.errorOrder(error))
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
card = elements.create('card');
|
||||
card.mount(this.$refs.stripeCard);
|
||||
},
|
||||
created() {
|
||||
axios.get('/api/user')
|
||||
|
||||
// Get setup intent for stripe
|
||||
axios.get('/api/stripe/setup-intent')
|
||||
.then(response => this.clientSecret = response.data.client_secret)
|
||||
|
||||
axios.get('/api/user/payments')
|
||||
.then(response => {
|
||||
|
||||
if (! this.requestedPlan) {
|
||||
if (!this.requestedPlan) {
|
||||
this.$router.push({name: 'UpgradePlan'})
|
||||
}
|
||||
|
||||
this.billing = {
|
||||
billing_name: response.data.relationships.settings.data.attributes.billing_name,
|
||||
billing_address: response.data.relationships.settings.data.attributes.billing_address,
|
||||
billing_state: response.data.relationships.settings.data.attributes.billing_state,
|
||||
billing_city: response.data.relationships.settings.data.attributes.billing_city,
|
||||
billing_postal_code: response.data.relationships.settings.data.attributes.billing_postal_code,
|
||||
billing_country: response.data.relationships.settings.data.attributes.billing_country,
|
||||
billing_phone_number: response.data.relationships.settings.data.attributes.billing_phone_number,
|
||||
}
|
||||
this.defaultPaymentCard = response.data.default
|
||||
this.paymentCards = response.data.others
|
||||
|
||||
this.isLoading = false
|
||||
})
|
||||
@@ -263,6 +413,133 @@
|
||||
@import '@assets/vue-file-manager/_mixins';
|
||||
@import '@assets/vue-file-manager/_forms';
|
||||
|
||||
.change-payment {
|
||||
padding-top: 10px;
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
span, a {
|
||||
color: $text-muted;
|
||||
@include font-size(14);
|
||||
}
|
||||
}
|
||||
|
||||
.card-error-message {
|
||||
padding-top: 10px;
|
||||
|
||||
span, a {
|
||||
@include font-size(14);
|
||||
font-weight: 600;
|
||||
color: $danger;
|
||||
}
|
||||
|
||||
.link, a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.registered-cards {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.register-card {
|
||||
margin-bottom: 55px;
|
||||
}
|
||||
|
||||
.credit-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 15px;
|
||||
background: $light_background;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
|
||||
&.is-error {
|
||||
box-shadow: 0 0 7px rgba($danger, 0.3);
|
||||
border: 2px solid $danger;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card-number {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.credit-card-numbers {
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.credit-card-icon {
|
||||
vertical-align: middle;
|
||||
max-height: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-demo-disclaimer {
|
||||
padding: 15px;
|
||||
background: $light_background;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.6;
|
||||
|
||||
b {
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
|
||||
.stripe-card {
|
||||
box-sizing: border-box;
|
||||
padding: 13px 20px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
|
||||
box-shadow: 0 1px 3px 0 #e6ebf1;
|
||||
-webkit-transition: box-shadow 150ms ease;
|
||||
transition: box-shadow 150ms ease;
|
||||
|
||||
&.is-error {
|
||||
box-shadow: 0 0 7px rgba($danger, 0.3);
|
||||
border: 2px solid $danger;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.StripeElement--focus {
|
||||
box-shadow: 0 1px 3px 0 #cfd7df;
|
||||
}
|
||||
|
||||
&.StripeElement--invalid {
|
||||
border-color: #fa755a;
|
||||
}
|
||||
|
||||
&.StripeElement--webkit-autofill {
|
||||
background-color: #fefde5 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.summary-list {
|
||||
box-shadow: 0 7px 20px 5px hsla(220, 36%, 16%, 0.06);
|
||||
border-radius: 8px;
|
||||
@@ -270,6 +547,15 @@
|
||||
padding: 25px;
|
||||
top: 30px;
|
||||
|
||||
&.is-error {
|
||||
border: 2px solid $danger;
|
||||
box-shadow: 0 7px 20px 5px rgba($danger, 0.06);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.next-submit {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
@@ -317,7 +603,7 @@
|
||||
.order {
|
||||
display: flex;
|
||||
|
||||
.billing {
|
||||
.steps {
|
||||
flex: 0 0 65%;
|
||||
padding-right: 30px;
|
||||
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
this.$store.commit('STORE_REQUESTED_PLAN', plan)
|
||||
}
|
||||
},
|
||||
/*beforeMount() {
|
||||
let StripeElementsScript = document.createElement('script')
|
||||
|
||||
StripeElementsScript.setAttribute('src', 'https://js.stripe.com/v3/')
|
||||
document.head.appendChild(StripeElementsScript)
|
||||
},*/
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<PageTabGroup v-if="paymentCards && paymentCards.length > 0">
|
||||
<DatatableWrapper :paginator="true" :columns="columns" :data="paymentCards" class="table">
|
||||
<template scope="{ row }">
|
||||
<tr :class="{'is-deleting': row.data.attributes.card_id === deletingID}">
|
||||
<td style="width: 300px">
|
||||
<span class="cell-item">
|
||||
<div class="credit-card">
|
||||
<img class="credit-card-icon" :src="$getCreditCardBrand(row.data.attributes.brand)"
|
||||
:alt="row.data.attributes.brand">
|
||||
<div class="credit-card-numbers">
|
||||
•••• {{ row.data.attributes.last4 }}
|
||||
</div>
|
||||
<ColorLabel v-if="row.data.id === defaultPaymentCard.data.id" color="purple">Default</ColorLabel>
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
<ColorLabel :color="getCardStatusColor(row.data.attributes.status)">{{ getCardStatus(row.data.attributes.status) }}</ColorLabel>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
{{ row.data.attributes.exp_month }} / {{ row.data.attributes.exp_year }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-icons">
|
||||
<credit-card-icon size="15" class="icon icon-card" title="Set as default card" @click="setDefaultCard(row.data.attributes)" v-if="row.data.id !== defaultPaymentCard.data.id"></credit-card-icon>
|
||||
<trash2-icon size="15" class="icon icon-trash" title="Delete card" @click="deleteCard(row.data.attributes)"></trash2-icon>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</PageTabGroup>
|
||||
<PageTabGroup v-else>
|
||||
You don't have any payment cards yet.
|
||||
</PageTabGroup>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatatableWrapper from '@/components/Others/Tables/DatatableWrapper'
|
||||
import PageTabGroup from '@/components/Others/Layout/PageTabGroup'
|
||||
import PageTab from '@/components/Others/Layout/PageTab'
|
||||
import ColorLabel from '@/components/Others/ColorLabel'
|
||||
import {CreditCardIcon, Trash2Icon} from "vue-feather-icons"
|
||||
import {events} from "@/bus"
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'UserPaymentCards',
|
||||
components: {
|
||||
DatatableWrapper,
|
||||
PageTabGroup,
|
||||
Trash2Icon,
|
||||
ColorLabel,
|
||||
CreditCardIcon,
|
||||
PageTab,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultPaymentCard: undefined,
|
||||
paymentCards: undefined,
|
||||
deletingID: undefined,
|
||||
columns: [
|
||||
{
|
||||
label: 'Card Number',
|
||||
field: 'data.attributes.total',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: 'Status',
|
||||
field: 'data.attributes.status',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: 'Expiration Date',
|
||||
field: 'data.attributes.total',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.action'),
|
||||
field: 'data.action',
|
||||
sortable: false
|
||||
},
|
||||
],
|
||||
isLoading: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCardStatusColor(status) {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'green'
|
||||
break
|
||||
case 'card_declined':
|
||||
return 'yellow'
|
||||
break
|
||||
case 'expired':
|
||||
return 'red'
|
||||
break
|
||||
}
|
||||
},
|
||||
getCardStatus(status) {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'Active'
|
||||
break
|
||||
case 'card_declined':
|
||||
return 'Rejected'
|
||||
break
|
||||
case 'expired':
|
||||
return 'Expired'
|
||||
break
|
||||
}
|
||||
},
|
||||
setDefaultCard(card) {
|
||||
events.$emit('confirm:open', {
|
||||
title: 'Set as default card?',
|
||||
message: 'Your card will be set as default and will be always charged for the next billings.',
|
||||
buttonColor: 'theme-solid',
|
||||
action: {
|
||||
id: card.card_id,
|
||||
operation: 'set-as-default-credit-card'
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteCard(card) {
|
||||
events.$emit('confirm:open', {
|
||||
title: 'Are you sure?',
|
||||
message: 'This event is irreversible and your payment card will be delete forever',
|
||||
action: {
|
||||
id: card.card_id,
|
||||
operation: 'delete-credit-card'
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchPaymentCards() {
|
||||
axios.get('/api/user/payments')
|
||||
.then(response => {
|
||||
|
||||
this.defaultPaymentCard = response.data.default
|
||||
|
||||
this.paymentCards = response.data.others.data
|
||||
this.paymentCards.push(response.data.default)
|
||||
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
// Get payments card
|
||||
this.fetchPaymentCards()
|
||||
|
||||
// Delete credit card
|
||||
events.$on('action:confirmed', data => {
|
||||
|
||||
if (data.operation === 'delete-credit-card') {
|
||||
|
||||
this.deletingID = data.id
|
||||
|
||||
axios.delete('/api/user/payment-cards/' + data.id)
|
||||
.then(() => {
|
||||
|
||||
// Get payments card
|
||||
this.fetchPaymentCards()
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: 'Your card was successfully deleted.',
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
})
|
||||
}
|
||||
|
||||
if (data.operation === 'set-as-default-credit-card') {
|
||||
|
||||
axios.patch('/api/user/payment-cards/' + data.id, {
|
||||
default: 1
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
// Get payments card
|
||||
this.fetchPaymentCards()
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: 'Your card was successfully set as default.',
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
events.$off('action:confirmed')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@assets/vue-file-manager/_variables';
|
||||
@import '@assets/vue-file-manager/_mixins';
|
||||
@import '@assets/vue-file-manager/_forms';
|
||||
|
||||
.is-deleting {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.credit-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.credit-card-numbers {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.credit-card-icon {
|
||||
max-height: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,34 +1,44 @@
|
||||
<template>
|
||||
<PageTab>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<PageTabGroup v-if="subscription">
|
||||
|
||||
<!--Info about active subscription-->
|
||||
<div v-if="! subscription.canceled" class="state active">
|
||||
<div v-if="! subscription.data.attributes.canceled" class="state active">
|
||||
<ListInfo class="list-info">
|
||||
<ListInfoItem class="list-item" title="Plan" :content="subscription.name + ' - ' + subscription.capacity_formatted"/>
|
||||
<ListInfoItem class="list-item" title="Plan" :content="subscription.data.attributes.name + ' - ' + subscription.data.attributes.capacity_formatted"/>
|
||||
<ListInfoItem class="list-item" title="Billed" content="Monthly"/>
|
||||
<ListInfoItem class="list-item" title="Status" :content="status"/>
|
||||
<ListInfoItem class="list-item" title="Created At" :content="subscription.created_at"/>
|
||||
<ListInfoItem class="list-item" title="Renews At" :content="subscription.ends_at"/>
|
||||
<ListInfoItem class="list-item" title="Created At" :content="subscription.data.attributes.created_at"/>
|
||||
<ListInfoItem class="list-item" title="Renews At" :content="subscription.data.attributes.ends_at"/>
|
||||
</ListInfo>
|
||||
<div class="cancel-plan">
|
||||
<div class="plan-action">
|
||||
<ButtonBase
|
||||
:disabled="isDeleting"
|
||||
@click.native="cancelSubscription"
|
||||
:button-style="cancelButtonStyle"
|
||||
class="cancel-button">
|
||||
class="confirm-button">
|
||||
{{ cancelButtonText }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Info about canceled subscription-->
|
||||
<div v-if="subscription.canceled" class="state canceled">
|
||||
<div v-if="subscription.data.attributes.canceled" class="state canceled">
|
||||
<ListInfo class="list-info">
|
||||
<ListInfoItem class="list-item" title="Plan" :content="subscription.name"/>
|
||||
<ListInfoItem class="list-item" title="Plan" :content="subscription.data.attributes.name"/>
|
||||
<ListInfoItem class="list-item" title="Status" :content="status"/>
|
||||
<ListInfoItem class="list-item" title="Canceled At" :content="subscription.canceled_at"/>
|
||||
<ListInfoItem class="list-item" title="Ends At" :content="subscription.ends_at"/>
|
||||
<ListInfoItem class="list-item" title="Canceled At" :content="subscription.data.attributes.canceled_at"/>
|
||||
<ListInfoItem class="list-item" title="Ends At" :content="subscription.data.attributes.ends_at"/>
|
||||
</ListInfo>
|
||||
<div class="plan-action">
|
||||
<ButtonBase
|
||||
:disabled="isResuming"
|
||||
@click.native="resumeSubscription"
|
||||
:button-style="resumeButtonStyle"
|
||||
class="confirm-button">
|
||||
{{ resumeButtonText }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</div>
|
||||
</PageTabGroup>
|
||||
<PageTabGroup v-else>
|
||||
@@ -65,26 +75,31 @@
|
||||
return this.isConfirmedCancel ? this.$t('popup_share_edit.confirm') : 'Cancel Plan'
|
||||
},
|
||||
cancelButtonStyle() {
|
||||
return this.isConfirmedCancel ? 'danger-solid' : 'danger'
|
||||
return this.isConfirmedCancel ? 'danger-solid' : 'secondary'
|
||||
},
|
||||
subscription() {
|
||||
return this.$store.getters.user.relationships.subscription
|
||||
? this.$store.getters.user.relationships.subscription.data.attributes
|
||||
: undefined
|
||||
resumeButtonText() {
|
||||
return this.isConfirmedResume ? this.$t('popup_share_edit.confirm') : 'Resume Plan'
|
||||
},
|
||||
resumeButtonStyle() {
|
||||
return this.isConfirmedResume ? 'theme-solid' : 'secondary'
|
||||
},
|
||||
status() {
|
||||
if (this.subscription.canceled) {
|
||||
if (this.subscription.data.attributes.canceled) {
|
||||
return 'Canceled'
|
||||
}
|
||||
if (this.subscription.active) {
|
||||
if (this.subscription.data.attributes.active) {
|
||||
return 'Active'
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
subscription: undefined,
|
||||
isConfirmedCancel: false,
|
||||
isConfirmedResume: false,
|
||||
isDeleting: false,
|
||||
isResuming: false,
|
||||
isLoading: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -98,6 +113,7 @@
|
||||
|
||||
// Start deleting spinner button
|
||||
this.isDeleting = true
|
||||
this.isLoading = true
|
||||
|
||||
// Send delete request
|
||||
axios
|
||||
@@ -105,7 +121,9 @@
|
||||
.then(() => {
|
||||
|
||||
// Update user data
|
||||
this.$store.dispatch('getAppData')
|
||||
this.$store.dispatch('getAppData').then(() => {
|
||||
this.fetchSubscriptionDetail()
|
||||
})
|
||||
|
||||
// End deleting spinner button
|
||||
this.isDeleting = false
|
||||
@@ -122,9 +140,61 @@
|
||||
|
||||
// End deleting spinner button
|
||||
this.isDeleting = false
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
resumeSubscription() {
|
||||
|
||||
// Set confirm button
|
||||
if (! this.isConfirmedResume) {
|
||||
|
||||
this.isConfirmedResume = true
|
||||
} else {
|
||||
|
||||
// Start deleting spinner button
|
||||
this.isResuming = true
|
||||
this.isLoading = true
|
||||
|
||||
// Send delete request
|
||||
axios
|
||||
.post('/api/subscription/resume')
|
||||
.then(() => {
|
||||
|
||||
// Update user data
|
||||
this.$store.dispatch('getAppData').then(() => {
|
||||
this.fetchSubscriptionDetail()
|
||||
})
|
||||
|
||||
// End deleting spinner button
|
||||
this.isResuming = false
|
||||
|
||||
events.$emit('alert:open', {
|
||||
emoji: '👍',
|
||||
title: 'Subscription Was Resumed',
|
||||
message: 'Your subscription was re-activated, and they will be billed on the original billing cycle.',
|
||||
buttonStyle: 'theme',
|
||||
button: 'That\'s awesome!'
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
// End deleting spinner button
|
||||
this.isResuming = false
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
fetchSubscriptionDetail() {
|
||||
axios.get('/api/user/subscription')
|
||||
.then(response => {
|
||||
this.subscription = response.data
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchSubscriptionDetail()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -133,7 +203,7 @@
|
||||
@import '@assets/vue-file-manager/_variables';
|
||||
@import '@assets/vue-file-manager/_mixins';
|
||||
|
||||
.cancel-plan {
|
||||
.plan-action {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user