mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-26 02:20:39 +00:00
deleted frontend code
This commit is contained in:
@@ -6,11 +6,6 @@
|
||||
<AppSpecification v-if="config.isAdminVueFileManagerBar" :data="data" class="hidden lg:flex" />
|
||||
<AppSpecification v-if="config.isAdminVueFileManagerBar" :data="data" class="card shadow-card lg:hidden" />
|
||||
|
||||
<!--Create metered plan alert-->
|
||||
<AlertBox v-if="config.subscriptionType === 'metered' && config.isEmptyPlans" color="rose">
|
||||
As you installed app with metered subscription type, you have to <router-link :to="{ name: 'CreateMeteredPlan' }" class="dark:text-rose-500 text-sm font-bold underline">create your plan</router-link> as soon as possible to prevent new user registration without automatically assigned subscription plan.
|
||||
</AlertBox>
|
||||
|
||||
<!--Cron Alert-->
|
||||
<AlertBox v-if="!data.app.isRunningCron && !config.isDev" color="rose">
|
||||
We detect your cron jobs probably doesn't work correctly, please check it, you need it for running app correctly. If you set your cron job, please get back one minute later.
|
||||
@@ -50,22 +45,6 @@
|
||||
<chevron-right-icon size="16" class="text-theme vue-feather" />
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-if="config.subscriptionType !== 'none'" class="card mb-4 w-full shadow-card md:mb-0">
|
||||
<FormLabel icon="dollar">
|
||||
{{ $t('earnings') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
|
||||
{{ data.app.earnings }}
|
||||
</b>
|
||||
|
||||
<router-link :to="{ name: 'Invoices' }" class="mt-6 flex items-center">
|
||||
<span class="mr-2 whitespace-nowrap text-xs font-bold">
|
||||
{{ $t('show_all_transactions') }}
|
||||
</span>
|
||||
<chevron-right-icon size="16" class="text-theme vue-feather" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Upload bandwidth widgets-->
|
||||
@@ -110,18 +89,6 @@
|
||||
|
||||
<WidgetLatestRegistrations />
|
||||
</div>
|
||||
|
||||
<!--Latest transactions widgets-->
|
||||
<div
|
||||
v-if="['fixed', 'metered'].includes(this.config.subscriptionType)"
|
||||
class="card mb-4 shadow-card md:mb-6"
|
||||
>
|
||||
<FormLabel icon="dollar">
|
||||
{{ $t('latest_transactions') }}
|
||||
</FormLabel>
|
||||
|
||||
<WidgetLatestTransactions />
|
||||
</div>
|
||||
</div>
|
||||
<div id="loader" v-if="isLoading">
|
||||
<Spinner />
|
||||
@@ -137,7 +104,6 @@ import FormLabel from '../../components/UI/Labels/FormLabel'
|
||||
import BarChart from '../../components/UI/BarChart/BarChart'
|
||||
import {mapGetters} from 'vuex'
|
||||
import axios from 'axios'
|
||||
import WidgetLatestTransactions from '../../components/Dashboard/Widgets/WidgetLatestTransactions'
|
||||
import AlertBox from "../../components/UI/Others/AlertBox";
|
||||
import AppSpecification from "../../components/Dashboard/AppSpecification";
|
||||
|
||||
@@ -146,7 +112,6 @@ export default {
|
||||
components: {
|
||||
AppSpecification,
|
||||
AlertBox,
|
||||
WidgetLatestTransactions,
|
||||
WidgetLatestRegistrations,
|
||||
ChevronRightIcon,
|
||||
FormLabel,
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--Datatable-->
|
||||
<DatatableWrapper
|
||||
v-if="!config.isEmptyTransactions"
|
||||
class="card overflow-x-auto shadow-card"
|
||||
api="/api/admin/transactions"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<!--Transaction rows-->
|
||||
<MeteredTransactionRow
|
||||
v-if="config.subscriptionType === 'metered'"
|
||||
:row="row"
|
||||
:user="true"
|
||||
@showDetail="showTransactionDetail"
|
||||
/>
|
||||
|
||||
<FixedTransactionRow v-if="config.subscriptionType === 'fixed'" :row="row" :user="true" />
|
||||
|
||||
<!--Transaction detail-->
|
||||
<MeteredTransactionDetailRow
|
||||
v-if="row.data.attributes.metadata && showedTransactionDetailById === row.data.id"
|
||||
:row="row"
|
||||
/>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
|
||||
<!--Empty State-->
|
||||
<div v-if="config.isEmptyTransactions" class="flex items-center justify-center fixed top-0 bottom-0 left-0 right-0">
|
||||
<div class="text-center">
|
||||
<img
|
||||
class="mb-6 inline-block w-28"
|
||||
src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f9ee.svg"
|
||||
alt="transaction"
|
||||
/>
|
||||
<h1 class="mb-1 text-2xl font-bold">
|
||||
{{ $t('there_is_nothing') }}
|
||||
</h1>
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ $t('transaction_will_be_here') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FixedTransactionRow from '../../components/Subscription/FixedTransactionRow'
|
||||
import MeteredTransactionDetailRow from '../../components/Subscription/MeteredTransactionDetailRow'
|
||||
import MeteredTransactionRow from '../../components/Subscription/MeteredTransactionRow'
|
||||
import MemberAvatar from '../../components/UI/Others/MemberAvatar'
|
||||
import DatatableWrapper from '../../components/UI/Table/DatatableWrapper'
|
||||
import ColorLabel from '../../components/UI/Labels/ColorLabel'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Invoices',
|
||||
components: {
|
||||
MeteredTransactionDetailRow,
|
||||
MeteredTransactionRow,
|
||||
FixedTransactionRow,
|
||||
DatatableWrapper,
|
||||
MemberAvatar,
|
||||
ColorLabel,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
columns() {
|
||||
if (config.subscriptionType === 'fixed') {
|
||||
return this.$store.getters.transactionColumns.filter((column) => !['type'].includes(column.field))
|
||||
}
|
||||
|
||||
return this.$store.getters.transactionColumns
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showedTransactionDetailById: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showTransactionDetail(id) {
|
||||
if (this.showedTransactionDetailById === id) this.showedTransactionDetailById = undefined
|
||||
else this.showedTransactionDetailById = id
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,122 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card shadow-card">
|
||||
<DatatableWrapper
|
||||
@init="isLoading = false"
|
||||
api="/api/admin/pages"
|
||||
:paginator="false"
|
||||
:columns="columns"
|
||||
class="overflow-x-auto"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<tr class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5">
|
||||
<td class="py-5 pr-3 md:pr-1">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'PageEdit',
|
||||
params: { slug: row.data.attributes.slug },
|
||||
}"
|
||||
class="cursor-pointer text-sm font-bold"
|
||||
tag="div"
|
||||
>
|
||||
{{ row.data.attributes.title }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.slug }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
<SwitchInput
|
||||
@input="
|
||||
$updateText(
|
||||
`/admin/pages/${row.data.id}`,
|
||||
'visibility',
|
||||
row.data.attributes.visibility
|
||||
)
|
||||
"
|
||||
v-model="row.data.attributes.visibility"
|
||||
:state="row.data.attributes.visibility"
|
||||
class="switch"
|
||||
/>
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<div class="flex w-full justify-end space-x-2">
|
||||
<router-link
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground"
|
||||
:to="{
|
||||
name: 'PageEdit',
|
||||
params: {
|
||||
slug: row.data.attributes.slug,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<Edit2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatatableWrapper from '../../components/UI/Table/DatatableWrapper'
|
||||
import MobileActionButton from '../../components/UI/Buttons/MobileActionButton'
|
||||
import EmptyPageContent from '../../components/Others/EmptyPageContent'
|
||||
import SwitchInput from '../../components/Inputs/SwitchInput'
|
||||
import MobileHeader from '../../components/Mobile/MobileHeader'
|
||||
import SectionTitle from '../../components/UI/Labels/SectionTitle'
|
||||
import ButtonBase from '../../components/UI/Buttons/ButtonBase'
|
||||
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
|
||||
import ColorLabel from '../../components/UI/Labels/ColorLabel'
|
||||
import Spinner from '../../components/UI/Others/Spinner'
|
||||
|
||||
export default {
|
||||
name: 'Pages',
|
||||
components: {
|
||||
MobileActionButton,
|
||||
EmptyPageContent,
|
||||
DatatableWrapper,
|
||||
SectionTitle,
|
||||
MobileHeader,
|
||||
SwitchInput,
|
||||
Trash2Icon,
|
||||
ButtonBase,
|
||||
ColorLabel,
|
||||
Edit2Icon,
|
||||
Spinner,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('page'),
|
||||
field: 'title',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('slug'),
|
||||
field: 'slug',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('status'),
|
||||
field: 'visibility',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!isLoading && page" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ page.data.attributes.title }}
|
||||
</FormLabel>
|
||||
<AppInputSwitch
|
||||
:title="$t('visibility')"
|
||||
:description="$t('admin_pages.form.visibility_help')"
|
||||
>
|
||||
<SwitchInput @input="changeStatus" class="switch" :state="page.data.attributes.visibility" />
|
||||
</AppInputSwitch>
|
||||
<AppInputText :title="$t('title')">
|
||||
<input
|
||||
@input="$updateText('/admin/pages/' + $route.params.slug, 'title', page.data.attributes.title)"
|
||||
v-model="page.data.attributes.title"
|
||||
:placeholder="$t('admin_pages.form.title_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
<AppInputText :title="$t('slug')">
|
||||
<input v-model="page.data.attributes.slug" type="text" class="focus-border-theme input-dark" disabled />
|
||||
</AppInputText>
|
||||
<AppInputText :title="$t('content')" :is-last="true">
|
||||
<textarea
|
||||
@input="$updateText('/admin/pages/' + $route.params.slug, 'content', page.data.attributes.content)"
|
||||
v-model="page.data.attributes.content"
|
||||
:placeholder="$t('admin_pages.form.content_plac')"
|
||||
class="focus-border-theme input-dark"
|
||||
rows="18"
|
||||
></textarea>
|
||||
</AppInputText>
|
||||
</div>
|
||||
<div id="loader" v-if="isLoading">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppInputSwitch from '../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import AppInputText from '../../../components/Forms/Layouts/AppInputText'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import FormLabel from '../../../components/UI/Labels/FormLabel'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import SwitchInput from '../../../components/Inputs/SwitchInput'
|
||||
import MobileHeader from '../../../components/Mobile/MobileHeader'
|
||||
import SectionTitle from '../../../components/UI/Labels/SectionTitle'
|
||||
import ButtonBase from '../../../components/UI/Buttons/ButtonBase'
|
||||
import Spinner from '../../../components/UI/Others/Spinner'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'PageEdit',
|
||||
components: {
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
FormLabel,
|
||||
SectionTitle,
|
||||
MobileHeader,
|
||||
SwitchInput,
|
||||
ButtonBase,
|
||||
required,
|
||||
Spinner,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
page: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeStatus(val) {
|
||||
this.$updateText('/admin/pages/' + this.$route.params.slug, 'visibility', val)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios.get('/api/admin/pages/' + this.$route.params.slug).then((response) => {
|
||||
this.page = response.data
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,39 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--Page Tab links-->
|
||||
<div class="card z-10 shadow-card" style="padding-bottom: 0; padding-top: 0">
|
||||
<CardNavigation :pages="pages" class="-mx-1" />
|
||||
</div>
|
||||
|
||||
<!--Page Content-->
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CardNavigation from '../../../components/UI/Others/CardNavigation'
|
||||
|
||||
export default {
|
||||
name: 'PaymentSettings',
|
||||
components: {
|
||||
CardNavigation,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
title: this.$t('payments'),
|
||||
route: 'AppPayments',
|
||||
},
|
||||
{
|
||||
title: this.$t('billings'),
|
||||
route: 'AppBillings',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$router.replace({ name: 'AppPayments' })
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,167 +0,0 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<div v-if="billingInformation" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('admin_settings.billings.section_company') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputText :title="$t('admin_settings.billings.company_name')">
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'billing_name', billingInformation.billing_name)"
|
||||
v-model="billingInformation.billing_name"
|
||||
:placeholder="$t('admin_settings.billings.company_name_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<AppInputText :title="$t('admin_settings.billings.vat')" :is-last="true">
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'billing_vat_number', billingInformation.billing_vat_number)"
|
||||
v-model="billingInformation.billing_vat_number"
|
||||
:placeholder="$t('admin_settings.billings.vat_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
<div v-if="billingInformation" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('admin_settings.billings.section_billing') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputText :title="$t('admin_settings.billings.country')">
|
||||
<SelectInput
|
||||
@input="$updateText('/admin/settings', 'billing_country', billingInformation.billing_country)"
|
||||
v-model="billingInformation.billing_country"
|
||||
:default="billingInformation.billing_country"
|
||||
:options="countries"
|
||||
:placeholder="$t('admin_settings.billings.country_plac')"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<AppInputText :title="$t('billing_address')">
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'billing_address', billingInformation.billing_address)"
|
||||
v-model="billingInformation.billing_address"
|
||||
:placeholder="$t('admin_settings.billings.address_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<div class="flex space-x-4">
|
||||
<AppInputText :title="$t('billing_city')" class="w-full">
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'billing_city', billingInformation.billing_city)"
|
||||
v-model="billingInformation.billing_city"
|
||||
:placeholder="$t('admin_settings.billings.city_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<AppInputText :title="$t('admin_settings.billings.postal_code')" class="w-full">
|
||||
<input
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'billing_postal_code',
|
||||
billingInformation.billing_postal_code
|
||||
)
|
||||
"
|
||||
v-model="billingInformation.billing_postal_code"
|
||||
:placeholder="$t('admin_settings.billings.postal_code_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
|
||||
<AppInputText :title="$t('admin_settings.billings.state')">
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'billing_state', billingInformation.billing_state)"
|
||||
v-model="billingInformation.billing_state"
|
||||
:placeholder="$t('admin_settings.billings.state_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<AppInputText :title="$t('admin_settings.billings.phone_number')" :is-last="true">
|
||||
<input
|
||||
@input="
|
||||
$updateText('/admin/settings', 'billing_phone_number', billingInformation.billing_phone_number)
|
||||
"
|
||||
v-model="billingInformation.billing_phone_number"
|
||||
:placeholder="$t('admin_settings.billings.phone_number_plac')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import PageTabGroup from '../../../../components/Layout/PageTabGroup'
|
||||
import SelectInput from '../../../../components/Inputs/SelectInput'
|
||||
import ImageInput from '../../../../components/Inputs/ImageInput'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import axios from 'axios'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AppAppearance',
|
||||
components: {
|
||||
ValidationObserver,
|
||||
ValidationProvider,
|
||||
AppInputText,
|
||||
PageTabGroup,
|
||||
SelectInput,
|
||||
ImageInput,
|
||||
ButtonBase,
|
||||
FormLabel,
|
||||
required,
|
||||
PageTab,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['countries']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
billingInformation: undefined,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
axios
|
||||
.get('/api/admin/settings', {
|
||||
params: {
|
||||
column: 'billing_phone_number|billing_postal_code|billing_vat_number|billing_address|billing_country|billing_state|billing_city|billing_name',
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
this.isLoading = false
|
||||
|
||||
this.billingInformation = {
|
||||
billing_phone_number: response.data.billing_phone_number,
|
||||
billing_postal_code: response.data.billing_postal_code,
|
||||
billing_vat_number: response.data.billing_vat_number,
|
||||
billing_address: response.data.billing_address,
|
||||
billing_country: response.data.billing_country,
|
||||
billing_state: response.data.billing_state,
|
||||
billing_city: response.data.billing_city,
|
||||
billing_name: response.data.billing_name,
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,664 +0,0 @@
|
||||
<template>
|
||||
<PageTab>
|
||||
<!--Global payment settings-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="dollar">
|
||||
{{ $t('subscription_payments') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('allow_subscription_payments')"
|
||||
:description="$t('allow_subscription_payments_description')"
|
||||
:is-last="true"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_payments', allowedPayments)"
|
||||
v-model="allowedPayments"
|
||||
:state="allowedPayments"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<!--Metered settings-->
|
||||
<div v-if="config.subscriptionType === 'metered' && allowedPayments" class="card shadow-card">
|
||||
<FormLabel icon="bar-chart">
|
||||
{{ $t('metered_billing_settings') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('allow_registration_bonus')"
|
||||
:description="$t('allow_registration_bonus_description')"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_registration_bonus', allowedRegistrationBonus)"
|
||||
v-model="allowedRegistrationBonus"
|
||||
:state="allowedRegistrationBonus"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputText
|
||||
v-if="allowedRegistrationBonus"
|
||||
:title="$t('registration_bonus_amount')"
|
||||
:description="$t('registration_bonus_amount_description')"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'registration_bonus_amount', registrationBonusAmount)"
|
||||
v-model="registrationBonusAmount"
|
||||
:placeholder="$t('registration_bonus_amount_')"
|
||||
type="number"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<AppInputButton
|
||||
:title="$t('metered_plan')"
|
||||
:description="$t('metered_plan_description')"
|
||||
:is-last="true"
|
||||
>
|
||||
<router-link
|
||||
v-if="config.isCreatedMeteredPlan"
|
||||
:to="{
|
||||
name: 'PlanMeteredSettings',
|
||||
params: { id: config.meteredPlanId },
|
||||
}"
|
||||
>
|
||||
<ButtonBase v-if="config.isCreatedMeteredPlan" class="w-full sm:w-auto" button-style="theme">
|
||||
{{ $t('plan_details') }}
|
||||
</ButtonBase>
|
||||
</router-link>
|
||||
|
||||
<router-link v-if="!config.isCreatedMeteredPlan" :to="{ name: 'CreateMeteredPlan' }">
|
||||
<ButtonBase class="w-full sm:w-auto" button-style="theme-solid">
|
||||
{{ $t('create_plan') }}
|
||||
</ButtonBase>
|
||||
</router-link>
|
||||
</AppInputButton>
|
||||
</div>
|
||||
|
||||
<!--Stripe method configuration-->
|
||||
<div v-if="allowedPayments" class="card shadow-card">
|
||||
<img :src="$getPaymentLogo('stripe')" alt="Stripe" class="mb-8 h-8" />
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('Allow Stripe Service')"
|
||||
:description="$t('allow_pay_by_card')"
|
||||
:is-last="!stripe.allowedService"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_stripe', stripe.allowedService)"
|
||||
v-model="stripe.allowedService"
|
||||
:state="stripe.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<!--Stripe credentials are set up-->
|
||||
<div v-if="stripe.allowedService">
|
||||
<AppInputText
|
||||
:title="$t('webhook_url')"
|
||||
:description="$t('copy_webhook_note')"
|
||||
>
|
||||
<CopyInput size="small" :str="getWebhookEndpoint('stripe')" />
|
||||
</AppInputText>
|
||||
|
||||
<div v-if="stripe.isConfigured">
|
||||
<AppInputText
|
||||
@input="$updateText('/admin/settings', 'stripe_payment_description', stripe.paymentDescription)"
|
||||
:title="$t('payment_description')"
|
||||
:description="$t('payment_description_note')"
|
||||
>
|
||||
<textarea
|
||||
rows="2"
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'stripe_payment_description',
|
||||
stripe.paymentDescription,
|
||||
true
|
||||
)
|
||||
"
|
||||
v-model="stripe.paymentDescription"
|
||||
:placeholder="
|
||||
$t('additional_info_about_payment_method_')
|
||||
"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<div
|
||||
@click="stripe.isVisibleCredentialsForm = !stripe.isVisibleCredentialsForm"
|
||||
class="flex cursor-pointer items-center"
|
||||
:class="{ 'mb-4': stripe.isVisibleCredentialsForm }"
|
||||
>
|
||||
<edit2-icon size="14" class="vue-feather text-theme mr-2.5" />
|
||||
<b class="text-sm">{{ $t('update_your_credentials') }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Set up Stripe credentials-->
|
||||
<ValidationObserver
|
||||
v-if="!stripe.isConfigured || stripe.isVisibleCredentialsForm"
|
||||
@submit.prevent="storeCredentials('stripe')"
|
||||
ref="stripe"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="rounded-xl p-5 shadow-lg"
|
||||
>
|
||||
<FormLabel v-if="!stripe.isConfigured" icon="shield">
|
||||
{{ $t('configure_your_credentials') }}
|
||||
</FormLabel>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Publishable Key"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :title="$t('admin_settings.payments.stripe_pub_key')" :error="errors[0]">
|
||||
<input
|
||||
v-model="stripe.credentials.key"
|
||||
:placeholder="$t('admin_settings.payments.stripe_pub_key_plac')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
<ValidationProvider tag="div" mode="passive" name="Secret Key" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('admin_settings.payments.stripe_sec_key')" :error="errors[0]">
|
||||
<input
|
||||
v-model="stripe.credentials.secret"
|
||||
:placeholder="$t('admin_settings.payments.stripe_sec_key_plac')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Webhook Secret"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :title="$t('Webhook Secret')" :error="errors[0]">
|
||||
<input
|
||||
v-model="stripe.credentials.webhook"
|
||||
:placeholder="$t('paste_webhook_secret')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ButtonBase
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
button-style="theme"
|
||||
type="submit"
|
||||
class="w-full"
|
||||
>
|
||||
{{ $t('store_credentials') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Paystack method configuration-->
|
||||
<div v-if="allowedPayments" class="card shadow-card">
|
||||
<img :src="$getPaymentLogo('paystack')" alt="Paystack" class="mb-8 h-7" />
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('Allow Paystack Service')"
|
||||
:description="$t('allow_pay_by_card')"
|
||||
:is-last="!paystack.allowedService"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_paystack', paystack.allowedService)"
|
||||
v-model="paystack.allowedService"
|
||||
:state="paystack.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<!--Paystack credentials are set up-->
|
||||
<div v-if="paystack.allowedService">
|
||||
<AppInputText
|
||||
:title="$t('webhook_url')"
|
||||
:description="$t('copy_webhook_note')"
|
||||
>
|
||||
<CopyInput size="small" :str="getWebhookEndpoint('paystack')" />
|
||||
</AppInputText>
|
||||
|
||||
<div v-if="paystack.isConfigured">
|
||||
<AppInputText
|
||||
@input="
|
||||
$updateText('/admin/settings', 'paystack_payment_description', paystack.paymentDescription)
|
||||
"
|
||||
:title="$t('payment_description')"
|
||||
:description="$t('payment_description_note')"
|
||||
>
|
||||
<textarea
|
||||
rows="2"
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'paystack_payment_description',
|
||||
paystack.paymentDescription,
|
||||
true
|
||||
)
|
||||
"
|
||||
v-model="paystack.paymentDescription"
|
||||
:placeholder="
|
||||
$t('additional_info_about_payment_method_')
|
||||
"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<div
|
||||
@click="paystack.isVisibleCredentialsForm = !paystack.isVisibleCredentialsForm"
|
||||
class="flex cursor-pointer items-center"
|
||||
:class="{ 'mb-4': paystack.isVisibleCredentialsForm }"
|
||||
>
|
||||
<edit2-icon size="14" class="vue-feather text-theme mr-2.5" />
|
||||
<b class="text-sm">{{ $t('update_your_credentials') }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Set up Paystack credentials-->
|
||||
<ValidationObserver
|
||||
v-if="!paystack.isConfigured || paystack.isVisibleCredentialsForm"
|
||||
@submit.prevent="storeCredentials('paystack')"
|
||||
ref="paystack"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="rounded-xl p-5 shadow-lg"
|
||||
>
|
||||
<FormLabel v-if="!paystack.isConfigured" icon="shield">
|
||||
{{ $t('configure_your_credentials') }}
|
||||
</FormLabel>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Publishable Key"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :title="$t('admin_settings.payments.stripe_pub_key')" :error="errors[0]">
|
||||
<input
|
||||
v-model="paystack.credentials.key"
|
||||
:placeholder="$t('admin_settings.payments.stripe_pub_key_plac')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
<ValidationProvider tag="div" mode="passive" name="Secret Key" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('admin_settings.payments.stripe_sec_key')" :error="errors[0]">
|
||||
<input
|
||||
v-model="paystack.credentials.secret"
|
||||
:placeholder="$t('admin_settings.payments.stripe_sec_key_plac')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ButtonBase
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
button-style="theme"
|
||||
type="submit"
|
||||
class="w-full"
|
||||
>
|
||||
{{ $t('store_credentials') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--PayPal method configuration-->
|
||||
<div v-if="allowedPayments" class="card shadow-card">
|
||||
<img :src="$getPaymentLogo('paypal')" alt="PayPal" class="mb-8 h-8" />
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('Allow PayPal Service')"
|
||||
:description="$t('allow_pay_by_card')"
|
||||
:is-last="!paypal.allowedService"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_paypal', paypal.allowedService)"
|
||||
v-model="paypal.allowedService"
|
||||
:state="paypal.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<!--Stripe credentials are set up-->
|
||||
<div v-if="paypal.allowedService">
|
||||
<AppInputText
|
||||
:title="$t('webhook_url')"
|
||||
:description="$t('copy_webhook_note')"
|
||||
>
|
||||
<CopyInput size="small" :str="getWebhookEndpoint('paypal')" />
|
||||
</AppInputText>
|
||||
|
||||
<div v-if="paypal.isConfigured">
|
||||
<AppInputSwitch :title="$t('Live Mode')" :description="$t('Toggle between live and sandbox mode')">
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'paypal_live', config.isPayPalLive)"
|
||||
v-model="config.isPayPalLive"
|
||||
:state="config.isPayPalLive"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputText
|
||||
@input="$updateText('/admin/settings', 'paypal_payment_description', paypal.paymentDescription)"
|
||||
:title="$t('payment_description')"
|
||||
:description="$t('payment_description_note')"
|
||||
>
|
||||
<textarea
|
||||
rows="2"
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'paypal_payment_description',
|
||||
paypal.paymentDescription,
|
||||
true
|
||||
)
|
||||
"
|
||||
v-model="paypal.paymentDescription"
|
||||
:placeholder="
|
||||
$t('additional_info_about_payment_method_')
|
||||
"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<div
|
||||
@click="paypal.isVisibleCredentialsForm = !paypal.isVisibleCredentialsForm"
|
||||
class="flex cursor-pointer items-center"
|
||||
:class="{ 'mb-4': paypal.isVisibleCredentialsForm }"
|
||||
>
|
||||
<edit2-icon size="14" class="vue-feather text-theme mr-2.5" />
|
||||
<b class="text-sm">{{ $t('update_your_credentials') }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Set up Paypal credentials-->
|
||||
<ValidationObserver
|
||||
v-if="!paypal.isConfigured || paypal.isVisibleCredentialsForm"
|
||||
@submit.prevent="storeCredentials('paypal')"
|
||||
ref="paypal"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="rounded-xl p-5 shadow-lg"
|
||||
>
|
||||
<FormLabel v-if="!paypal.isConfigured" icon="shield">
|
||||
{{ $t('configure_your_credentials') }}
|
||||
</FormLabel>
|
||||
|
||||
<ValidationProvider>
|
||||
<AppInputSwitch v-if="! paypal.isConfigured" :title="$t('Live Mode')" :description="$t('Toggle between live and sandbox mode')">
|
||||
<SwitchInput v-model="paypal.credentials.live" :state="paypal.credentials.live" />
|
||||
</AppInputSwitch>
|
||||
</ValidationProvider>
|
||||
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Publishable Key"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText :title="$t('admin_settings.payments.stripe_pub_key')" :error="errors[0]">
|
||||
<input
|
||||
v-model="paypal.credentials.key"
|
||||
:placeholder="$t('admin_settings.payments.stripe_pub_key_plac')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
<ValidationProvider tag="div" mode="passive" name="Secret Key" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('admin_settings.payments.stripe_sec_key')" :error="errors[0]">
|
||||
<input
|
||||
v-model="paypal.credentials.secret"
|
||||
:placeholder="$t('admin_settings.payments.stripe_sec_key_plac')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
<ValidationProvider tag="div" mode="passive" name="Webhook ID" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Webhook ID')" :error="errors[0]">
|
||||
<input
|
||||
v-model="paypal.credentials.webhook"
|
||||
:placeholder="$t('Paste your webhook id')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ButtonBase
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
button-style="theme"
|
||||
type="submit"
|
||||
class="w-full"
|
||||
>
|
||||
{{ $t('store_credentials') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Edit2Icon, Trash2Icon } from 'vue-feather-icons'
|
||||
import AppInputButton from '../../../../components/Forms/Layouts/AppInputButton'
|
||||
import DatatableWrapper from '../../../../components/UI/Table/DatatableWrapper'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import PageTabGroup from '../../../../components/Layout/PageTabGroup'
|
||||
import SelectInput from '../../../../components/Inputs/SelectInput'
|
||||
import SwitchInput from '../../../../components/Inputs/SwitchInput'
|
||||
import ImageInput from '../../../../components/Inputs/ImageInput'
|
||||
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import CopyInput from '../../../../components/Inputs/CopyInput'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import { events } from '../../../../bus'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'AppPayments',
|
||||
components: {
|
||||
AppInputButton,
|
||||
DatatableWrapper,
|
||||
ValidationObserver,
|
||||
ValidationProvider,
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
PageTabGroup,
|
||||
SwitchInput,
|
||||
SelectInput,
|
||||
ImageInput,
|
||||
ButtonBase,
|
||||
CopyInput,
|
||||
FormLabel,
|
||||
Trash2Icon,
|
||||
Edit2Icon,
|
||||
required,
|
||||
PageTab,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allowedRegistrationBonus: true,
|
||||
registrationBonusAmount: undefined,
|
||||
|
||||
allowedPayments: false,
|
||||
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
errorMessage: '',
|
||||
stripe: {
|
||||
allowedService: true,
|
||||
isConfigured: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
paymentDescription: undefined,
|
||||
credentials: {
|
||||
key: undefined,
|
||||
secret: undefined,
|
||||
webhook: undefined,
|
||||
},
|
||||
},
|
||||
paystack: {
|
||||
allowedService: true,
|
||||
isConfigured: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
paymentDescription: undefined,
|
||||
credentials: {
|
||||
key: undefined,
|
||||
secret: undefined,
|
||||
},
|
||||
},
|
||||
paypal: {
|
||||
allowedService: true,
|
||||
isConfigured: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
paymentDescription: undefined,
|
||||
credentials: {
|
||||
key: undefined,
|
||||
secret: undefined,
|
||||
webhook: undefined,
|
||||
live: undefined,
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('name'),
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('currency'),
|
||||
field: 'currency',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('interval'),
|
||||
field: 'interval',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('subscribers'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async storeCredentials(service) {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs[service].validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
// Start loading
|
||||
this.isLoading = true
|
||||
|
||||
// Send request to get verify account
|
||||
axios
|
||||
.post('/api/admin/settings/payment-service', {
|
||||
service: service,
|
||||
key: this[service].credentials.key,
|
||||
secret: this[service].credentials.secret,
|
||||
webhook: this[service].credentials.webhook || undefined,
|
||||
live: this[service].credentials.live,
|
||||
})
|
||||
.then(() => {
|
||||
// Update Credentials
|
||||
let commitKey = {
|
||||
stripe: 'SET_STRIPE_CREDENTIALS',
|
||||
paystack: 'SET_PAYSTACK_CREDENTIALS',
|
||||
paypal: 'SET_PAYPAL_CREDENTIALS',
|
||||
}[service]
|
||||
|
||||
// Commit credentials
|
||||
this.$store.commit(commitKey, this[service].credentials)
|
||||
|
||||
this[service].allowedService = true
|
||||
this[service].isConfigured = true
|
||||
this[service].isVisibleCredentialsForm = false
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.credentials_set', {
|
||||
service: service,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 500) {
|
||||
this.isError = true
|
||||
this.errorMessage = error.response.data.message
|
||||
}
|
||||
})
|
||||
.finally(() => (this.isLoading = false))
|
||||
},
|
||||
getWebhookEndpoint(service) {
|
||||
return `${this.config.host}/api/subscriptions/${service}/webhooks`
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// Set payment description
|
||||
this.stripe.paymentDescription = this.config.stripe_payment_description
|
||||
this.paystack.paymentDescription = this.config.paystack_payment_description
|
||||
this.paypal.paymentDescription = this.config.paypal_payment_description
|
||||
|
||||
// Set if service is allowed
|
||||
this.stripe.allowedService = this.config.isStripe
|
||||
this.paystack.allowedService = this.config.isPaystack
|
||||
this.paypal.allowedService = this.config.isPayPal
|
||||
|
||||
if (this.config.stripe_public_key) this.stripe.isConfigured = true
|
||||
|
||||
if (this.config.paystack_public_key) this.paystack.isConfigured = true
|
||||
|
||||
if (this.config.paypal_client_id) this.paypal.isConfigured = true
|
||||
|
||||
this.allowedPayments = this.config.allowed_payments
|
||||
|
||||
// Set metered
|
||||
this.allowedRegistrationBonus = this.config.allowed_registration_bonus
|
||||
this.registrationBonusAmount = this.config.registration_bonus_amount
|
||||
},
|
||||
destroyed() {
|
||||
events.$off('action:confirmed')
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,300 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--Plans-->
|
||||
<div v-if="!config.isEmptyPlans" class="card shadow-card">
|
||||
<!--Create button-->
|
||||
<div v-if="!config.isCreatedMeteredPlan || config.subscriptionType === 'fixed'" class="mb-6">
|
||||
<router-link :to="{ name: createPlanRoute }">
|
||||
<MobileActionButton icon="plus">
|
||||
{{ $t('create_plan') }}
|
||||
</MobileActionButton>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!--Datatable-->
|
||||
<DatatableWrapper
|
||||
@data="plans = $event"
|
||||
@init="isLoading = false"
|
||||
api="/api/subscriptions/admin/plans"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
class="overflow-x-auto"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<!--Metered subscription-->
|
||||
<tr
|
||||
v-if="config.subscriptionType === 'metered'"
|
||||
class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5"
|
||||
>
|
||||
<td class="py-5 pr-3 md:pr-1">
|
||||
<router-link
|
||||
class="text-sm font-bold"
|
||||
:to="{
|
||||
name: 'PlanMeteredSettings',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
{{ row.data.attributes.name }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<ColorLabel :color="$getPlanStatusColor(row.data.attributes.status)">
|
||||
{{ $t(row.data.attributes.status) }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.currency }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold capitalize">
|
||||
{{ $t(row.data.attributes.interval) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.subscribers }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<div class="flex w-full justify-end space-x-2">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'PlanMeteredSettings',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground"
|
||||
>
|
||||
<Edit2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
<router-link
|
||||
v-if="row.data.attributes.status !== 'archived'"
|
||||
:to="{
|
||||
name: 'PlanMeteredDelete',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-red-100 dark:bg-2x-dark-foreground"
|
||||
>
|
||||
<Trash2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!--Fixed subscription-->
|
||||
<tr
|
||||
v-if="config.subscriptionType === 'fixed'"
|
||||
class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5"
|
||||
>
|
||||
<td class="py-5 pr-3 md:pr-1">
|
||||
<span v-if="row.data.attributes.status === 'archived'" class="ml-6 text-gray-300">-</span>
|
||||
<SwitchInput
|
||||
v-if="row.data.attributes.status === 'active'"
|
||||
@input="
|
||||
$updateInput(
|
||||
`/subscriptions/admin/plans/${row.data.id}`,
|
||||
'visible',
|
||||
row.data.attributes.visible
|
||||
)
|
||||
"
|
||||
v-model="row.data.attributes.visible"
|
||||
:state="row.data.attributes.visible"
|
||||
class="switch"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<router-link
|
||||
class="text-sm font-bold"
|
||||
:to="{
|
||||
name: 'PlanFixedSettings',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
{{ row.data.attributes.name }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<ColorLabel :color="$getPlanStatusColor(row.data.attributes.status)">
|
||||
{{ $t(row.data.attributes.status) }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.price }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold capitalize">
|
||||
{{ $t(row.data.attributes.interval) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.subscribers }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.features.max_storage_amount }}
|
||||
GB
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<div class="flex w-full justify-end space-x-2">
|
||||
<router-link
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground"
|
||||
:to="{
|
||||
name: 'PlanFixedSettings',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<Edit2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
<router-link
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-red-100 dark:bg-2x-dark-foreground"
|
||||
:to="{
|
||||
name: 'PlanFixedDelete',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<Trash2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
|
||||
<!--Empty State-->
|
||||
<div v-if="config.isEmptyPlans" class="flex items-center justify-center fixed top-0 bottom-0 left-0 right-0">
|
||||
<div class="text-center">
|
||||
<img
|
||||
class="mb-6 inline-block w-28"
|
||||
src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f9fe.svg"
|
||||
alt="transaction"
|
||||
/>
|
||||
|
||||
<h1 class="mb-1 text-2xl font-bold">
|
||||
{{ $t('there_is_nothing') }}
|
||||
</h1>
|
||||
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ $t('all_visible_plans_here') }}
|
||||
</p>
|
||||
|
||||
<router-link :to="{ name: createPlanRoute }" class="mt-6 inline-block">
|
||||
<ButtonBase class="action-confirm" button-style="theme">
|
||||
{{ $t('create_first_plan') }}
|
||||
</ButtonBase>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatatableWrapper from '../../components/UI/Table/DatatableWrapper'
|
||||
import MobileActionButton from '../../components/UI/Buttons/MobileActionButton'
|
||||
import SwitchInput from '../../components/Inputs/SwitchInput'
|
||||
import ButtonBase from '../../components/UI/Buttons/ButtonBase'
|
||||
import ColorLabel from '../../components/UI/Labels/ColorLabel'
|
||||
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Plans',
|
||||
components: {
|
||||
MobileActionButton,
|
||||
DatatableWrapper,
|
||||
SwitchInput,
|
||||
ColorLabel,
|
||||
Trash2Icon,
|
||||
ButtonBase,
|
||||
Edit2Icon,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
createPlanRoute() {
|
||||
return {
|
||||
metered: 'CreateMeteredPlan',
|
||||
fixed: 'CreateFixedPlan',
|
||||
}[this.config.subscriptionType]
|
||||
},
|
||||
columns() {
|
||||
return {
|
||||
metered: [
|
||||
{
|
||||
label: this.$t('name'),
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('status'),
|
||||
field: 'status',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('currency'),
|
||||
field: 'currency',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('interval'),
|
||||
field: 'interval',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('subscribers'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
fixed: [
|
||||
{
|
||||
label: this.$t('visibility'),
|
||||
field: 'visible',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('name'),
|
||||
field: 'name',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('status'),
|
||||
field: 'status',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('price'),
|
||||
field: 'amount',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('interval'),
|
||||
field: 'interval',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('subscribers'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('storage'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
}[this.config.subscriptionType]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,267 +0,0 @@
|
||||
<template>
|
||||
<ValidationObserver @submit.prevent="createPlan" ref="createPlan" v-slot="{ invalid }" tag="form">
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('details') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Name-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Name" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('name')">
|
||||
<input
|
||||
v-model="plan.name"
|
||||
:placeholder="$t('plan_name')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Description-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Description" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('description_optional')" :is-last="true">
|
||||
<textarea
|
||||
v-model="plan.description"
|
||||
:placeholder="$t('plan_description')"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
maxlength="120"
|
||||
></textarea>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('pricing') }}
|
||||
</FormLabel>
|
||||
|
||||
<div class="justify-items md:flex md:space-x-4">
|
||||
<!--Price-->
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Price"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
class="w-full"
|
||||
>
|
||||
<AppInputText :title="$t('plan_price')" class="w-full">
|
||||
<input
|
||||
v-model="plan.amount"
|
||||
:placeholder="$t('plan_price')"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="1"
|
||||
max="999999999999"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Currency-->
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Currency"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
class="w-full"
|
||||
>
|
||||
<AppInputText :title="$t('currency')" class="w-full">
|
||||
<SelectInput
|
||||
v-model="plan.currency"
|
||||
:options="currencyList"
|
||||
:placeholder="$t('select_plan_currency')"
|
||||
:isError="errors[0]"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<!--Interval-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Interval" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('interval')" :is-last="true">
|
||||
<SelectInput
|
||||
v-model="plan.interval"
|
||||
:options="intervalList"
|
||||
:placeholder="$t('select_billing_interval')"
|
||||
:isError="errors[0]"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('features') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Storage Capacity-->
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Max Storage Capacity"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText
|
||||
:title="$t('admin_page_plans.form.storage')"
|
||||
:description="$t('admin_page_plans.form.storage_helper')"
|
||||
>
|
||||
<input
|
||||
v-model="plan.features.max_storage_amount"
|
||||
:placeholder="$t('admin_page_plans.form.storage_plac')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Team Members-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Max Team Members" rules="required" v-slot="{ errors }">
|
||||
<AppInputText
|
||||
:title="$t('team_members')"
|
||||
:description="$t('zero_for_unlimited_members')"
|
||||
:is-last="true"
|
||||
>
|
||||
<input
|
||||
v-model="plan.features.max_team_members"
|
||||
:placeholder="$t('add_max_team_members')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<InfoBox v-if="isError" type="error" style="margin-top: 40px">
|
||||
<p>{{ errorMessage }}</p>
|
||||
</InfoBox>
|
||||
|
||||
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit" class="w-full sm:w-auto">
|
||||
{{ $t('create_plan') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import SelectInput from '../../../../components/Inputs/SelectInput'
|
||||
import ImageInput from '../../../../components/Inputs/ImageInput'
|
||||
import MobileHeader from '../../../../components/Mobile/MobileHeader'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import SectionTitle from '../../../../components/UI/Labels/SectionTitle'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { events } from '../../../../bus'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'CreateFixedPlan',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
SectionTitle,
|
||||
AppInputText,
|
||||
MobileHeader,
|
||||
SelectInput,
|
||||
ButtonBase,
|
||||
ImageInput,
|
||||
FormLabel,
|
||||
required,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currencyList', 'intervalList', 'config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
plan: {
|
||||
type: 'fixed',
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
interval: undefined,
|
||||
amount: undefined,
|
||||
currency: undefined,
|
||||
features: {
|
||||
max_storage_amount: undefined,
|
||||
max_team_members: undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createPlan() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.createPlan.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
// Start loading
|
||||
this.isLoading = true
|
||||
|
||||
axios
|
||||
.post('/api/subscriptions/admin/plans', this.plan)
|
||||
.then((response) => {
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.plan_created'),
|
||||
})
|
||||
|
||||
// Go to plan page
|
||||
this.$router.push({
|
||||
name: 'PlanFixedSettings',
|
||||
params: { id: response.data.data.id },
|
||||
})
|
||||
|
||||
// Set default state {isEmptyPlans} to false
|
||||
if (this.config.isEmptyPlans) {
|
||||
this.$store.commit('REPLACE_CONFIG_VALUE', {
|
||||
key: 'isEmptyPlans',
|
||||
value: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// Validation errors
|
||||
if (error.response.status === 422) {
|
||||
if (error.response.data.errors['max_storage_amount']) {
|
||||
this.$refs.createPlan.setErrors({
|
||||
'Max Storage Capacity': this.$t('errors.capacity_digit'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (error.response.status === 500 && error.response.data.type) {
|
||||
events.$emit('alert:open', {
|
||||
title: error.response.data.title,
|
||||
message: error.response.data.message,
|
||||
})
|
||||
} else if (error.response.status === 500) {
|
||||
this.isError = true
|
||||
this.errorMessage = error.response.data.message
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,351 +0,0 @@
|
||||
<template>
|
||||
<ValidationObserver @submit.prevent="createPlan" ref="createPlan" v-slot="{ invalid }" tag="form">
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('details') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Name-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Name" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('name')">
|
||||
<input
|
||||
v-model="plan.name"
|
||||
:placeholder="$t('plan_name')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Description-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Description" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('description_optional')">
|
||||
<textarea
|
||||
v-model="plan.description"
|
||||
:placeholder="$t('plan_description')"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Currency-->
|
||||
<ValidationProvider tag="div" mode="passive" name="Currency" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('currency')" class="w-full" :is-last="true">
|
||||
<SelectInput
|
||||
v-model="plan.currency"
|
||||
:options="currencyList"
|
||||
:placeholder="$t('select_plan_currency')"
|
||||
:isError="errors[0]"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('charged_features') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Bandwidth-->
|
||||
<div>
|
||||
<AppInputSwitch
|
||||
:title="$t('bandwidth_per_gb')"
|
||||
:description="$t('bandwidth_per_gb_note')"
|
||||
>
|
||||
<SwitchInput
|
||||
v-model="plan.features.bandwidth.active"
|
||||
class="switch"
|
||||
:state="plan.features.bandwidth.active"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<ValidationProvider
|
||||
v-if="plan.features.bandwidth.active"
|
||||
class="-mt-3"
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Bandwidth Price"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText class="w-full">
|
||||
<input
|
||||
v-model="plan.features.bandwidth.per_unit"
|
||||
:placeholder="$t('type_bandwidth_price')"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
max="999999999999"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<!--Storage-->
|
||||
<div>
|
||||
<AppInputSwitch
|
||||
:title="$t('storage_per_gb')"
|
||||
:description="$t('storage_per_gb_note')"
|
||||
>
|
||||
<SwitchInput
|
||||
v-model="plan.features.storage.active"
|
||||
class="switch"
|
||||
:state="plan.features.storage.active"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<ValidationProvider
|
||||
v-if="plan.features.storage.active"
|
||||
class="-mt-3"
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Storage Price"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText class="w-full">
|
||||
<input
|
||||
v-model="plan.features.storage.per_unit"
|
||||
:placeholder="$t('type_storage_price')"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
max="999999999999"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Member-->
|
||||
<div>
|
||||
<AppInputSwitch
|
||||
:title="$t('member_per_unit')"
|
||||
:description="$t('member_per_unit_note')"
|
||||
>
|
||||
<SwitchInput
|
||||
v-model="plan.features.member.active"
|
||||
class="switch"
|
||||
:state="plan.features.member.active"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<ValidationProvider
|
||||
v-if="plan.features.member.active"
|
||||
class="-mt-3"
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Member Price"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText class="w-full">
|
||||
<input
|
||||
v-model="plan.features.member.per_unit"
|
||||
:placeholder="$t('type_member_price')"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
max="999999999999"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<!--Flat Fee-->
|
||||
<div>
|
||||
<AppInputSwitch
|
||||
:title="$t('flat_fee_unit_gb')"
|
||||
:description="$t('flat_fee_unit_gb_note')"
|
||||
:is-last="!plan.features.flatFee.active"
|
||||
>
|
||||
<SwitchInput
|
||||
v-model="plan.features.flatFee.active"
|
||||
class="switch"
|
||||
:state="plan.features.flatFee.active"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<ValidationProvider
|
||||
v-if="plan.features.flatFee.active"
|
||||
class="-mt-3"
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="FlatFee Price"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<AppInputText class="w-full" :is-last="true">
|
||||
<input
|
||||
v-model="plan.features.flatFee.per_unit"
|
||||
:placeholder="$t('type_flat_fee_price')"
|
||||
type="number"
|
||||
step="0.01"
|
||||
min="0.01"
|
||||
max="999999999999"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit" class="w-full sm:w-auto">
|
||||
{{ $t('create_plan') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SwitchInput from '../../../../components/Inputs/SwitchInput'
|
||||
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
|
||||
import SelectInput from '../../../../components/Inputs/SelectInput'
|
||||
import ImageInput from '../../../../components/Inputs/ImageInput'
|
||||
import MobileHeader from '../../../../components/Mobile/MobileHeader'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import SectionTitle from '../../../../components/UI/Labels/SectionTitle'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import {required} from 'vee-validate/dist/rules'
|
||||
import {mapGetters} from 'vuex'
|
||||
import {events} from '../../../../bus'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'CreateMeteredPlan',
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputSwitch,
|
||||
SwitchInput,
|
||||
SectionTitle,
|
||||
AppInputText,
|
||||
MobileHeader,
|
||||
SelectInput,
|
||||
ButtonBase,
|
||||
ImageInput,
|
||||
FormLabel,
|
||||
required,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currencyList', 'intervalList', 'config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: undefined,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
plan: {
|
||||
type: 'fixed',
|
||||
name: undefined,
|
||||
description: undefined,
|
||||
currency: undefined,
|
||||
features: {
|
||||
bandwidth: {
|
||||
active: false,
|
||||
per_unit: undefined,
|
||||
first_unit: 1,
|
||||
aggregate_strategy: 'sum_of_usage',
|
||||
},
|
||||
storage: {
|
||||
active: false,
|
||||
per_unit: undefined,
|
||||
first_unit: 1,
|
||||
aggregate_strategy: 'maximum_usage',
|
||||
},
|
||||
member: {
|
||||
active: false,
|
||||
per_unit: undefined,
|
||||
first_unit: 1,
|
||||
aggregate_strategy: 'maximum_usage',
|
||||
},
|
||||
flatFee: {
|
||||
active: false,
|
||||
per_unit: undefined,
|
||||
aggregate_strategy: 'maximum_usage',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createPlan() {
|
||||
let tiers = []
|
||||
|
||||
Object.entries(this.plan.features).forEach(([key, feature]) => {
|
||||
if (feature.active) {
|
||||
tiers.push({
|
||||
aggregate_strategy: feature.aggregate_strategy,
|
||||
key: key,
|
||||
tiers: [
|
||||
{
|
||||
per_unit: feature.per_unit,
|
||||
first_unit: 1,
|
||||
flat_fee: null,
|
||||
last_unit: null,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.createPlan.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
// Start loading
|
||||
this.isLoading = true
|
||||
|
||||
axios
|
||||
.post('/api/subscriptions/admin/plans', {
|
||||
type: 'metered',
|
||||
name: this.plan.name,
|
||||
description: this.plan.description,
|
||||
currency: this.plan.currency,
|
||||
meters: tiers,
|
||||
})
|
||||
.then((response) => {
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.plan_created'),
|
||||
})
|
||||
|
||||
// Go to plan page
|
||||
this.$router.push({
|
||||
name: 'PlanMeteredSettings',
|
||||
params: {id: response.data.data.id},
|
||||
})
|
||||
|
||||
// Set default state {isEmptyPlans} to false
|
||||
if (this.config.isEmptyPlans) {
|
||||
this.$store.commit('REPLACE_CONFIG_VALUE', {
|
||||
key: 'isEmptyPlans',
|
||||
value: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
events.$emit('toaster', {
|
||||
type: 'danger',
|
||||
message: this.$t('popup_error.title'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="plan" class="card sticky top-0 z-10 shadow-card" style="padding-bottom: 0">
|
||||
<div class="mb-2">
|
||||
<h1 class="text-lg font-bold sm:text-xl">
|
||||
{{ plan.attributes.name }}
|
||||
</h1>
|
||||
<small class="text-xs font-bold dark:text-gray-500 text-gray-500 sm:text-sm">
|
||||
{{ plan.attributes.price }} /
|
||||
{{ $t(`interval.${plan.attributes.interval}`) }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<CardNavigation :pages="pages" class="-mx-1.5" />
|
||||
</div>
|
||||
|
||||
<router-view v-if="!isLoading" :plan="plan" />
|
||||
|
||||
<div id="loader" v-if="isLoading">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CardNavigation from '../../../components/UI/Others/CardNavigation'
|
||||
import Spinner from '../../../components/UI/Others/Spinner'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'FixedPlan',
|
||||
components: {
|
||||
CardNavigation,
|
||||
Spinner,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
plan: undefined,
|
||||
pages: [
|
||||
{
|
||||
title: this.$t('settings'),
|
||||
route: 'PlanFixedSettings',
|
||||
},
|
||||
{
|
||||
title: this.$t('subscribers'),
|
||||
route: 'PlanFixedSubscribers',
|
||||
},
|
||||
{
|
||||
title: this.$t('delete_plan'),
|
||||
route: 'PlanFixedDelete',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios
|
||||
.get('/api/subscriptions/admin/plans/' + this.$route.params.id)
|
||||
.then((response) => {
|
||||
this.plan = response.data.data
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="plan" class="card sticky top-0 z-10 shadow-card" style="padding-bottom: 0">
|
||||
<div class="mb-2">
|
||||
<h1 class="text-lg font-bold sm:text-xl">
|
||||
{{ plan.attributes.name }}
|
||||
</h1>
|
||||
<small class="text-xs font-bold dark:text-gray-500 text-gray-500 sm:text-sm">
|
||||
{{ $t('x_days_intervals') }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!--Navigation-->
|
||||
<CardNavigation :pages="pages" class="-mx-1" />
|
||||
</div>
|
||||
|
||||
<router-view v-if="!isLoading" :plan="plan" />
|
||||
|
||||
<div id="loader" v-if="isLoading">
|
||||
<Spinner />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CardNavigation from '../../../components/UI/Others/CardNavigation'
|
||||
import Spinner from '../../../components/UI/Others/Spinner'
|
||||
import axios from 'axios'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'MeteredPlan',
|
||||
components: {
|
||||
CardNavigation,
|
||||
Spinner,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
pages() {
|
||||
let pages = [
|
||||
{
|
||||
title: this.$t('settings'),
|
||||
route: 'PlanMeteredSettings',
|
||||
},
|
||||
{
|
||||
title: this.$t('subscribers'),
|
||||
route: 'PlanMeteredSubscribers',
|
||||
},
|
||||
]
|
||||
|
||||
if (this.plan && this.plan.attributes.status === 'active') {
|
||||
pages.push({
|
||||
title: this.$t('delete_plan'),
|
||||
route: 'PlanMeteredDelete',
|
||||
})
|
||||
}
|
||||
|
||||
return pages
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
plan: undefined,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios
|
||||
.get('/api/subscriptions/admin/plans/' + this.$route.params.id)
|
||||
.then((response) => {
|
||||
this.plan = response.data.data
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,118 +0,0 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('delete_plan') }}
|
||||
</FormLabel>
|
||||
<ValidationObserver ref="deletePlan" @submit.prevent="deletePlan" v-slot="{ invalid }" tag="form">
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
v-slot="{ errors }"
|
||||
mode="passive"
|
||||
name="Plan name"
|
||||
:rules="'required|is:' + plan.attributes.name"
|
||||
>
|
||||
<AppInputText
|
||||
:title="
|
||||
$t('admin_page_user.label_delete_user', {
|
||||
user: plan.attributes.name,
|
||||
})
|
||||
"
|
||||
:description="$t('admin_page_plans.disclaimer_delete_plan')"
|
||||
:error="errors[0]"
|
||||
:is-last="true"
|
||||
>
|
||||
<div class="space-y-4 sm:flex sm:space-x-4 sm:space-y-0">
|
||||
<input
|
||||
v-model="planName"
|
||||
:placeholder="$t('type_plan_name')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<ButtonBase
|
||||
:loading="isSendingRequest"
|
||||
:disabled="isSendingRequest"
|
||||
type="submit"
|
||||
button-style="danger"
|
||||
class="w-full sm:w-auto"
|
||||
>
|
||||
{{ $t('delete_plan') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import { required, is } from 'vee-validate/dist/rules'
|
||||
import { events } from '../../../../bus'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'PlanDelete',
|
||||
props: ['plan'],
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputText,
|
||||
ButtonBase,
|
||||
FormLabel,
|
||||
required,
|
||||
InfoBox,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSendingRequest: false,
|
||||
isLoading: false,
|
||||
planName: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async deletePlan() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.deletePlan.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
this.isSendingRequest = true
|
||||
|
||||
axios
|
||||
.post(`/api/subscriptions/admin/plans/${this.$route.params.id}`, {
|
||||
_method: 'delete',
|
||||
})
|
||||
.then(() => {
|
||||
// If metered subscription, then set isEmptyPlans to true
|
||||
if (this.$store.getters.config.subscriptionType === 'metered') {
|
||||
this.$store.commit('REPLACE_CONFIG_VALUE', {
|
||||
key: 'isEmptyPlans',
|
||||
value: true,
|
||||
})
|
||||
}
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('popup_deleted_plan.title'),
|
||||
})
|
||||
|
||||
this.$router.push({ name: 'Plans' })
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('toaster', {
|
||||
type: 'danger',
|
||||
message: this.$t('popup_error.title'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSendingRequest = false
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,143 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('details') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Visible-->
|
||||
<AppInputSwitch
|
||||
:title="$t('status')"
|
||||
:description="$t('admin_page_plans.form.status_help')"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="
|
||||
$updateInput(
|
||||
'/subscriptions/admin/plans/' + $route.params.id,
|
||||
'visible',
|
||||
plan.attributes.visible
|
||||
)
|
||||
"
|
||||
v-model="plan.attributes.visible"
|
||||
class="switch"
|
||||
:state="plan.attributes.visible"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<!--Name-->
|
||||
<AppInputText :title="$t('name')">
|
||||
<input
|
||||
@input="
|
||||
$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'name', plan.attributes.name)
|
||||
"
|
||||
v-model="plan.attributes.name"
|
||||
:placeholder="$t('plan_name')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<!--Description-->
|
||||
<AppInputText :title="$t('description_optional')">
|
||||
<textarea
|
||||
@input="
|
||||
$updateInput(
|
||||
'/subscriptions/admin/plans/' + $route.params.id,
|
||||
'description',
|
||||
plan.attributes.description
|
||||
)
|
||||
"
|
||||
v-model="plan.attributes.description"
|
||||
:placeholder="$t('plan_description')"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
</AppInputText>
|
||||
|
||||
<InfoBox style="margin-bottom: 0">
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
'price_change_not_possible_create_new'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</InfoBox>
|
||||
</div>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('features') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Storage Capacity-->
|
||||
<AppInputText
|
||||
:title="$t('admin_page_plans.form.storage')"
|
||||
:description="$t('admin_page_plans.form.storage_helper')"
|
||||
>
|
||||
<input
|
||||
@input="
|
||||
$updateInput(
|
||||
`/subscriptions/admin/plans/${$route.params.id}/features`,
|
||||
'max_storage_amount',
|
||||
plan.attributes.features.max_storage_amount
|
||||
)
|
||||
"
|
||||
v-model="plan.attributes.features.max_storage_amount"
|
||||
:placeholder="$t('admin_page_plans.form.storage_plac')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<!--Team Members-->
|
||||
<AppInputText :title="$t('max_team_members')" :description="$t('zero_for_unlimited_members')" is-last="true">
|
||||
<input
|
||||
@input="
|
||||
$updateInput(
|
||||
`/subscriptions/admin/plans/${$route.params.id}/features`,
|
||||
'max_team_members',
|
||||
plan.attributes.features.max_team_members
|
||||
)
|
||||
"
|
||||
v-model="plan.attributes.features.max_team_members"
|
||||
:placeholder="$t('add_max_team_members')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SwitchInput from '../../../../components/Inputs/SwitchInput'
|
||||
import SelectInput from '../../../../components/Inputs/SelectInput'
|
||||
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
|
||||
export default {
|
||||
name: 'PlanFixedSettings',
|
||||
props: ['plan'],
|
||||
components: {
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
SwitchInput,
|
||||
SelectInput,
|
||||
FormLabel,
|
||||
InfoBox,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: undefined,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.visible = this.plan.attributes.visible
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,160 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('details') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Name-->
|
||||
<AppInputText :title="$t('name')">
|
||||
<input
|
||||
@input="
|
||||
$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'name', plan.attributes.name)
|
||||
"
|
||||
v-model="plan.attributes.name"
|
||||
:placeholder="$t('plan_name')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<!--Description-->
|
||||
<AppInputText :title="$t('description_optional')" :is-last="true">
|
||||
<textarea
|
||||
@input="
|
||||
$updateInput(
|
||||
'/subscriptions/admin/plans/' + $route.params.id,
|
||||
'description',
|
||||
plan.attributes.description
|
||||
)
|
||||
"
|
||||
v-model="plan.attributes.description"
|
||||
:placeholder="$t('plan_description')"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
</AppInputText>
|
||||
</div>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('charged_features') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Bandwidth-->
|
||||
<AppInputText
|
||||
v-if="plan.attributes.features.bandwidth"
|
||||
:title="$t('bandwidth_per_gb')"
|
||||
:description="$t('bandwidth_per_gb_note')"
|
||||
class="w-full"
|
||||
>
|
||||
<input
|
||||
:value="
|
||||
formatCurrency(plan.attributes.currency, plan.attributes.features.bandwidth.tiers[0].per_unit)
|
||||
"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
disabled
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<!--Storage-->
|
||||
<AppInputText
|
||||
v-if="plan.attributes.features.storage"
|
||||
:title="$t('storage_per_gb')"
|
||||
:description="$t('storage_per_gb_note')"
|
||||
class="w-full"
|
||||
>
|
||||
<input
|
||||
:value="
|
||||
formatCurrency(plan.attributes.currency, plan.attributes.features.storage.tiers[0].per_unit)
|
||||
"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
disabled
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<!--Member-->
|
||||
<AppInputText
|
||||
v-if="plan.attributes.features.member"
|
||||
:title="$t('member_per_unit')"
|
||||
:description="$t('member_per_unit_note')"
|
||||
class="w-full"
|
||||
>
|
||||
<input
|
||||
:value="formatCurrency(plan.attributes.currency, plan.attributes.features.member.tiers[0].per_unit)"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
disabled
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<!--Flat Fee-->
|
||||
<AppInputText
|
||||
v-if="plan.attributes.features.flatFee"
|
||||
:title="$t('flat_fee_unit_gb')"
|
||||
:description="$t('flat_fee_unit_gb_note')"
|
||||
class="w-full"
|
||||
>
|
||||
<input
|
||||
:value="
|
||||
formatCurrency(plan.attributes.currency, plan.attributes.features.flatFee.tiers[0].per_unit)
|
||||
"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
disabled
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<InfoBox style="margin-bottom: 0">
|
||||
<p>
|
||||
{{
|
||||
$t(
|
||||
'price_change_not_possible_create_new'
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</InfoBox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SwitchInput from '../../../../components/Inputs/SwitchInput'
|
||||
import SelectInput from '../../../../components/Inputs/SelectInput'
|
||||
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
|
||||
export default {
|
||||
name: 'PlanMeteredSettings',
|
||||
props: ['plan'],
|
||||
components: {
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
SwitchInput,
|
||||
SelectInput,
|
||||
FormLabel,
|
||||
InfoBox,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatCurrency(currency, amount) {
|
||||
// TODO: add user locale
|
||||
let formatter = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
})
|
||||
|
||||
return formatter.format(amount)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.visible = this.plan.attributes.visible
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,215 +0,0 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<DatatableWrapper
|
||||
@data="subscribers = $event"
|
||||
@init="isLoading = false"
|
||||
:api="`/api/subscriptions/admin/plans/${this.$route.params.id}/subscribers`"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
class="card overflow-x-auto shadow-card"
|
||||
>
|
||||
<!--Table data content-->
|
||||
<template slot-scope="{ row }">
|
||||
<tr
|
||||
v-if="config.subscriptionType === 'metered'"
|
||||
class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5"
|
||||
>
|
||||
<td class="py-3 pr-3 md:pr-1">
|
||||
<router-link
|
||||
class="flex items-center"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: {
|
||||
id: row.data.relationships.user.data.id,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<MemberAvatar :is-border="false" :size="36" :member="row.data.relationships.user" />
|
||||
<div class="ml-3 pr-10">
|
||||
<b
|
||||
class="max-w-1 block overflow-hidden text-ellipsis whitespace-nowrap text-sm font-bold"
|
||||
style="max-width: 155px"
|
||||
>
|
||||
{{ row.data.relationships.user.data.attributes.name }}
|
||||
</b>
|
||||
<span class="block text-xs text-gray-600 dark:text-gray-500">
|
||||
{{ row.data.relationships.user.data.attributes.email }}
|
||||
</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="whitespace-nowrap text-sm font-bold">
|
||||
{{ row.data.attributes.renews_at }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<img
|
||||
class="inline-block max-h-5"
|
||||
:src="$getPaymentLogo(row.data.attributes.driver)"
|
||||
:alt="row.data.attributes.driver"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="config.subscriptionType === 'fixed'"
|
||||
class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5"
|
||||
>
|
||||
<td class="py-3 pr-3 md:pr-1">
|
||||
<router-link
|
||||
class="flex items-center"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: {
|
||||
id: row.data.relationships.user.data.id,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<MemberAvatar :is-border="false" :size="36" :member="row.data.relationships.user" />
|
||||
<div class="ml-3 pr-10">
|
||||
<b
|
||||
class="max-w-1 block overflow-hidden text-ellipsis whitespace-nowrap text-sm font-bold"
|
||||
style="max-width: 155px"
|
||||
>
|
||||
{{ row.data.relationships.user.data.attributes.name }}
|
||||
</b>
|
||||
<span class="block text-xs text-gray-600 dark:text-gray-500">
|
||||
{{ row.data.relationships.user.data.attributes.email }}
|
||||
</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<ColorLabel :color="$getSubscriptionStatusColor(row.data.attributes.status)">
|
||||
{{ $t(row.data.attributes.status) }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-limit text-sm font-bold capitalize" style="max-width: 160px">
|
||||
{{ row.data.attributes.name }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
<!--todo: update renew attribute-->
|
||||
{{
|
||||
row.data.attributes.renews_at
|
||||
? row.data.attributes.renews_at
|
||||
: row.data.attributes.created_at
|
||||
}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.ends_at ? row.data.attributes.ends_at : '-' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<img
|
||||
class="inline-block max-h-5"
|
||||
:src="$getPaymentLogo(row.data.attributes.driver)"
|
||||
:alt="row.data.attributes.driver"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!--Empty page-->
|
||||
<template v-slot:empty-page>
|
||||
<InfoBox style="margin-bottom: 0">
|
||||
<p>{{ $t('admin_page_plans.subscribers.empty') }}</p>
|
||||
</InfoBox>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ColorLabel from '../../../../components/UI/Labels/ColorLabel'
|
||||
import MemberAvatar from '../../../../components/UI/Others/MemberAvatar'
|
||||
import DatatableCellImage from '../../../../components/UI/Table/DatatableCellImage'
|
||||
import { DownloadCloudIcon, Edit2Icon, Trash2Icon } from 'vue-feather-icons'
|
||||
import DatatableWrapper from '../../../../components/UI/Table/DatatableWrapper'
|
||||
import PageTabGroup from '../../../../components/Layout/PageTabGroup'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'PlanSubscribers',
|
||||
components: {
|
||||
DatatableCellImage,
|
||||
DownloadCloudIcon,
|
||||
DatatableWrapper,
|
||||
PageTabGroup,
|
||||
MemberAvatar,
|
||||
ColorLabel,
|
||||
Trash2Icon,
|
||||
Edit2Icon,
|
||||
PageTab,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
columns() {
|
||||
return {
|
||||
metered: [
|
||||
{
|
||||
label: this.$t('user'),
|
||||
field: 'user_id',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('renews_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('service'),
|
||||
field: 'driver',
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
fixed: [
|
||||
{
|
||||
label: this.$t('user'),
|
||||
field: 'user_id',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('status'),
|
||||
field: 'status',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('note'),
|
||||
field: 'plan.name',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('renews_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('ends_at'),
|
||||
field: 'ends_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('service'),
|
||||
field: 'driver',
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
}[this.config.subscriptionType]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
subscribers: undefined,
|
||||
isLoading: true,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -25,22 +25,10 @@ export default {
|
||||
title: this.$t('admin_settings.tabs.others'),
|
||||
route: 'AppOthers',
|
||||
},
|
||||
{
|
||||
title: this.$t('login_and_registration'),
|
||||
route: 'AppSignInUp',
|
||||
},
|
||||
{
|
||||
title: this.$t('appearance'),
|
||||
route: 'AppAppearance',
|
||||
},
|
||||
{
|
||||
title: this.$t('Adsense'),
|
||||
route: 'AppAdsense',
|
||||
},
|
||||
{
|
||||
title: this.$t('homepage'),
|
||||
route: 'AppIndex',
|
||||
},
|
||||
{
|
||||
title: this.$t('environment'),
|
||||
route: 'AppEnvironment',
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
<template>
|
||||
<PageTab>
|
||||
<!--Adsense basic setup-->
|
||||
<div v-if="adsense" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Basic Setup') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch :title="$t('Allow Google Adsense')" :description="$t('Allow ads on app pages.')">
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_adsense', adsense.allowedService)"
|
||||
v-model="adsense.allowedService"
|
||||
class="switch"
|
||||
:state="adsense.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputText
|
||||
:title="$t('Client Id')"
|
||||
:description="$t('Paste your Adsense Client ID e.g. ca-pub-XXXXXXXXXXXXXXXXX')"
|
||||
:is-last="true"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'adsense_client_id', adsense.clientId, true)"
|
||||
v-model="adsense.clientId"
|
||||
:placeholder="$t('Client Id...')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
|
||||
<!--Adsense places-->
|
||||
<div v-if="adsense" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Ads') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputText
|
||||
:title="$t('File Viewport Banner')"
|
||||
:description="$t('This banner will be showed above user files')"
|
||||
>
|
||||
<textarea
|
||||
rows="3"
|
||||
@input="$updateText('/admin/settings', 'adsense_banner_01', adsense.banner01, true)"
|
||||
v-model="adsense.banner01"
|
||||
:placeholder="$t('Paste the <ins></ins> tag here...')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<AppInputText
|
||||
:title="$t('Download Page Banner')"
|
||||
:description="$t('This banner will be showed below file download page')"
|
||||
>
|
||||
<textarea
|
||||
rows="3"
|
||||
@input="$updateText('/admin/settings', 'adsense_banner_02', adsense.banner02, true)"
|
||||
v-model="adsense.banner02"
|
||||
:placeholder="$t('Paste the <ins></ins> tag here...')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
|
||||
<AppInputText
|
||||
:title="$t('Homepage Banner')"
|
||||
:description="$t('This banner will be showed on the homepage')"
|
||||
:is-last="true"
|
||||
>
|
||||
<textarea
|
||||
rows="3"
|
||||
@input="$updateText('/admin/settings', 'adsense_banner_03', adsense.banner03, true)"
|
||||
v-model="adsense.banner03"
|
||||
:placeholder="$t('Paste the <ins></ins> tag here...')"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SwitchInput from '../../../../components/Inputs/SwitchInput'
|
||||
import AppInputButton from '../../../../components/Forms/Layouts/AppInputButton'
|
||||
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Adsense',
|
||||
components: {
|
||||
AppInputButton,
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
SwitchInput,
|
||||
FormLabel,
|
||||
PageTab,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
adsense: {
|
||||
allowedService: undefined,
|
||||
clientId: undefined,
|
||||
banner01: undefined,
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.adsense = {
|
||||
allowedService: this.config.allowedAdsense,
|
||||
clientId: this.config.adsenseClientId,
|
||||
banner01: this.config.adsenseBanner01,
|
||||
banner02: this.config.adsenseBanner02,
|
||||
banner03: this.config.adsenseBanner03,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,26 +1,5 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<!--Broadcasting setup-->
|
||||
<ValidationObserver
|
||||
@submit.prevent="broadcastSetupSubmit"
|
||||
ref="broadcastSetup"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="card shadow-card"
|
||||
>
|
||||
<BroadcastSetup v-model="broadcast" />
|
||||
|
||||
<ButtonBase
|
||||
:loading="isSendingBroadcastForm"
|
||||
:disabled="isSendingBroadcastForm"
|
||||
type="submit"
|
||||
button-style="theme"
|
||||
class="mt-6 w-full sm:mt-7 sm:w-auto"
|
||||
>
|
||||
{{ $t('save_broadcast_settings') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
|
||||
<!--Storage setup-->
|
||||
<ValidationObserver
|
||||
@submit.prevent="storageSetupSubmit"
|
||||
@@ -76,12 +55,10 @@ import PageTab from '../../../../components/Layout/PageTab'
|
||||
import MailSetup from '../../../../components/Forms/MailSetup'
|
||||
import { events } from '../../../../bus'
|
||||
import axios from 'axios'
|
||||
import BroadcastSetup from "../../../../components/Forms/BroadcastSetup";
|
||||
|
||||
export default {
|
||||
name: 'AppEnvironment',
|
||||
components: {
|
||||
BroadcastSetup,
|
||||
ValidationObserver,
|
||||
ValidationProvider,
|
||||
StorageSetup,
|
||||
@@ -99,40 +76,9 @@ export default {
|
||||
isLoading: false,
|
||||
isSendingEmailForm: false,
|
||||
isSendingStorageForm: false,
|
||||
isSendingBroadcastForm: false,
|
||||
broadcast: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async broadcastSetupSubmit() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.broadcastSetup.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
// Start loading
|
||||
this.isSendingBroadcastForm = true
|
||||
|
||||
axios
|
||||
.post('/api/admin/settings/broadcast', { ...this.broadcast })
|
||||
.then(() => {
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('broadcast_driver_updated'),
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('toaster', {
|
||||
type: 'danger',
|
||||
message: this.$t('popup_error.title'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSendingBroadcastForm = false
|
||||
|
||||
this.broadcast = undefined
|
||||
})
|
||||
},
|
||||
async storageSetupSubmit() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.storageSetup.validate()
|
||||
|
||||
@@ -1,588 +0,0 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<PageTabGroup v-if="app">
|
||||
<div class="form block-form">
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('homepage') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('allow_homepage')"
|
||||
:description="$t('allow_homepage_note')"
|
||||
:is-last="true"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allow_homepage', app.allow_homepage)"
|
||||
v-model="app.allow_homepage"
|
||||
class="switch"
|
||||
:state="app.allow_homepage"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<!--Header-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>Header Title</FormLabel>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<img src="/assets/images/admin/main-header.jpg" alt="Main Header" class="page-image" />
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Title:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Title"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'header_title', app.header_title)"
|
||||
v-model="app.header_title"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Description:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Description"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<textarea
|
||||
@input="$updateText('/admin/settings', 'header_description', app.header_description)"
|
||||
rows="2"
|
||||
v-model="app.header_description"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Features title-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>Features Title</FormLabel>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<div>
|
||||
<div class="inline-wrapper">
|
||||
<div class="switch-label">
|
||||
<label class="input-label"> Show section: </label>
|
||||
</div>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'section_features', app.section_features)"
|
||||
v-model="app.section_features"
|
||||
class="switch"
|
||||
:state="app.section_features"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="app.section_features">
|
||||
<div class="block-wrapper">
|
||||
<img src="/assets/images/admin/main-features.jpg" alt="Main Features" class="page-image" />
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Title:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Title"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'features_title', app.features_title)"
|
||||
v-model="app.features_title"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Description:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Description"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<textarea
|
||||
@input="
|
||||
$updateText('/admin/settings', 'features_description', app.features_description)
|
||||
"
|
||||
rows="2"
|
||||
v-model="app.features_description"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Feature boxes-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>Feature Boxes</FormLabel>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<div>
|
||||
<div class="inline-wrapper">
|
||||
<div class="switch-label">
|
||||
<label class="input-label"> Show section: </label>
|
||||
</div>
|
||||
<SwitchInput
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'section_feature_boxes',
|
||||
app.section_feature_boxes
|
||||
)
|
||||
"
|
||||
v-model="app.section_feature_boxes"
|
||||
class="switch"
|
||||
:state="app.section_feature_boxes"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="app.section_feature_boxes">
|
||||
<div class="block-wrapper">
|
||||
<img src="/assets/images/admin/feature-boxes.jpg" alt="Main Features" class="page-image" />
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>First Box Title:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Feature Title 1"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'feature_title_1', app.feature_title_1)"
|
||||
v-model="app.feature_title_1"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>First Box Description:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Feature Description 1"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<textarea
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'feature_description_1',
|
||||
app.feature_description_1
|
||||
)
|
||||
"
|
||||
rows="2"
|
||||
v-model="app.feature_description_1"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>Second Box Title:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Feature Title 2"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'feature_title_2', app.feature_title_2)"
|
||||
v-model="app.feature_title_2"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>Second Box Description:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Feature Description 2"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<textarea
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'feature_description_2',
|
||||
app.feature_description_2
|
||||
)
|
||||
"
|
||||
rows="2"
|
||||
v-model="app.feature_description_2"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>Third Box Title:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Feature Title 3"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'feature_title_3', app.feature_title_3)"
|
||||
v-model="app.feature_title_3"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>Third Box Description:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="Feature Description 3"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<textarea
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'feature_description_3',
|
||||
app.feature_description_3
|
||||
)
|
||||
"
|
||||
rows="2"
|
||||
v-model="app.feature_description_3"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Pricing Content-->
|
||||
<div v-if="config.isSaaS" class="card shadow-card">
|
||||
<FormLabel>Pricing Content</FormLabel>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<div>
|
||||
<div class="inline-wrapper">
|
||||
<div class="switch-label">
|
||||
<label class="input-label"> Show section: </label>
|
||||
</div>
|
||||
<SwitchInput
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'section_pricing_content',
|
||||
app.section_pricing_content
|
||||
)
|
||||
"
|
||||
v-model="app.section_pricing_content"
|
||||
class="switch"
|
||||
:state="app.section_pricing_content"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="app.section_pricing_content">
|
||||
<div class="block-wrapper">
|
||||
<img
|
||||
src="/assets/images/admin/pricing-content.jpg"
|
||||
alt="Main Features"
|
||||
class="page-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>Title:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Title"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'pricing_title', app.pricing_title)"
|
||||
v-model="app.pricing_title"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Description:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Description"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<textarea
|
||||
@input="
|
||||
$updateText('/admin/settings', 'pricing_description', app.pricing_description)
|
||||
"
|
||||
rows="2"
|
||||
v-model="app.pricing_description"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Get Started-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>Get Started Content</FormLabel>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<div>
|
||||
<div class="inline-wrapper">
|
||||
<div class="switch-label">
|
||||
<label class="input-label"> Show section: </label>
|
||||
</div>
|
||||
<SwitchInput
|
||||
@input="
|
||||
$updateText('/admin/settings', 'section_get_started', app.section_get_started)
|
||||
"
|
||||
v-model="app.section_get_started"
|
||||
class="switch"
|
||||
:state="app.section_get_started"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="app.section_get_started">
|
||||
<div class="block-wrapper">
|
||||
<img
|
||||
src="/assets/images/admin/get-started-content.jpg"
|
||||
alt="Main Features"
|
||||
class="page-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="block-wrapper">
|
||||
<label>Title:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Title"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'get_started_title', app.get_started_title)"
|
||||
v-model="app.get_started_title"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Description:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Description"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<textarea
|
||||
@input="
|
||||
$updateText(
|
||||
'/admin/settings',
|
||||
'get_started_description',
|
||||
app.get_started_description
|
||||
)
|
||||
"
|
||||
rows="2"
|
||||
v-model="app.get_started_description"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
></textarea>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Footer-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>Footer</FormLabel>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>Footer content:</label>
|
||||
<ValidationProvider
|
||||
tag="div"
|
||||
mode="passive"
|
||||
name="App Title"
|
||||
rules="required"
|
||||
v-slot="{ errors }"
|
||||
>
|
||||
<input
|
||||
@input="$updateText('/admin/settings', 'footer_content', app.footer_content)"
|
||||
v-model="app.footer_content"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
|
||||
</ValidationProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PageTabGroup>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import PageTabGroup from '../../../../components/Layout/PageTabGroup'
|
||||
import SelectInput from '../../../../components/Inputs/SelectInput'
|
||||
import SwitchInput from '../../../../components/Inputs/SwitchInput'
|
||||
import ImageInput from '../../../../components/Inputs/ImageInput'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import axios from 'axios'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'AppIndex',
|
||||
components: {
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
ValidationObserver,
|
||||
ValidationProvider,
|
||||
PageTabGroup,
|
||||
SwitchInput,
|
||||
SelectInput,
|
||||
ImageInput,
|
||||
ButtonBase,
|
||||
FormLabel,
|
||||
required,
|
||||
PageTab,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
app: undefined,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
axios
|
||||
.get('/api/admin/settings', {
|
||||
params: {
|
||||
column: 'allow_homepage|footer_content|get_started_description|get_started_title|pricing_description|pricing_title|feature_description_3|feature_title_3|feature_description_2|feature_title_2|feature_description_1|feature_title_1|features_description|features_title|header_description|header_title|section_get_started|section_pricing_content|section_feature_boxes|section_features',
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
this.app = {
|
||||
allow_homepage: parseInt(response.data.allow_homepage),
|
||||
section_features: parseInt(response.data.section_features),
|
||||
section_feature_boxes: parseInt(response.data.section_feature_boxes),
|
||||
section_pricing_content: parseInt(response.data.section_pricing_content),
|
||||
section_get_started: parseInt(response.data.section_get_started),
|
||||
header_title: response.data.header_title,
|
||||
header_description: response.data.header_description,
|
||||
features_title: response.data.features_title,
|
||||
features_description: response.data.features_description,
|
||||
feature_title_1: response.data.feature_title_1,
|
||||
feature_description_1: response.data.feature_description_1,
|
||||
feature_title_2: response.data.feature_title_2,
|
||||
feature_description_2: response.data.feature_description_2,
|
||||
feature_title_3: response.data.feature_title_3,
|
||||
feature_description_3: response.data.feature_description_3,
|
||||
pricing_title: response.data.pricing_title,
|
||||
pricing_description: response.data.pricing_description,
|
||||
get_started_title: response.data.get_started_title,
|
||||
get_started_description: response.data.get_started_description,
|
||||
footer_content: response.data.footer_content,
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../../../sass/vuefilemanager/variables';
|
||||
@import '../../../../../sass/vuefilemanager/mixins';
|
||||
@import '../../../../../sass/vuefilemanager/forms';
|
||||
|
||||
.block-form {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.page-image {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ececec;
|
||||
}
|
||||
</style>
|
||||
@@ -58,42 +58,27 @@
|
||||
{{ $t('user_features') }}
|
||||
</FormLabel>
|
||||
|
||||
<!--Available only when is not metered billing-->
|
||||
<div v-if="config.subscriptionType !== 'metered'">
|
||||
<AppInputSwitch
|
||||
:description="$t('admin_settings.others.storage_limit_help')"
|
||||
:title="$t('admin_settings.others.storage_limit')"
|
||||
>
|
||||
<SwitchInput
|
||||
v-model="app.storageLimitation"
|
||||
:state="app.storageLimitation"
|
||||
class="switch"
|
||||
@input="$updateText('/admin/settings', 'storage_limitation', app.storageLimitation)"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
<AppInputSwitch
|
||||
:description="$t('admin_settings.others.storage_limit_help')"
|
||||
:title="$t('admin_settings.others.storage_limit')"
|
||||
>
|
||||
<SwitchInput
|
||||
v-model="app.storageLimitation"
|
||||
:state="app.storageLimitation"
|
||||
class="switch"
|
||||
@input="$updateText('/admin/settings', 'storage_limitation', app.storageLimitation)"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputText v-if="app.storageLimitation" :title="$t('admin_settings.others.default_storage')">
|
||||
<input
|
||||
v-model="app.defaultStorage"
|
||||
:placeholder="$t('admin_settings.others.default_storage_plac')"
|
||||
class="focus-border-theme input-dark"
|
||||
max="999999999"
|
||||
min="1"
|
||||
type="number"
|
||||
@input="$updateText('/admin/settings', 'default_max_storage_amount', app.defaultStorage)"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
|
||||
<AppInputText :description="$t('zero_for_unlimited_members')" :is-last="true" :title="$t('max_team_members')">
|
||||
<AppInputText v-if="app.storageLimitation" :title="$t('admin_settings.others.default_storage')">
|
||||
<input
|
||||
v-model="app.teamsDefaultMembers"
|
||||
v-model="app.defaultStorage"
|
||||
:placeholder="$t('admin_settings.others.default_storage_plac')"
|
||||
class="focus-border-theme input-dark"
|
||||
max="999999999"
|
||||
min="1"
|
||||
type="number"
|
||||
@input="$updateText('/admin/settings', 'default_max_team_member', app.teamsDefaultMembers)"
|
||||
@input="$updateText('/admin/settings', 'default_max_storage_amount', app.defaultStorage)"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
@@ -216,63 +201,6 @@
|
||||
@input="$updateText('/admin/settings', 'google_analytics', app.googleAnalytics, true)"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
|
||||
<!--Upgrade License-->
|
||||
<div v-if="app && !config.isSaaS" class="card shadow-card">
|
||||
<FormLabel icon="trending-up">
|
||||
{{ $t('Upgrade your License') }}
|
||||
</FormLabel>
|
||||
|
||||
<ValidationObserver
|
||||
ref="upgradeLicense"
|
||||
v-slot="{ invalid }"
|
||||
class="mt-6"
|
||||
tag="form"
|
||||
@submit.prevent="upgradeLicense"
|
||||
>
|
||||
<ValidationProvider
|
||||
v-slot="{ errors }"
|
||||
mode="passive"
|
||||
name="Purchase Code"
|
||||
rules="required"
|
||||
tag="div"
|
||||
>
|
||||
<AppInputText
|
||||
:error="errors[0]"
|
||||
:is-last="true"
|
||||
>
|
||||
<div class="space-y-4 sm:flex sm:space-x-4 sm:space-y-0">
|
||||
<input
|
||||
v-model="purchaseCode"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
:placeholder="$t('Paste your Purchase code here...')"
|
||||
class="focus-border-theme input-dark"
|
||||
type="text"
|
||||
/>
|
||||
<ButtonBase :loading="isLoadingUpgradingButton" button-style="theme" class="w-full sm:w-auto" type="submit">
|
||||
{{ $t('Upgrade') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
|
||||
<!-- Subscription -->
|
||||
<div v-if="app && config.isSaaS" class="card shadow-card">
|
||||
<FormLabel icon="credit-card">
|
||||
{{ $t('subscription') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputText :description="$t('subscription_type_note')" :is-last="true" :title="$t('subscription_type')">
|
||||
<SelectInput
|
||||
:default="app.subscriptionType"
|
||||
:options="subscriptionTypes"
|
||||
:placeholder="$t('select_subscription_type')"
|
||||
@change="subscriptionTypeChange"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
@@ -310,7 +238,7 @@ export default {
|
||||
PageTab,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['subscriptionTypes', 'config']),
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -330,55 +258,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async upgradeLicense() {
|
||||
this.isLoadingUpgradingButton = true
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.upgradeLicense.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
axios.post('/api/admin/upgrade-license', {
|
||||
purchaseCode: this.purchaseCode
|
||||
})
|
||||
.then((response) => {
|
||||
this.$store.dispatch('getLanguageTranslations', this.config.locale)
|
||||
|
||||
this.$store.commit('REPLACE_CONFIG_VALUE', {
|
||||
key: 'isSaaS',
|
||||
value: true,
|
||||
})
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('Your license was successfully upgraded'),
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 400) {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('Purchase code is invalid or is not Extended License'),
|
||||
})
|
||||
} else {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoadingUpgradingButton = false
|
||||
})
|
||||
},
|
||||
subscriptionTypeChange(type) {
|
||||
events.$emit('confirm:open', {
|
||||
title: this.$t('subscription_type_change_warn'),
|
||||
message: this.$t('subscription_type_change_warn_description'),
|
||||
action: {
|
||||
type: type,
|
||||
operation: 'change-subscription-type',
|
||||
},
|
||||
})
|
||||
},
|
||||
async storeCredentials(service) {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.credentialsForm.validate()
|
||||
@@ -453,29 +332,9 @@ export default {
|
||||
storageLimitation: parseInt(response.data.storage_limitation),
|
||||
mimetypesBlacklist: response.data.mimetypes_blacklist,
|
||||
uploadLimit: response.data.upload_limit,
|
||||
subscriptionType: response.data.subscriptionType,
|
||||
chunkSize: response.data.chunk_size,
|
||||
teamsDefaultMembers: response.data.default_max_team_member,
|
||||
}
|
||||
})
|
||||
},
|
||||
created() {
|
||||
events.$on('action:confirmed', (data) => {
|
||||
if (data.operation === 'change-subscription-type') {
|
||||
|
||||
// Update database
|
||||
this.$updateText('/admin/settings', 'subscription_type', data.type)
|
||||
|
||||
// Update config
|
||||
this.$store.commit('REPLACE_CONFIG_VALUE', {
|
||||
key: 'subscriptionType',
|
||||
value: data.type,
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
events.$off('action:confirmed')
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,411 +0,0 @@
|
||||
<template>
|
||||
<PageTab>
|
||||
<!--User Login/Registration-->
|
||||
<div v-if="app" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('User Login/Registration') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('admin_settings.others.allow_registration')"
|
||||
:description="$t('admin_settings.others.allow_registration_help')"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'registration', app.userRegistration)"
|
||||
v-model="app.userRegistration"
|
||||
class="switch"
|
||||
:state="app.userRegistration"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('require_email_verification')"
|
||||
:description="$t('require_email_verification_note')"
|
||||
:is-last="true"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'user_verification', app.userVerification)"
|
||||
v-model="app.userVerification"
|
||||
class="switch"
|
||||
:state="app.userVerification"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<!--Facebook Social Authentication-->
|
||||
<div class="card shadow-card">
|
||||
<img :src="$getSocialLogo('facebook')" alt="Facebook" class="mb-8 h-5" />
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('Allow Login via Facebook')"
|
||||
:description="$t('You users will be able to login via Facebook account.')"
|
||||
:is-last="!facebook.allowedService"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_facebook_login', facebook.allowedService)"
|
||||
v-model="facebook.allowedService"
|
||||
class="switch"
|
||||
:state="facebook.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputText
|
||||
v-if="facebook.allowedService"
|
||||
:title="$t('Your Callback URL')"
|
||||
:description="$t('Please copy your url and paste it to the service callback URL.')"
|
||||
>
|
||||
<CopyInput size="small" :str="getCallbackEndpoint('facebook')" />
|
||||
</AppInputText>
|
||||
|
||||
<div
|
||||
v-if="config.isFacebookLoginConfigured && facebook.allowedService"
|
||||
@click="facebook.isVisibleCredentialsForm = !facebook.isVisibleCredentialsForm"
|
||||
class="flex cursor-pointer items-center"
|
||||
:class="{ 'mb-4': facebook.isVisibleCredentialsForm }"
|
||||
>
|
||||
<edit2-icon size="12" class="vue-feather text-theme mr-2" />
|
||||
<b class="text-xs">{{ $t('update_your_credentials') }}</b>
|
||||
</div>
|
||||
|
||||
<!--Set up facebook credentials-->
|
||||
<ValidationObserver
|
||||
v-if="
|
||||
(!config.isFacebookLoginConfigured || facebook.isVisibleCredentialsForm) && facebook.allowedService
|
||||
"
|
||||
@submit.prevent="storeCredentials('facebook')"
|
||||
ref="facebook"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="rounded-xl p-5 shadow-lg"
|
||||
>
|
||||
<FormLabel v-if="!config.isFacebookLoginConfigured" icon="shield">
|
||||
{{ $t('Configure Credentials') }}
|
||||
</FormLabel>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Client ID" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Client ID')" :error="errors[0]">
|
||||
<input
|
||||
v-model="facebook.credentials.client_id"
|
||||
:placeholder="$t('Paste your Client ID here')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Client Secret" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Client Secret')" :error="errors[0]">
|
||||
<input
|
||||
v-model="facebook.credentials.client_secret"
|
||||
:placeholder="$t('Paste your Client Secret here')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ButtonBase
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
button-style="theme"
|
||||
type="submit"
|
||||
class="w-full"
|
||||
>
|
||||
{{ $t('store_credentials') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
|
||||
<!--Google Social Authentication-->
|
||||
<div class="card shadow-card">
|
||||
<img :src="$getSocialLogo('google')" alt="Google" class="mb-8 h-7" />
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('Allow Login via Google')"
|
||||
:description="$t('You users will be able to login via Google account.')"
|
||||
:is-last="!google.allowedService"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_google_login', google.allowedService)"
|
||||
v-model="google.allowedService"
|
||||
class="switch"
|
||||
:state="google.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputText
|
||||
v-if="google.allowedService"
|
||||
:title="$t('Your Callback URL')"
|
||||
:description="$t('Please copy your url and paste it to the service callback URL.')"
|
||||
>
|
||||
<CopyInput size="small" :str="getCallbackEndpoint('google')" />
|
||||
</AppInputText>
|
||||
|
||||
<div
|
||||
v-if="config.isGoogleLoginConfigured && google.allowedService"
|
||||
@click="google.isVisibleCredentialsForm = !google.isVisibleCredentialsForm"
|
||||
class="flex cursor-pointer items-center"
|
||||
:class="{ 'mb-4': google.isVisibleCredentialsForm }"
|
||||
>
|
||||
<edit2-icon size="12" class="vue-feather text-theme mr-2" />
|
||||
<b class="text-xs">{{ $t('update_your_credentials') }}</b>
|
||||
</div>
|
||||
|
||||
<!--Set up Google credentials-->
|
||||
<ValidationObserver
|
||||
v-if="(!config.isGoogleLoginConfigured || google.isVisibleCredentialsForm) && google.allowedService"
|
||||
@submit.prevent="storeCredentials('google')"
|
||||
ref="google"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="rounded-xl p-5 shadow-lg"
|
||||
>
|
||||
<FormLabel v-if="!config.isGoogleLoginConfigured" icon="shield">
|
||||
{{ $t('Configure Credentials') }}
|
||||
</FormLabel>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Client ID" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Client ID')" :error="errors[0]">
|
||||
<input
|
||||
v-model="google.credentials.client_id"
|
||||
:placeholder="$t('Paste your Client ID here')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Client Secret" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Client Secret')" :error="errors[0]">
|
||||
<input
|
||||
v-model="google.credentials.client_secret"
|
||||
:placeholder="$t('Paste your Client Secret here')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ButtonBase
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
button-style="theme"
|
||||
type="submit"
|
||||
class="w-full"
|
||||
>
|
||||
{{ $t('store_credentials') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
|
||||
<!--Github Social Authentication-->
|
||||
<div class="card shadow-card">
|
||||
<img :src="$getSocialLogo('github')" alt="Github" class="mb-8 h-5" />
|
||||
|
||||
<AppInputSwitch
|
||||
:title="$t('Allow Login via GitHub')"
|
||||
:description="$t('You users will be able to login via GitHub account.')"
|
||||
:is-last="!github.allowedService"
|
||||
>
|
||||
<SwitchInput
|
||||
@input="$updateText('/admin/settings', 'allowed_github_login', github.allowedService)"
|
||||
v-model="github.allowedService"
|
||||
class="switch"
|
||||
:state="github.allowedService"
|
||||
/>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputText
|
||||
v-if="github.allowedService"
|
||||
:title="$t('Your Callback URL')"
|
||||
:description="$t('Please copy your url and paste it to the service callback URL.')"
|
||||
>
|
||||
<CopyInput size="small" :str="getCallbackEndpoint('github')" />
|
||||
</AppInputText>
|
||||
|
||||
<div
|
||||
v-if="config.isGithubLoginConfigured && github.allowedService"
|
||||
@click="github.isVisibleCredentialsForm = !github.isVisibleCredentialsForm"
|
||||
class="flex cursor-pointer items-center"
|
||||
:class="{ 'mb-4': github.isVisibleCredentialsForm }"
|
||||
>
|
||||
<edit2-icon size="12" class="vue-feather text-theme mr-2" />
|
||||
<b class="text-xs">{{ $t('update_your_credentials') }}</b>
|
||||
</div>
|
||||
|
||||
<!--Set up github credentials-->
|
||||
<ValidationObserver
|
||||
v-if="(!config.isGithubLoginConfigured || github.isVisibleCredentialsForm) && github.allowedService"
|
||||
@submit.prevent="storeCredentials('github')"
|
||||
ref="github"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="rounded-xl p-5 shadow-lg"
|
||||
>
|
||||
<FormLabel v-if="!config.isGithubLoginConfigured" icon="shield">
|
||||
{{ $t('Configure Credentials') }}
|
||||
</FormLabel>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Client ID" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Client ID')" :error="errors[0]">
|
||||
<input
|
||||
v-model="github.credentials.client_id"
|
||||
:placeholder="$t('Paste your Client ID here')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ValidationProvider tag="div" mode="passive" name="Client Secret" rules="required" v-slot="{ errors }">
|
||||
<AppInputText :title="$t('Client Secret')" :error="errors[0]">
|
||||
<input
|
||||
v-model="github.credentials.client_secret"
|
||||
:placeholder="$t('Paste your Client Secret here')"
|
||||
type="text"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
class="focus-border-theme input-dark"
|
||||
/>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
|
||||
<ButtonBase
|
||||
:disabled="isLoading"
|
||||
:loading="isLoading"
|
||||
button-style="theme"
|
||||
type="submit"
|
||||
class="w-full"
|
||||
>
|
||||
{{ $t('store_credentials') }}
|
||||
</ButtonBase>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Edit2Icon } from 'vue-feather-icons'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import SwitchInput from '../../../../components/Inputs/SwitchInput'
|
||||
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
|
||||
import CopyInput from '../../../../components/Inputs/CopyInput'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import { events } from '../../../../bus'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'SignInUp',
|
||||
components: {
|
||||
CopyInput,
|
||||
ValidationObserver,
|
||||
ValidationProvider,
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
SwitchInput,
|
||||
ButtonBase,
|
||||
Edit2Icon,
|
||||
FormLabel,
|
||||
required,
|
||||
PageTab,
|
||||
events,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
app: {
|
||||
userRegistration: undefined,
|
||||
userVerification: undefined,
|
||||
},
|
||||
facebook: {
|
||||
allowedService: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
credentials: {
|
||||
key: undefined,
|
||||
secret: undefined,
|
||||
},
|
||||
},
|
||||
google: {
|
||||
allowedService: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
credentials: {
|
||||
key: undefined,
|
||||
secret: undefined,
|
||||
},
|
||||
},
|
||||
github: {
|
||||
allowedService: false,
|
||||
isVisibleCredentialsForm: false,
|
||||
credentials: {
|
||||
key: undefined,
|
||||
secret: undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCallbackEndpoint(service) {
|
||||
return `${this.config.host}/socialite/${service}/callback`
|
||||
},
|
||||
async storeCredentials(service) {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs[service].validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
// Start loading
|
||||
this.isLoading = true
|
||||
|
||||
// Send request to get verify account
|
||||
axios
|
||||
.post('/api/admin/settings/social-service', {
|
||||
client_id: this[service].credentials.client_id,
|
||||
client_secret: this[service].credentials.client_secret,
|
||||
service: service,
|
||||
})
|
||||
.then(() => {
|
||||
// Commit credentials
|
||||
this.$store.commit('SET_SOCIAL_LOGIN_CONFIGURED', service)
|
||||
|
||||
this[service].allowedService = true
|
||||
this[service].isVisibleCredentialsForm = false
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.credentials_set', {
|
||||
service: service,
|
||||
}),
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 500) {
|
||||
this.isError = true
|
||||
this.errorMessage = error.response.data.message
|
||||
}
|
||||
})
|
||||
.finally(() => (this.isLoading = false))
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.facebook.allowedService = this.config.allowedFacebookLogin
|
||||
this.google.allowedService = this.config.allowedGoogleLogin
|
||||
this.github.allowedService = this.config.allowedGithubLogin
|
||||
|
||||
this.app.userRegistration = this.config.userRegistration
|
||||
this.app.userVerification = this.config.userVerification
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,155 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--Datatable-->
|
||||
<DatatableWrapper
|
||||
v-if="!config.isEmptySubscriptions"
|
||||
@init="isLoading = false"
|
||||
api="/api/subscriptions/admin"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
class="card overflow-x-auto shadow-card"
|
||||
>
|
||||
<!--Table data content-->
|
||||
<template slot-scope="{ row }">
|
||||
<tr class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5">
|
||||
<td class="py-5 pr-3 md:pr-1">
|
||||
<router-link
|
||||
class="flex items-center"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: {
|
||||
id: row.data.relationships.user.data.id,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<MemberAvatar :is-border="false" :size="36" :member="row.data.relationships.user" />
|
||||
<div class="ml-3 pr-10">
|
||||
<b
|
||||
class="max-w-1 block overflow-hidden text-ellipsis whitespace-nowrap text-sm font-bold"
|
||||
style="max-width: 155px"
|
||||
>
|
||||
{{ row.data.relationships.user.data.attributes.name }}
|
||||
</b>
|
||||
<span class="block text-xs text-gray-600 dark:text-gray-500">
|
||||
{{ row.data.relationships.user.data.attributes.email }}
|
||||
</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<ColorLabel :color="$getSubscriptionStatusColor(row.data.attributes.status)">
|
||||
{{ $t(row.data.attributes.status) }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-limit text-sm font-bold capitalize" style="max-width: 160px">
|
||||
{{ row.data.attributes.name }}
|
||||
</span>
|
||||
<span class="block text-xs font-bold text-gray-400">
|
||||
{{ row.data.relationships.plan.data.attributes.price }}
|
||||
/
|
||||
{{ $t(`interval.${row.data.relationships.plan.data.attributes.interval}`) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
<!--todo: update renew attribute-->
|
||||
{{
|
||||
row.data.attributes.renews_at
|
||||
? row.data.attributes.renews_at
|
||||
: row.data.attributes.created_at
|
||||
}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.ends_at ? row.data.attributes.ends_at : '-' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<img
|
||||
class="inline-block max-h-5"
|
||||
:src="$getPaymentLogo(row.data.attributes.driver)"
|
||||
:alt="row.data.attributes.driver"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
|
||||
<!--Empty State-->
|
||||
<div v-if="config.isEmptySubscriptions" class="flex items-center justify-center fixed top-0 bottom-0 left-0 right-0">
|
||||
<div class="text-center">
|
||||
<img
|
||||
class="mb-6 inline-block w-28"
|
||||
src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f5c3.svg"
|
||||
alt="transaction"
|
||||
/>
|
||||
|
||||
<h1 class="mb-1 text-2xl font-bold">
|
||||
{{ $t('there_is_nothing') }}
|
||||
</h1>
|
||||
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ $t('all_visible_subscriptions_here') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ColorLabel from '../../components/UI/Labels/ColorLabel'
|
||||
import MemberAvatar from '../../components/UI/Others/MemberAvatar'
|
||||
import DatatableWrapper from '../../components/UI/Table/DatatableWrapper'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Subscriptions',
|
||||
components: {
|
||||
ColorLabel,
|
||||
MemberAvatar,
|
||||
DatatableWrapper,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('user'),
|
||||
field: 'user_id',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('status'),
|
||||
field: 'status',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('note'),
|
||||
field: 'plan.name',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('renews_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('ends_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('service'),
|
||||
field: 'driver.driver',
|
||||
sortable: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -22,9 +22,7 @@
|
||||
class="overflow-x-auto"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<!--Not a subscription-->
|
||||
<tr
|
||||
v-if="config.subscriptionType === 'none'"
|
||||
class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5"
|
||||
>
|
||||
<td class="py-3 pr-3 md:pr-1">
|
||||
@@ -99,165 +97,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!--Fixed subscription-->
|
||||
<tr
|
||||
v-if="config.subscriptionType === 'fixed'"
|
||||
class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5"
|
||||
>
|
||||
<td class="py-3 pr-3 md:pr-1">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<MemberAvatar
|
||||
:is-border="false"
|
||||
:size="44"
|
||||
:member="row"
|
||||
/>
|
||||
<div class="ml-3 pr-10">
|
||||
<b
|
||||
class="max-w-1 block overflow-hidden text-ellipsis whitespace-nowrap text-sm font-bold"
|
||||
style="max-width: 155px"
|
||||
>
|
||||
{{ row.data.relationships.settings.data.attributes.name }}
|
||||
</b>
|
||||
<span class="block text-xs text-gray-600 dark:text-gray-500">
|
||||
{{ row.data.attributes.email }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<ColorLabel :color="$getUserRoleColor(row.data.attributes.role)">
|
||||
{{ $t(row.data.attributes.role) }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.relationships.subscription ? $t('premium') : $t('free') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span v-if="row.data.attributes.storage.capacity !== 0" class="text-sm font-bold">
|
||||
{{ row.data.attributes.storage.used_formatted }}
|
||||
</span>
|
||||
<span v-if="row.data.attributes.storage.capacity === 0" class="text-sm font-bold"> - </span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1" v-if="config.storageLimit">
|
||||
<span v-if="row.data.attributes.storage.capacity !== 0" class="text-sm font-bold">
|
||||
{{ row.data.attributes.storage.capacity_formatted }}
|
||||
</span>
|
||||
<span v-if="row.data.attributes.storage.capacity === 0" class="text-sm font-bold"> - </span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.created_at }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<div class="flex w-full justify-end space-x-2">
|
||||
<router-link
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<Edit2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
<router-link
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-red-100 dark:bg-2x-dark-foreground"
|
||||
:to="{
|
||||
name: 'UserDelete',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<Trash2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!--Metered subscription-->
|
||||
<tr
|
||||
v-if="config.subscriptionType === 'metered'"
|
||||
class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5"
|
||||
>
|
||||
<td class="py-3 pr-3 md:pr-1">
|
||||
<router-link
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<MemberAvatar
|
||||
:is-border="false"
|
||||
:size="44"
|
||||
:member="row"
|
||||
/>
|
||||
<div class="ml-3 pr-10">
|
||||
<b
|
||||
class="max-w-1 block overflow-hidden text-ellipsis whitespace-nowrap text-sm font-bold"
|
||||
style="max-width: 155px"
|
||||
>
|
||||
{{ row.data.relationships.settings.data.attributes.name }}
|
||||
</b>
|
||||
<span class="block text-xs text-gray-600 dark:text-gray-500">
|
||||
{{ row.data.attributes.email }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<ColorLabel :color="$getUserRoleColor(row.data.attributes.role)">
|
||||
{{ $t(row.data.attributes.role) }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.meta.usages ? row.data.meta.usages.featureEstimates.storage.usage : '-' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.meta.usages ? row.data.meta.usages.costEstimate : '-' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-3 md:px-1">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.created_at }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="pl-3 text-right md:pl-1">
|
||||
<div class="flex w-full justify-end space-x-2">
|
||||
<router-link
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground"
|
||||
:to="{
|
||||
name: 'UserDetail',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<Edit2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
<router-link
|
||||
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-red-100 dark:bg-2x-dark-foreground"
|
||||
:to="{
|
||||
name: 'UserDelete',
|
||||
params: { id: row.data.id },
|
||||
}"
|
||||
>
|
||||
<Trash2Icon size="15" class="opacity-75" />
|
||||
</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
@@ -279,7 +118,6 @@ import ColorLabel from '../../components/UI/Labels/ColorLabel'
|
||||
import Spinner from '../../components/UI/Others/Spinner'
|
||||
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'Users',
|
||||
@@ -299,101 +137,36 @@ export default {
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
columns() {
|
||||
return {
|
||||
metered: [
|
||||
{
|
||||
label: this.$t('user'),
|
||||
field: 'email',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('role'),
|
||||
field: 'role',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('storage_used'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('billing_est.'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('created_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
fixed: [
|
||||
{
|
||||
label: this.$t('user'),
|
||||
field: 'email',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('role'),
|
||||
field: 'role',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('account'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('storage_used'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('max_storage'),
|
||||
sortable: false,
|
||||
hidden: !this.config.storageLimit,
|
||||
},
|
||||
{
|
||||
label: this.$t('created_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
none: [
|
||||
{
|
||||
label: this.$t('user'),
|
||||
field: 'email',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('role'),
|
||||
field: 'role',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('storage_used'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('max_storage'),
|
||||
sortable: false,
|
||||
hidden: !this.config.storageLimit,
|
||||
},
|
||||
{
|
||||
label: this.$t('created_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
],
|
||||
}[this.config.subscriptionType]
|
||||
return [
|
||||
{
|
||||
label: this.$t('user'),
|
||||
field: 'email',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('role'),
|
||||
field: 'role',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('storage_used'),
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
label: this.$t('max_storage'),
|
||||
sortable: false,
|
||||
hidden: !this.config.storageLimit,
|
||||
},
|
||||
{
|
||||
label: this.$t('created_at'),
|
||||
field: 'created_at',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: this.$t('action'),
|
||||
sortable: false,
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<!--Image input for replace avatar-->
|
||||
<MemberAvatar class="shadow-lg rounded-xl" :size="64" :is-border="false" :member="user" />
|
||||
|
||||
<!--User name & email-->
|
||||
<!--User name & email-->
|
||||
<div class="ml-4">
|
||||
<b class="text-md block font-bold sm:text-lg">
|
||||
{{ user.data.relationships.settings.data.attributes.first_name }}
|
||||
@@ -27,7 +27,7 @@
|
||||
<CardNavigation :pages="pages" class="-mx-1" />
|
||||
</div>
|
||||
|
||||
<!--Router Content-->
|
||||
<!--Router Content-->
|
||||
<router-view :user="user" @reload-user="fetchUser" />
|
||||
</div>
|
||||
<div id="loader" v-if="isLoading">
|
||||
@@ -38,106 +38,82 @@
|
||||
|
||||
<script>
|
||||
import CardNavigation from '../../../components/UI/Others/CardNavigation'
|
||||
import { UserIcon, HardDriveIcon, LockIcon, Trash2Icon, FileTextIcon, CreditCardIcon } from 'vue-feather-icons'
|
||||
import {UserIcon, HardDriveIcon, LockIcon, Trash2Icon, FileTextIcon, CreditCardIcon} from 'vue-feather-icons'
|
||||
import MobileHeader from '../../../components/Mobile/MobileHeader'
|
||||
import SectionTitle from '../../../components/UI/Labels/SectionTitle'
|
||||
import ColorLabel from '../../../components/UI/Labels/ColorLabel'
|
||||
import Spinner from '../../../components/UI/Others/Spinner'
|
||||
import { events } from '../../../bus'
|
||||
import { mapGetters } from 'vuex'
|
||||
import {events} from '../../../bus'
|
||||
import {mapGetters} from 'vuex'
|
||||
import axios from 'axios'
|
||||
import MemberAvatar from "../../../components/UI/Others/MemberAvatar";
|
||||
|
||||
export default {
|
||||
name: 'Profile',
|
||||
components: {
|
||||
name: 'Profile',
|
||||
components: {
|
||||
MemberAvatar,
|
||||
CardNavigation,
|
||||
CreditCardIcon,
|
||||
HardDriveIcon,
|
||||
SectionTitle,
|
||||
FileTextIcon,
|
||||
MobileHeader,
|
||||
ColorLabel,
|
||||
Trash2Icon,
|
||||
UserIcon,
|
||||
LockIcon,
|
||||
Spinner,
|
||||
},
|
||||
watch: {
|
||||
'$route.fullPath': function () {
|
||||
this.fetchUser()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
admin() {
|
||||
return this.$store.getters.user ? this.$store.getters.user : undefined
|
||||
},
|
||||
pages() {
|
||||
if (this.config.subscriptionType === 'none') {
|
||||
return [
|
||||
{
|
||||
title: this.$t('detail'),
|
||||
route: 'UserDetail',
|
||||
},
|
||||
{
|
||||
title: this.$t('storage'),
|
||||
route: 'UserStorage',
|
||||
},
|
||||
{
|
||||
title: this.$t('password'),
|
||||
route: 'UserPassword',
|
||||
},
|
||||
{
|
||||
title: this.$t('delete_account'),
|
||||
route: 'UserDelete',
|
||||
},
|
||||
]
|
||||
}
|
||||
CardNavigation,
|
||||
CreditCardIcon,
|
||||
HardDriveIcon,
|
||||
SectionTitle,
|
||||
FileTextIcon,
|
||||
MobileHeader,
|
||||
ColorLabel,
|
||||
Trash2Icon,
|
||||
UserIcon,
|
||||
LockIcon,
|
||||
Spinner,
|
||||
},
|
||||
watch: {
|
||||
'$route.fullPath': function () {
|
||||
this.fetchUser()
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
admin() {
|
||||
return this.$store.getters.user ? this.$store.getters.user : undefined
|
||||
},
|
||||
pages() {
|
||||
return [
|
||||
{
|
||||
title: this.$t('detail'),
|
||||
route: 'UserDetail',
|
||||
},
|
||||
{
|
||||
title: this.$t('storage'),
|
||||
route: 'UserStorage',
|
||||
},
|
||||
{
|
||||
title: this.$t('password'),
|
||||
route: 'UserPassword',
|
||||
},
|
||||
{
|
||||
title: this.$t('delete_account'),
|
||||
route: 'UserDelete',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
user: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchUser() {
|
||||
axios.get('/api/admin/users/' + this.$route.params.id)
|
||||
.then((response) => {
|
||||
this.user = response.data
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchUser()
|
||||
|
||||
return [
|
||||
{
|
||||
title: this.$t('detail'),
|
||||
route: 'UserDetail',
|
||||
},
|
||||
{
|
||||
title: this.$t('storage'),
|
||||
route: 'UserStorage',
|
||||
},
|
||||
{
|
||||
title: this.$t('billing'),
|
||||
route: 'UserSubscription',
|
||||
},
|
||||
{
|
||||
title: this.$t('password'),
|
||||
route: 'UserPassword',
|
||||
},
|
||||
{
|
||||
title: this.$t('delete_account'),
|
||||
route: 'UserDelete',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
user: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchUser() {
|
||||
axios.get('/api/admin/users/' + this.$route.params.id).then((response) => {
|
||||
this.user = response.data
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.fetchUser()
|
||||
|
||||
events.$on('reload:user', () => this.fetchUser())
|
||||
},
|
||||
events.$on('reload:user', () => this.fetchUser())
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -71,67 +71,6 @@
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>{{ $t('billing_information') }}</FormLabel>
|
||||
<AppInputText :title="$t('name')">
|
||||
<input
|
||||
:value="user.data.relationships.settings.data.attributes.name"
|
||||
type="text"
|
||||
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
|
||||
disabled
|
||||
/>
|
||||
</AppInputText>
|
||||
<AppInputText :title="$t('address')">
|
||||
<input
|
||||
:value="user.data.relationships.settings.data.attributes.address"
|
||||
type="text"
|
||||
disabled
|
||||
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
|
||||
/>
|
||||
</AppInputText>
|
||||
<AppInputText :title="$t('country')">
|
||||
<input
|
||||
:value="user.data.relationships.settings.data.attributes.country"
|
||||
type="text"
|
||||
disabled
|
||||
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
|
||||
/>
|
||||
</AppInputText>
|
||||
<div class="flex space-x-4">
|
||||
<AppInputText :title="$t('city')" class="w-full">
|
||||
<input
|
||||
:value="user.data.relationships.settings.data.attributes.city"
|
||||
type="text"
|
||||
disabled
|
||||
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
|
||||
/>
|
||||
</AppInputText>
|
||||
<AppInputText :title="$t('postal_code')" class="w-full">
|
||||
<input
|
||||
:value="user.data.relationships.settings.data.attributes.postal_code"
|
||||
type="text"
|
||||
disabled
|
||||
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
<AppInputText :title="$t('state')">
|
||||
<input
|
||||
:value="user.data.relationships.settings.data.attributes.state"
|
||||
type="text"
|
||||
disabled
|
||||
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
|
||||
/>
|
||||
</AppInputText>
|
||||
<AppInputText :title="$t('phone_number')" :is-last="true">
|
||||
<input
|
||||
:value="user.data.relationships.settings.data.attributes.phone_number"
|
||||
type="text"
|
||||
disabled
|
||||
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
|
||||
/>
|
||||
</AppInputText>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('subscription') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="-mt-3 mb-0.5 block text-xl font-extrabold sm:text-3xl">
|
||||
{{ status }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 mb-8 block text-sm dark:text-gray-500 text-gray-400">
|
||||
{{ subscription.relationships.plan.data.attributes.name }} /
|
||||
{{ price }}
|
||||
</b>
|
||||
|
||||
<div v-for="(limit, i) in limitations" :key="i" :class="{ 'mb-6': Object.keys(limitations).length - 1 !== i }">
|
||||
<b class="mb-3 block text-sm dark:text-gray-500 text-gray-400">
|
||||
{{ limit.message }}
|
||||
</b>
|
||||
<ProgressLine v-if="limit.isVisibleBar" :data="limit.distribution" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import ProgressLine from '../../../../components/UI/ProgressChart/ProgressLine'
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'UserFixedSubscription',
|
||||
props: ['subscription', 'user'],
|
||||
components: {
|
||||
ProgressLine,
|
||||
FormLabel,
|
||||
},
|
||||
computed: {
|
||||
status() {
|
||||
return {
|
||||
active: this.$t('active_until', {date: this.subscription.attributes.renews_at}),
|
||||
cancelled: this.$t('ends_at_date', {date: this.subscription.attributes.ends_at}),
|
||||
}[this.subscription.attributes.status]
|
||||
},
|
||||
price() {
|
||||
return {
|
||||
month: this.$t('price_per_month', {price: this.subscription.relationships.plan.data.attributes.price}),
|
||||
year: this.$t('price_per_year', {price: this.subscription.relationships.plan.data.attributes.price}),
|
||||
}[this.subscription.relationships.plan.data.attributes.interval]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
limitations: [],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
Object.entries(this.user.data.meta.limitations).map(([key, item]) => {
|
||||
let payload = {
|
||||
color: {
|
||||
max_storage_amount: 'warning',
|
||||
max_team_members: 'purple',
|
||||
},
|
||||
message: {
|
||||
max_storage_amount: this.$t('total_x_of_x_used', {use: item.use, total:item.total }),
|
||||
max_team_members: item.total === -1
|
||||
? this.$t('max_team_members.unlimited')
|
||||
: this.$t('total_x_of_x_members', {use: item.use, total:item.total }),
|
||||
},
|
||||
title: {
|
||||
max_storage_amount: this.$t('storage'),
|
||||
max_team_members: this.$t('team_members'),
|
||||
},
|
||||
}
|
||||
|
||||
this.limitations.push({
|
||||
message: payload.message[key],
|
||||
isVisibleBar: item.total === -1,
|
||||
distribution: [
|
||||
{
|
||||
progress: item.percentage,
|
||||
color: payload.color[key],
|
||||
title: payload.title[key],
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,161 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!--Balance-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
{{ $t('balance') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
|
||||
{{ user.data.relationships.balance.data.attributes.formatted }}
|
||||
</b>
|
||||
|
||||
<ValidationObserver
|
||||
ref="creditUserBalance"
|
||||
@submit.prevent="increaseBalance"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
class="mt-6"
|
||||
>
|
||||
<ValidationProvider tag="div" v-slot="{ errors }" mode="passive" name="Balance Amount" rules="required">
|
||||
<AppInputText
|
||||
:description="$t('balance_will_be_increased')"
|
||||
:error="errors[0]"
|
||||
:is-last="true"
|
||||
>
|
||||
<div class="space-y-4 sm:flex sm:space-x-4 sm:space-y-0">
|
||||
<input
|
||||
v-model="balanceAmount"
|
||||
:placeholder="$t('increase_for')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
/>
|
||||
<ButtonBase
|
||||
type="submit"
|
||||
button-style="theme"
|
||||
class="w-full sm:w-auto"
|
||||
:loading="isUpdatingBalanceAmount"
|
||||
:disabled="isUpdatingBalanceAmount"
|
||||
>
|
||||
{{ $t('increase_balance') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
|
||||
<!--Usage Estimates-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
{{ $t('usage_estimates') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
|
||||
{{ user.data.meta.usages.costEstimate }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 mb-5 block text-sm text-gray-400">
|
||||
{{ user.data.relationships.subscription.data.attributes.updated_at }}
|
||||
{{ $t('till_now') }}
|
||||
</b>
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="flex items-center justify-between border-b border-dashed border-light py-2 dark:border-opacity-5"
|
||||
v-for="(usage, i) in user.data.meta.usages.featureEstimates"
|
||||
:key="i"
|
||||
>
|
||||
<div class="w-2/4 leading-none">
|
||||
<b class="text-sm font-bold leading-none">
|
||||
{{ $t(usage.feature) }}
|
||||
</b>
|
||||
<small class="block pt-2 text-xs leading-none dark:text-gray-500 text-gray-500">
|
||||
{{ $t(`feature_usage_desc_${usage.feature}`) }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="w-1/4 text-left">
|
||||
<span class="text-gray-560 text-sm font-bold">
|
||||
{{ usage.usage }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-1/4 text-right">
|
||||
<span class="text-theme text-sm font-bold">
|
||||
{{ usage.cost }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import ColorLabel from '../../../../components/UI/Labels/ColorLabel'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
import { events } from '../../../../bus'
|
||||
|
||||
export default {
|
||||
name: 'UserMeteredSubscription',
|
||||
props: ['subscription', 'user'],
|
||||
components: {
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputText,
|
||||
ButtonBase,
|
||||
ColorLabel,
|
||||
FormLabel,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([]),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
balanceAmount: undefined,
|
||||
isUpdatingBalanceAmount: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async increaseBalance() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.creditUserBalance.validate()
|
||||
|
||||
if (!isValid) return
|
||||
|
||||
this.isUpdatingBalanceAmount = true
|
||||
|
||||
axios
|
||||
.post(`/api/subscriptions/admin/users/${this.user.data.id}/credit`, {
|
||||
amount: this.balanceAmount,
|
||||
})
|
||||
.then(() => {
|
||||
events.$emit('reload:user')
|
||||
|
||||
this.balanceAmount = undefined
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('balance_was_increased'),
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('toaster', {
|
||||
type: 'danger',
|
||||
message: this.$t('popup_error.title'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isUpdatingBalanceAmount = false
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {},
|
||||
}
|
||||
</script>
|
||||
@@ -11,11 +11,11 @@
|
||||
</b>
|
||||
|
||||
<b
|
||||
v-if="
|
||||
v-if="
|
||||
config.subscriptionType === 'fixed' || (config.subscriptionType === 'none' && config.storageLimit)
|
||||
"
|
||||
class="mt-0.5 block text-sm dark:text-gray-500 text-gray-400"
|
||||
>
|
||||
class="mt-0.5 block text-sm dark:text-gray-500 text-gray-400"
|
||||
>
|
||||
{{ $t('total_of', {capacity: storage.data.attributes.capacity}) }}
|
||||
{{ $t('used') }}
|
||||
</b>
|
||||
@@ -23,7 +23,7 @@
|
||||
<ProgressLine v-if="storage.data.attributes.used !== '0B'" :data="distribution" class="mt-5" />
|
||||
</div>
|
||||
|
||||
<!--Upload-->
|
||||
<!--Upload-->
|
||||
<div v-if="distribution" class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
{{ $t('upload') }}
|
||||
@@ -40,7 +40,7 @@
|
||||
<BarChart :data="storage.data.meta.traffic.chart.upload" color="#FFBD2D" />
|
||||
</div>
|
||||
|
||||
<!--Download-->
|
||||
<!--Download-->
|
||||
<div v-if="distribution" class="card shadow-card">
|
||||
<FormLabel icon="hard-drive">
|
||||
{{ $t('download') }}
|
||||
@@ -57,44 +57,44 @@
|
||||
<BarChart :data="storage.data.meta.traffic.chart.download" color="#9d66fe" />
|
||||
</div>
|
||||
|
||||
<!--Set Storage Size-->
|
||||
<!--Set Storage Size-->
|
||||
<div
|
||||
v-if="config.storageLimit && !user.data.attributes.subscription && config.subscriptionType !== 'metered'"
|
||||
class="card shadow-card"
|
||||
>
|
||||
v-if="config.storageLimit"
|
||||
class="card shadow-card"
|
||||
>
|
||||
<FormLabel>
|
||||
{{ $t('user_box_storage.title') }}
|
||||
</FormLabel>
|
||||
<ValidationObserver
|
||||
ref="changeStorageCapacity"
|
||||
@submit.prevent="changeStorageCapacity"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
>
|
||||
ref="changeStorageCapacity"
|
||||
@submit.prevent="changeStorageCapacity"
|
||||
v-slot="{ invalid }"
|
||||
tag="form"
|
||||
>
|
||||
<ValidationProvider tag="div" v-slot="{ errors }" mode="passive" name="Capacity" rules="required">
|
||||
<AppInputText
|
||||
:title="$t('admin_page_user.label_change_capacity')"
|
||||
:description="$t('user_box_storage.description')"
|
||||
:error="errors[0]"
|
||||
:is-last="true"
|
||||
>
|
||||
:title="$t('admin_page_user.label_change_capacity')"
|
||||
:description="$t('user_box_storage.description')"
|
||||
:error="errors[0]"
|
||||
:is-last="true"
|
||||
>
|
||||
<div class="space-y-4 sm:flex sm:space-x-4 sm:space-y-0">
|
||||
<input
|
||||
v-model="capacity"
|
||||
:placeholder="$t('admin_page_user.label_change_capacity')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
/>
|
||||
v-model="capacity"
|
||||
:placeholder="$t('admin_page_user.label_change_capacity')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{ '!border-rose-600': errors[0] }"
|
||||
/>
|
||||
<ButtonBase
|
||||
:loading="isSendingRequest"
|
||||
:disabled="isSendingRequest"
|
||||
type="submit"
|
||||
button-style="theme"
|
||||
class="w-full sm:w-auto"
|
||||
>
|
||||
:loading="isSendingRequest"
|
||||
:disabled="isSendingRequest"
|
||||
type="submit"
|
||||
button-style="theme"
|
||||
class="w-full sm:w-auto"
|
||||
>
|
||||
{{ $t('change_capacity') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
@@ -112,102 +112,103 @@ import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import PageTabGroup from '../../../../components/Layout/PageTabGroup'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
|
||||
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
|
||||
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
|
||||
import { required } from 'vee-validate/dist/rules'
|
||||
import {required} from 'vee-validate/dist/rules'
|
||||
import BarChart from '../../../../components/UI/BarChart/BarChart'
|
||||
import { events } from '../../../../bus'
|
||||
import { mapGetters } from 'vuex'
|
||||
import {events} from '../../../../bus'
|
||||
import {mapGetters} from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'UserStorage',
|
||||
props: ['user'],
|
||||
components: {
|
||||
ProgressLine,
|
||||
AppInputText,
|
||||
PageTabGroup,
|
||||
FormLabel,
|
||||
PageTab,
|
||||
InfoBox,
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
ButtonBase,
|
||||
required,
|
||||
BarChart,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
isSendingRequest: false,
|
||||
capacity: undefined,
|
||||
storage: undefined,
|
||||
distribution: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async changeStorageCapacity() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.changeStorageCapacity.validate()
|
||||
name: 'UserStorage',
|
||||
props: ['user'],
|
||||
components: {
|
||||
ProgressLine,
|
||||
AppInputText,
|
||||
PageTabGroup,
|
||||
FormLabel,
|
||||
PageTab,
|
||||
InfoBox,
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
ButtonBase,
|
||||
required,
|
||||
BarChart,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
isSendingRequest: false,
|
||||
capacity: undefined,
|
||||
storage: undefined,
|
||||
distribution: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async changeStorageCapacity() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.changeStorageCapacity.validate()
|
||||
|
||||
if (!isValid) return
|
||||
if (!isValid) return
|
||||
|
||||
this.isSendingRequest = true
|
||||
this.isSendingRequest = true
|
||||
|
||||
// Send request to get user reset link
|
||||
axios
|
||||
.post(this.$store.getters.api + '/admin/users/' + this.$route.params.id + '/capacity', {
|
||||
attributes: {
|
||||
max_storage_amount: this.capacity,
|
||||
},
|
||||
_method: 'patch',
|
||||
})
|
||||
.then(() => {
|
||||
// Reset errors
|
||||
this.$refs.changeStorageCapacity.reset()
|
||||
// Send request to get user reset link
|
||||
axios
|
||||
.post(this.$store.getters.api + '/admin/users/' + this.$route.params.id + '/capacity', {
|
||||
attributes: {
|
||||
max_storage_amount: this.capacity,
|
||||
},
|
||||
_method: 'patch',
|
||||
})
|
||||
.then(() => {
|
||||
// Reset errors
|
||||
this.$refs.changeStorageCapacity.reset()
|
||||
|
||||
this.isSendingRequest = false
|
||||
this.isSendingRequest = false
|
||||
|
||||
this.getStorageDetails()
|
||||
this.getStorageDetails()
|
||||
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.changed_capacity'),
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.isSendingRequest = false
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.changed_capacity'),
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
this.isSendingRequest = false
|
||||
|
||||
if (error.response.status == 422) {
|
||||
// Password validation error
|
||||
if (error.response.data.errors['attributes.max_storage_amount']) {
|
||||
this.$refs.changeStorageCapacity.setErrors({
|
||||
Capacity: this.$t('errors.capacity_digit'),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getStorageDetails() {
|
||||
axios.get('/api/admin/users/' + this.$route.params.id + '/storage').then((response) => {
|
||||
this.distribution = this.$mapStorageUsage(response.data)
|
||||
if (error.response.status == 422) {
|
||||
// Password validation error
|
||||
if (error.response.data.errors['attributes.max_storage_amount']) {
|
||||
this.$refs.changeStorageCapacity.setErrors({
|
||||
Capacity: this.$t('errors.capacity_digit'),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getStorageDetails() {
|
||||
axios.get('/api/admin/users/' + this.$route.params.id + '/storage')
|
||||
.then((response) => {
|
||||
this.distribution = this.$mapStorageUsage(response.data)
|
||||
|
||||
this.storage = response.data
|
||||
this.storage = response.data
|
||||
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getStorageDetails()
|
||||
},
|
||||
this.isLoading = false
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getStorageDetails()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<UserMeteredSubscription
|
||||
v-if="subscription && config.subscriptionType === 'metered'"
|
||||
:subscription="subscription"
|
||||
:user="user"
|
||||
/>
|
||||
|
||||
<UserFixedSubscription
|
||||
v-if="subscription && config.subscriptionType === 'fixed'"
|
||||
:subscription="subscription"
|
||||
:user="user"
|
||||
/>
|
||||
|
||||
<!--Free Plan-->
|
||||
<div v-if="!subscription && config.subscriptionType === 'fixed'" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('subscription') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
|
||||
{{ $t('free_plan') }}
|
||||
</b>
|
||||
|
||||
<b class="block text-sm text-gray-400">
|
||||
{{ $t('free_plan_parameters', {storage: config.storageDefaultSpaceFormatted, members: config.teamsDefaultMembers}) }}
|
||||
</b>
|
||||
</div>
|
||||
|
||||
<!--Transactions-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="file-text">
|
||||
{{ $t('transactions') }}
|
||||
</FormLabel>
|
||||
|
||||
<DatatableWrapper
|
||||
class="overflow-x-auto"
|
||||
@init="isLoading = false"
|
||||
:api="'/api/admin/users/' + this.$route.params.id + '/transactions'"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<!--Transaction rows-->
|
||||
<MeteredTransactionRow
|
||||
v-if="config.subscriptionType === 'metered'"
|
||||
:row="row"
|
||||
@showDetail="showTransactionDetail"
|
||||
/>
|
||||
<FixedTransactionRow v-if="config.subscriptionType === 'fixed'" :row="row" />
|
||||
|
||||
<!--Transaction detail-->
|
||||
<MeteredTransactionDetailRow
|
||||
v-if="row.data.attributes.metadata && showedTransactionDetailById === row.data.id"
|
||||
:row="row"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!--Empty page-->
|
||||
<template v-slot:empty-page>
|
||||
<InfoBox style="margin-bottom: 0">
|
||||
<p>
|
||||
{{ $t("user_dont_have_transactions") }}
|
||||
</p>
|
||||
</InfoBox>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MeteredTransactionDetailRow from '../../../../components/Subscription/MeteredTransactionDetailRow'
|
||||
import MeteredTransactionRow from '../../../../components/Subscription/MeteredTransactionRow'
|
||||
import FixedTransactionRow from '../../../../components/Subscription/FixedTransactionRow'
|
||||
import DatatableWrapper from '../../../../components/UI/Table/DatatableWrapper'
|
||||
import FormLabel from '../../../../components/UI/Labels/FormLabel'
|
||||
import PageTab from '../../../../components/Layout/PageTab'
|
||||
import InfoBox from '../../../../components/UI/Others/InfoBox'
|
||||
import UserMeteredSubscription from './UserMeteredSubscription'
|
||||
import UserFixedSubscription from './UserFixedSubscription'
|
||||
import { mapGetters } from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'UserSubscription',
|
||||
props: ['user'],
|
||||
components: {
|
||||
MeteredTransactionDetailRow,
|
||||
UserMeteredSubscription,
|
||||
MeteredTransactionRow,
|
||||
UserFixedSubscription,
|
||||
FixedTransactionRow,
|
||||
DatatableWrapper,
|
||||
FormLabel,
|
||||
InfoBox,
|
||||
PageTab,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
columns() {
|
||||
let filter = {
|
||||
metered: ['user_id'],
|
||||
fixed: ['type', 'user_id'],
|
||||
}
|
||||
|
||||
return this.$store.getters.transactionColumns.filter(
|
||||
(column) => !filter[config.subscriptionType].includes(column.field)
|
||||
)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showedTransactionDetailById: undefined,
|
||||
subscription: undefined,
|
||||
isLoading: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showTransactionDetail(id) {
|
||||
if (this.showedTransactionDetailById === id) this.showedTransactionDetailById = undefined
|
||||
else this.showedTransactionDetailById = id
|
||||
},
|
||||
},
|
||||
created() {
|
||||
axios
|
||||
.get(`/api/subscriptions/admin/users/${this.$route.params.id}/subscription`)
|
||||
.then((response) => {
|
||||
this.subscription = response.data.data
|
||||
|
||||
this.isLoading = false
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response.status === 404) this.isLoading = false
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user