added prettier

This commit is contained in:
Čarodej
2022-02-01 12:21:38 +01:00
parent 5ae875233b
commit b38b532cbe
284 changed files with 25410 additions and 25338 deletions

View File

@@ -1,52 +1,51 @@
<template>
<div>
<!--Page Tab links-->
<div class="card shadow-card z-10" style="padding-bottom: 0; padding-top: 0;">
<CardNavigation :pages="pages" class="-mx-1" />
</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 />
<!--Page Content-->
<router-view />
</div>
</template>
<script>
import CardNavigation from "../../../components/Admin/CardNavigation";
import CardNavigation from '../../../components/Admin/CardNavigation'
export default {
name: 'AppSettings',
components: {
CardNavigation,
},
data() {
return {
pages: [
{
title: this.$t('admin_settings.tabs.others'),
route: 'AppOthers',
},
{
title: this.$t('Login & Registration'),
route: 'AppSignInUp',
},
{
title: this.$t('admin_settings.tabs.appearance'),
route: 'AppAppearance',
},
{
title: this.$t('Homepage'),
route: 'AppIndex',
},
{
title: this.$t('admin_settings.tabs.email'),
route: 'AppEmail',
},
]
}
},
mounted() {
this.$router.push({name: 'AppOthers'})
}
}
export default {
name: 'AppSettings',
components: {
CardNavigation,
},
data() {
return {
pages: [
{
title: this.$t('admin_settings.tabs.others'),
route: 'AppOthers',
},
{
title: this.$t('Login & Registration'),
route: 'AppSignInUp',
},
{
title: this.$t('admin_settings.tabs.appearance'),
route: 'AppAppearance',
},
{
title: this.$t('Homepage'),
route: 'AppIndex',
},
{
title: this.$t('admin_settings.tabs.email'),
route: 'AppEmail',
},
],
}
},
mounted() {
this.$router.push({ name: 'AppOthers' })
},
}
</script>

View File

@@ -1,109 +1,126 @@
<template>
<PageTab :is-loading="isLoading">
<div v-if="app" class="card shadow-card">
<FormLabel>
{{ $t('admin_settings.appearance.section_general') }}
</FormLabel>
<div v-if="app" class="card shadow-card">
<FormLabel>
{{ $t('admin_settings.appearance.section_general') }}
</FormLabel>
<AppInputSwitch :title="$t('color_theme')" :description="$t('color_theme_description')">
<input @input="$updateText('/admin/settings', 'app_color', app.color)" v-model="app.color" :placeholder="$t('admin_settings.appearance.title_plac')" type="color"/>
</AppInputSwitch>
<AppInputSwitch :title="$t('color_theme')" :description="$t('color_theme_description')">
<input @input="$updateText('/admin/settings', 'app_color', app.color)" v-model="app.color" :placeholder="$t('admin_settings.appearance.title_plac')" type="color" />
</AppInputSwitch>
<AppInputText :title="$t('admin_settings.appearance.title')">
<input @input="$updateText('/admin/settings', 'app_title', app.title)" v-model="app.title" :placeholder="$t('admin_settings.appearance.title_plac')" type="text" class="focus-border-theme input-dark"/>
</AppInputText>
<AppInputText :title="$t('admin_settings.appearance.title')">
<input
@input="$updateText('/admin/settings', 'app_title', app.title)"
v-model="app.title"
:placeholder="$t('admin_settings.appearance.title_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('admin_settings.appearance.description')" :is-last="true">
<input @input="$updateText('/admin/settings', 'app_description', app.description)" v-model="app.description" :placeholder="$t('admin_settings.appearance.description_plac')" type="text" class="focus-border-theme input-dark"/>
</AppInputText>
</div>
<div v-if="app" class="card shadow-card">
<FormLabel>
{{ $t('Branding') }}
</FormLabel>
<AppInputText :title="$t('admin_settings.appearance.description')" :is-last="true">
<input
@input="$updateText('/admin/settings', 'app_description', app.description)"
v-model="app.description"
:placeholder="$t('admin_settings.appearance.description_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<div v-if="app" class="card shadow-card">
<FormLabel>
{{ $t('Branding') }}
</FormLabel>
<AppInputText :title="$t('admin_settings.appearance.logo')">
<ImageInput @input="$updateImage('/admin/settings', 'app_logo', app.logo)" :image="$getImage(app.logo)" v-model="app.logo"/>
</AppInputText>
<AppInputText :title="$t('admin_settings.appearance.logo')">
<ImageInput @input="$updateImage('/admin/settings', 'app_logo', app.logo)" :image="$getImage(app.logo)" v-model="app.logo" />
</AppInputText>
<AppInputText :title="$t('admin_settings.appearance.logo_horizontal')">
<ImageInput @input="$updateImage('/admin/settings', 'app_logo_horizontal', app.logo_horizontal)" :image="$getImage(app.logo_horizontal)" v-model="app.logo_horizontal"/>
</AppInputText>
<AppInputText :title="$t('admin_settings.appearance.logo_horizontal')">
<ImageInput
@input="$updateImage('/admin/settings', 'app_logo_horizontal', app.logo_horizontal)"
:image="$getImage(app.logo_horizontal)"
v-model="app.logo_horizontal"
/>
</AppInputText>
<AppInputText :title="$t('admin_settings.appearance.favicon')">
<ImageInput @input="$updateImage('/admin/settings', 'app_favicon', app.favicon)" :image="$getImage(app.favicon)" v-model="app.favicon"/>
</AppInputText>
<AppInputText :title="$t('admin_settings.appearance.favicon')">
<ImageInput @input="$updateImage('/admin/settings', 'app_favicon', app.favicon)" :image="$getImage(app.favicon)" v-model="app.favicon" />
</AppInputText>
<AppInputText :title="$t('og_image')" :description="$t('og_image_description')">
<ImageInput @input="$updateImage('/admin/settings', 'app_og_image', app.og_image)" :image="$getImage(app.og_image)" v-model="app.og_image"/>
</AppInputText>
<AppInputText :title="$t('og_image')" :description="$t('og_image_description')">
<ImageInput @input="$updateImage('/admin/settings', 'app_og_image', app.og_image)" :image="$getImage(app.og_image)" v-model="app.og_image" />
</AppInputText>
<AppInputText :title="$t('app_touch_icon')" :description="$t('app_touch_icon_description')">
<ImageInput @input="$updateImage('/admin/settings', 'app_touch_icon', app.touch_icon)" :image="$getImage(app.touch_icon)" v-model="app.touch_icon"/>
</AppInputText>
</div>
<AppInputText :title="$t('app_touch_icon')" :description="$t('app_touch_icon_description')">
<ImageInput @input="$updateImage('/admin/settings', 'app_touch_icon', app.touch_icon)" :image="$getImage(app.touch_icon)" v-model="app.touch_icon" />
</AppInputText>
</div>
</PageTab>
</template>
<script>
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch";
import AppInputText from "../../../../components/Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import ImageInput from "../../../../components/Others/Forms/ImageInput";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import axios from 'axios'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import AppInputText from '../../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import ImageInput from '../../../../components/Others/Forms/ImageInput'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import { required } from 'vee-validate/dist/rules'
import axios from 'axios'
export default {
name: 'AppAppearance',
components: {
AppInputSwitch,
AppInputText,
ValidationObserver,
ValidationProvider,
PageTabGroup,
SelectInput,
ImageInput,
ButtonBase,
FormLabel,
SetupBox,
required,
PageTab,
InfoBox,
},
data() {
return {
isLoading: true,
app: undefined,
}
},
mounted() {
axios.get('/api/admin/settings', {
export default {
name: 'AppAppearance',
components: {
AppInputSwitch,
AppInputText,
ValidationObserver,
ValidationProvider,
PageTabGroup,
SelectInput,
ImageInput,
ButtonBase,
FormLabel,
SetupBox,
required,
PageTab,
InfoBox,
},
data() {
return {
isLoading: true,
app: undefined,
}
},
mounted() {
axios
.get('/api/admin/settings', {
params: {
column: 'app_title|app_description|app_logo|app_favicon|app_logo_horizontal|app_color|app_og_image|app_touch_icon'
column: 'app_title|app_description|app_logo|app_favicon|app_logo_horizontal|app_color|app_og_image|app_touch_icon',
},
})
.then((response) => {
this.app = {
logo_horizontal: response.data.app_logo_horizontal,
description: response.data.app_description,
favicon: response.data.app_favicon,
title: response.data.app_title,
color: response.data.app_color,
logo: response.data.app_logo,
og_image: response.data.app_og_image,
touch_icon: response.data.app_touch_icon,
}
})
.then(response => {
this.app = {
logo_horizontal: response.data.app_logo_horizontal,
description: response.data.app_description,
favicon: response.data.app_favicon,
title: response.data.app_title,
color: response.data.app_color,
logo: response.data.app_logo,
og_image: response.data.app_og_image,
touch_icon: response.data.app_touch_icon,
}
})
.finally(() => {
this.isLoading = false
})
}
}
.finally(() => {
this.isLoading = false
})
},
}
</script>

View File

@@ -7,32 +7,62 @@
</InfoBox>
<ValidationProvider tag="div" mode="passive" name="Mail Driver" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.email.driver')" :error="errors[0]">
<input v-model="mail.driver" :placeholder="$t('admin_settings.email.driver_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
<input
v-model="mail.driver"
:placeholder="$t('admin_settings.email.driver_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Mail Host" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.email.host')" :error="errors[0]">
<input v-model="mail.host" :placeholder="$t('admin_settings.email.host_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
<input
v-model="mail.host"
:placeholder="$t('admin_settings.email.host_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Mail Port" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.email.port')" :error="errors[0]">
<input v-model="mail.port" :placeholder="$t('admin_settings.email.port_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
<input
v-model="mail.port"
:placeholder="$t('admin_settings.email.port_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Mail Username" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.email.username')" :error="errors[0]">
<input v-model="mail.username" :placeholder="$t('admin_settings.email.username_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
<input
v-model="mail.username"
:placeholder="$t('admin_settings.email.username_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Mail Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.email.password')" :error="errors[0]">
<input v-model="mail.password" :placeholder="$t('admin_settings.email.password_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
<input
v-model="mail.password"
:placeholder="$t('admin_settings.email.password_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Mail Encryption" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.email.encryption')" :error="errors[0]">
<SelectInput v-model="mail.encryption" :options="encryptionList" :placeholder="$t('admin_settings.email.encryption_plac')" :isError="errors[0]"/>
<SelectInput v-model="mail.encryption" :options="encryptionList" :placeholder="$t('admin_settings.email.encryption_plac')" :isError="errors[0]" />
</AppInputText>
</ValidationProvider>
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="theme" class="submit-button">
@@ -43,93 +73,90 @@
</template>
<script>
import AppInputText from "../../../../components/Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import ImageInput from "../../../../components/Others/Forms/ImageInput";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import {events} from '../../../../bus'
import axios from 'axios'
import AppInputText from '../../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import ImageInput from '../../../../components/Others/Forms/ImageInput'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import { required } from 'vee-validate/dist/rules'
import { events } from '../../../../bus'
import axios from 'axios'
export default {
name: 'AppAppearance',
components: {
AppInputText,
ValidationObserver,
ValidationProvider,
PageTabGroup,
SelectInput,
ImageInput,
ButtonBase,
FormLabel,
SetupBox,
required,
PageTab,
InfoBox,
},
data() {
return {
isLoading: false,
isSendingRequest: false,
encryptionList: [
{
label: 'TLS',
value: 'tls',
},
{
label: 'SSL',
value: 'ssl',
},
],
mail: {
driver: '',
host: '',
port: '',
username: '',
password: '',
encryption: '',
}
}
},
methods: {
async EmailSetupSubmit() {
// Validate fields
const isValid = await this.$refs.EmailSetup.validate();
if (!isValid) return;
// Start loading
this.isSendingRequest = true
// Send request to get verify account
axios
.post('/api/admin/settings/email', this.mail)
.then(() => {
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.email_set'),
})
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
.finally(() => {
// End loading
this.isSendingRequest = false
})
export default {
name: 'AppAppearance',
components: {
AppInputText,
ValidationObserver,
ValidationProvider,
PageTabGroup,
SelectInput,
ImageInput,
ButtonBase,
FormLabel,
SetupBox,
required,
PageTab,
InfoBox,
},
data() {
return {
isLoading: false,
isSendingRequest: false,
encryptionList: [
{
label: 'TLS',
value: 'tls',
},
{
label: 'SSL',
value: 'ssl',
},
],
mail: {
driver: '',
host: '',
port: '',
username: '',
password: '',
encryption: '',
},
}
}
},
methods: {
async EmailSetupSubmit() {
// Validate fields
const isValid = await this.$refs.EmailSetup.validate()
if (!isValid) return
// Start loading
this.isSendingRequest = true
// Send request to get verify account
axios
.post('/api/admin/settings/email', this.mail)
.then(() => {
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.email_set'),
})
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
.finally(() => {
// End loading
this.isSendingRequest = false
})
},
},
}
</script>

View File

@@ -1,31 +1,40 @@
<template>
<PageTab :is-loading="isLoading">
<PageTabGroup v-if="app">
<div class="form block-form">
<div class="card shadow-card">
<FormLabel>
{{ $t('Homepage') }}
</FormLabel>
<div class="card shadow-card">
<FormLabel>
{{ $t('Homepage') }}
</FormLabel>
<AppInputSwitch :title="$t('Allow Homepage')" :description="$t('When this is turned on, your visitors can visit your default homepage.')" :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>
<AppInputSwitch :title="$t('Allow Homepage')" :description="$t('When this is turned on, your visitors can visit your default homepage.')" :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">
<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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'header_title', app.header_title)"
v-model="app.header_title"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -33,7 +42,13 @@
<div class="block-wrapper">
<label>Description:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"></textarea>
<textarea
@input="$updateText('/admin/settings', 'header_description', app.header_description)"
rows="2"
v-model="app.header_description"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
></textarea>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -47,25 +62,33 @@
<div class="input-wrapper">
<div class="inline-wrapper">
<div class="switch-label">
<label class="input-label">
Show section:
</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"/>
<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">
<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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'features_title', app.features_title)"
v-model="app.features_title"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -73,7 +96,13 @@
<div class="block-wrapper">
<label>Description:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"></textarea>
<textarea
@input="$updateText('/admin/settings', 'features_description', app.features_description)"
rows="2"
v-model="app.features_description"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
></textarea>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -88,58 +117,97 @@
<div class="input-wrapper">
<div class="inline-wrapper">
<div class="switch-label">
<label class="input-label">
Show section:
</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"/>
<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">
<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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'feature_title_1', app.feature_title_1)"
v-model="app.feature_title_1"
type="text"
:class="{ 'border-red': 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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"></textarea>
<textarea
@input="$updateText('/admin/settings', 'feature_description_1', app.feature_description_1)"
rows="2"
v-model="app.feature_description_1"
:class="{ 'border-red': 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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'feature_title_2', app.feature_title_2)"
v-model="app.feature_title_2"
type="text"
:class="{ 'border-red': 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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"></textarea>
<textarea
@input="$updateText('/admin/settings', 'feature_description_2', app.feature_description_2)"
rows="2"
v-model="app.feature_description_2"
:class="{ 'border-red': 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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'feature_title_3', app.feature_title_3)"
v-model="app.feature_title_3"
type="text"
:class="{ 'border-red': 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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"></textarea>
<textarea
@input="$updateText('/admin/settings', 'feature_description_3', app.feature_description_3)"
rows="2"
v-model="app.feature_description_3"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
></textarea>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -154,23 +222,32 @@
<div class="input-wrapper">
<div class="inline-wrapper">
<div class="switch-label">
<label class="input-label">
Show section:
</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"/>
<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">
<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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'pricing_title', app.pricing_title)"
v-model="app.pricing_title"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -178,7 +255,13 @@
<div class="block-wrapper">
<label>Description:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"></textarea>
<textarea
@input="$updateText('/admin/settings', 'pricing_description', app.pricing_description)"
rows="2"
v-model="app.pricing_description"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
></textarea>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -193,24 +276,32 @@
<div class="input-wrapper">
<div class="inline-wrapper">
<div class="switch-label">
<label class="input-label">
Show section:
</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"/>
<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">
<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" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'get_started_title', app.get_started_title)"
v-model="app.get_started_title"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -218,7 +309,13 @@
<div class="block-wrapper">
<label>Description:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"></textarea>
<textarea
@input="$updateText('/admin/settings', 'get_started_description', app.get_started_description)"
rows="2"
v-model="app.get_started_description"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
></textarea>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -232,7 +329,13 @@
<div class="block-wrapper">
<label>Footer content:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" 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-red': errors[0]}" class="focus-border-theme input-dark"/>
<input
@input="$updateText('/admin/settings', 'footer_content', app.footer_content)"
v-model="app.footer_content"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -243,18 +346,18 @@
</template>
<script>
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch";
import AppInputText from "../../../../components/Admin/AppInputText";
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import AppInputText from '../../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import SwitchInput from "../../../../components/Others/Forms/SwitchInput";
import ImageInput from "../../../../components/Others/Forms/ImageInput";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import SwitchInput from '../../../../components/Others/Forms/SwitchInput'
import ImageInput from '../../../../components/Others/Forms/ImageInput'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import { required } from 'vee-validate/dist/rules'
import axios from 'axios'
import { mapGetters } from 'vuex'
@@ -262,8 +365,8 @@ import { mapGetters } from 'vuex'
export default {
name: 'AppIndex',
components: {
AppInputSwitch,
AppInputText,
AppInputSwitch,
AppInputText,
ValidationObserver,
ValidationProvider,
PageTabGroup,
@@ -275,24 +378,25 @@ export default {
SetupBox,
required,
PageTab,
InfoBox
InfoBox,
},
computed: {
...mapGetters(['config'])
...mapGetters(['config']),
},
data() {
return {
isLoading: true,
app: undefined
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 => {
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),
@@ -313,13 +417,13 @@ export default {
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
footer_content: response.data.footer_content,
}
})
.finally(() => {
this.isLoading = false
})
}
},
}
</script>

View File

@@ -1,247 +1,291 @@
<template>
<PageTab :is-loading="isLoading">
<!--Store & Upload-->
<div class="card shadow-card">
<FormLabel>
{{ $t('Storage & Upload') }}
</FormLabel>
<!--Store & Upload-->
<div class="card shadow-card">
<FormLabel>
{{ $t('Storage & Upload') }}
</FormLabel>
<!--Available only when is not metered billing-->
<div v-if="config.subscriptionType !== 'metered'">
<AppInputSwitch :title="$t('admin_settings.others.storage_limit')" :description="$t('admin_settings.others.storage_limit_help')">
<SwitchInput
@input="$updateText('/admin/settings', 'storage_limitation', app.storageLimitation)"
v-model="app.storageLimitation"
:state="app.storageLimitation"
class="switch"
/>
</AppInputSwitch>
<!--Available only when is not metered billing-->
<div v-if="config.subscriptionType !== 'metered'">
<AppInputSwitch :title="$t('admin_settings.others.storage_limit')" :description="$t('admin_settings.others.storage_limit_help')">
<SwitchInput
@input="$updateText('/admin/settings', 'storage_limitation', app.storageLimitation)"
v-model="app.storageLimitation"
:state="app.storageLimitation"
class="switch"
/>
</AppInputSwitch>
<AppInputText v-if="app.storageLimitation" :title="$t('admin_settings.others.default_storage')">
<input
@input="$updateText('/admin/settings', 'default_max_storage_amount', app.defaultStorage)"
v-model="app.defaultStorage"
min="1"
max="999999999"
:placeholder="$t('admin_settings.others.default_storage_plac')"
type="number"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<AppInputText v-if="app.storageLimitation" :title="$t('admin_settings.others.default_storage')">
<input @input="$updateText('/admin/settings', 'default_max_storage_amount', app.defaultStorage)"
v-model="app.defaultStorage"
min="1"
max="999999999"
:placeholder="$t('admin_settings.others.default_storage_plac')"
type="number"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<AppInputText :title="$t('admin_settings.others.upload_limit')" :description="$t('admin_settings.others.upload_limit_help')">
<input
@input="$updateText('/admin/settings', 'upload_limit', app.uploadLimit, true)"
v-model="app.uploadLimit"
:placeholder="$t('admin_settings.others.upload_limit_plac')"
type="number"
min="0"
step="1"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('admin_settings.others.upload_limit')" :description="$t('admin_settings.others.upload_limit_help')">
<input @input="$updateText('/admin/settings', 'upload_limit', app.uploadLimit, true)" v-model="app.uploadLimit" :placeholder="$t('admin_settings.others.upload_limit_plac')" type="number" min="0" step="1" class="focus-border-theme input-dark" />
</AppInputText>
<AppInputText :title="$t('admin_settings.others.mimetypes_blacklist')" :description="$t('admin_settings.others.mimetypes_blacklist_help')" :is-last="true">
<textarea
rows="2"
@input="$updateText('/admin/settings', 'mimetypes_blacklist', app.mimetypesBlacklist, true)"
v-model="app.mimetypesBlacklist"
:placeholder="$t('admin_settings.others.mimetypes_blacklist_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<AppInputText :title="$t('admin_settings.others.mimetypes_blacklist')" :description="$t('admin_settings.others.mimetypes_blacklist_help')" :is-last="true">
<textarea rows="2" @input="$updateText('/admin/settings', 'mimetypes_blacklist', app.mimetypesBlacklist, true)" v-model="app.mimetypesBlacklist" :placeholder="$t('admin_settings.others.mimetypes_blacklist_plac')" type="text" class="focus-border-theme input-dark" />
</AppInputText>
</div>
<!--Other Settings-->
<div class="card shadow-card">
<FormLabel>
{{ $t('Application') }}
</FormLabel>
<!--Other Settings-->
<div class="card shadow-card">
<FormLabel>
{{ $t('Application') }}
</FormLabel>
<AppInputButton :title="$t('Cache')" :description="$t('Did you change anything in your .env file? Then clear your cache.')">
<ButtonBase @click.native="flushCache" :loading="isFlushingCache" :disabled="isFlushingCache" class="w-full sm:w-auto" button-style="theme">
{{ $t('admin_settings.others.cache_clear') }}
</ButtonBase>
</AppInputButton>
<AppInputButton :title="$t('Cache')" :description="$t('Did you change anything in your .env file? Then clear your cache.')">
<ButtonBase @click.native="flushCache" :loading="isFlushingCache" :disabled="isFlushingCache" class="sm:w-auto w-full" button-style="theme">
{{ $t('admin_settings.others.cache_clear') }}
</ButtonBase>
</AppInputButton>
<AppInputText :title="$t('admin_settings.others.contact_email')">
<input
class="focus-border-theme input-dark"
@input="$updateText('/admin/settings', 'contact_email', app.contactMail)"
v-model="app.contactMail"
:placeholder="$t('admin_settings.others.contact_email_plac')"
type="email"
/>
</AppInputText>
<AppInputText :title="$t('admin_settings.others.contact_email')">
<input class="focus-border-theme input-dark" @input="$updateText('/admin/settings', 'contact_email', app.contactMail)" v-model="app.contactMail" :placeholder="$t('admin_settings.others.contact_email_plac')" type="email" />
</AppInputText>
<AppInputText :title="$t('admin_settings.others.google_analytics')" :is-last="true">
<input
@input="$updateText('/admin/settings', 'google_analytics', app.googleAnalytics, true)"
v-model="app.googleAnalytics"
:placeholder="$t('admin_settings.others.google_analytics_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<AppInputText :title="$t('admin_settings.others.google_analytics')" :is-last="true">
<input @input="$updateText('/admin/settings', 'google_analytics', app.googleAnalytics, true)" v-model="app.googleAnalytics" :placeholder="$t('admin_settings.others.google_analytics_plac')" type="text" class="focus-border-theme input-dark" />
</AppInputText>
</div>
<!-- ReCaptcha -->
<div class="card shadow-card">
<FormLabel icon="shield">
{{ $t('reCaptcha') }}
</FormLabel>
<!-- ReCaptcha -->
<div class="card shadow-card">
<FormLabel icon="shield">
{{ $t('reCaptcha') }}
</FormLabel>
<AppInputSwitch
:title="$t('Allow ReCaptcha')"
:description="$t('ReCaptcha will be allowed on Registration and Contact Us forms.')"
:is-last="!recaptcha.allowedService"
>
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_recaptcha', recaptcha.allowedService)"
v-model="recaptcha.allowedService"
class="switch"
:state="recaptcha.allowedService"
/>
</AppInputSwitch>
<AppInputSwitch :title="$t('Allow ReCaptcha')" :description="$t('ReCaptcha will be allowed on Registration and Contact Us forms.')" :is-last="! recaptcha.allowedService">
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_recaptcha', recaptcha.allowedService)"
v-model="recaptcha.allowedService"
class="switch"
:state="recaptcha.allowedService"
/>
</AppInputSwitch>
<div
v-if="config.isRecaptchaConfigured && recaptcha.allowedService"
@click="recaptcha.isVisibleCredentialsForm = !recaptcha.isVisibleCredentialsForm"
class="flex cursor-pointer items-center"
:class="{ 'mb-4': recaptcha.isVisibleCredentialsForm }"
>
<edit2-icon size="12" class="vue-feather text-theme mr-2" />
<b class="text-xs">{{ $t('Update Your Credentials') }}</b>
</div>
<div v-if="config.isRecaptchaConfigured && recaptcha.allowedService" @click="recaptcha.isVisibleCredentialsForm = !recaptcha.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :class="{'mb-4': recaptcha.isVisibleCredentialsForm}">
<edit2-icon size="12" class="vue-feather text-theme mr-2" />
<b class="text-xs">{{ $t('Update Your Credentials') }}</b>
</div>
<!--Set up recaptcha credentials-->
<ValidationObserver
v-if="(!config.isRecaptchaConfigured || recaptcha.isVisibleCredentialsForm) && recaptcha.allowedService"
@submit.prevent="storeCredentials('recaptcha')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="rounded-xl p-5 shadow-lg"
>
<FormLabel v-if="!config.isRecaptchaConfigured" icon="shield">
{{ $t('Configure Credentials') }}
</FormLabel>
<!--Set up recaptcha credentials-->
<ValidationObserver
v-if="(! config.isRecaptchaConfigured || recaptcha.isVisibleCredentialsForm) && recaptcha.allowedService"
@submit.prevent="storeCredentials('recaptcha')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="p-5 shadow-lg rounded-xl"
>
<FormLabel v-if="! config.isRecaptchaConfigured" icon="shield">
{{ $t('Configure Credentials') }}
</FormLabel>
<ValidationProvider tag="div" mode="passive" name="Site Key" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('Site Key')" :error="errors[0]">
<input
v-model="recaptcha.credentials.client_id"
:placeholder="$t('Paste your Site Key here')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Site Key" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('Site Key')" :error="errors[0]">
<input v-model="recaptcha.credentials.client_id" :placeholder="$t('Paste your Site Key here')" type="text" :class="{'border-red': 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('Secret Key')" :error="errors[0]">
<input
v-model="recaptcha.credentials.client_secret"
:placeholder="$t('Paste your Secret key here')"
type="text"
:class="{ 'border-red': 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('Secret Key')" :error="errors[0]">
<input v-model="recaptcha.credentials.client_secret" :placeholder="$t('Paste your Secret key here')" type="text" :class="{'border-red': 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>
<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/Others/Forms/SwitchInput";
import AppInputButton from "../../../../components/Admin/AppInputButton"
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch"
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import AppInputText from "../../../../components/Admin/AppInputText"
import PageTab from "../../../../components/Others/Layout/PageTab";
import {required} from 'vee-validate/dist/rules'
import {events} from '../../../../bus'
import {mapGetters} from "vuex"
import axios from 'axios'
import { Edit2Icon } from 'vue-feather-icons'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SwitchInput from '../../../../components/Others/Forms/SwitchInput'
import AppInputButton from '../../../../components/Admin/AppInputButton'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import AppInputText from '../../../../components/Admin/AppInputText'
import PageTab from '../../../../components/Others/Layout/PageTab'
import { required } from 'vee-validate/dist/rules'
import { events } from '../../../../bus'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'AppOthers',
components: {
AppInputButton,
ValidationObserver,
ValidationProvider,
AppInputSwitch,
AppInputText,
SwitchInput,
ButtonBase,
Edit2Icon,
FormLabel,
required,
PageTab,
},
computed: {
...mapGetters([
'config',
])
},
data() {
return {
isLoading: true,
isFlushingCache: false,
app: undefined,
recaptcha: {
allowedService: false,
isVisibleCredentialsForm: false,
credentials: {
key: undefined,
secret: undefined,
},
},
}
},
methods: {
async storeCredentials(service) {
export default {
name: 'AppOthers',
components: {
AppInputButton,
ValidationObserver,
ValidationProvider,
AppInputSwitch,
AppInputText,
SwitchInput,
ButtonBase,
Edit2Icon,
FormLabel,
required,
PageTab,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: true,
isFlushingCache: false,
app: undefined,
recaptcha: {
allowedService: false,
isVisibleCredentialsForm: false,
credentials: {
key: undefined,
secret: undefined,
},
},
}
},
methods: {
async storeCredentials(service) {
// Validate fields
const isValid = await this.$refs.credentialsForm.validate()
// Validate fields
const isValid = await this.$refs.credentialsForm.validate();
if (!isValid) return
if (!isValid) return;
// Start loading
this.isLoading = true
// 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)
// 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
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))
},
flushCache() {
this.isFlushingCache = true
// Show toaster
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.credentials_set', {service: service}),
})
})
.catch(error => {
axios
.get('/api/admin/settings/flush-cache')
.then(() => {
events.$emit('toaster', {
type: 'success',
message: 'Your cache was successfully deleted.',
})
})
.finally(() => {
this.isFlushingCache = false
})
},
},
mounted() {
this.recaptcha.allowedService = this.config.allowedRecaptcha
if (error.response.status === 500) {
this.isError = true
this.errorMessage = error.response.data.message
}
})
.finally(() => this.isLoading = false)
},
flushCache() {
axios
.get('/api/admin/settings', {
params: {
column: 'contact_email|google_analytics|default_max_storage_amount|storage_limitation|mimetypes_blacklist|upload_limit',
},
})
.then((response) => {
this.isLoading = false
this.isFlushingCache = true
axios.get('/api/admin/settings/flush-cache')
.then(() => {
events.$emit('toaster', {
type: 'success',
message: 'Your cache was successfully deleted.',
})
})
.finally(() => {
this.isFlushingCache = false
})
}
},
mounted() {
this.recaptcha.allowedService = this.config.allowedRecaptcha
axios.get('/api/admin/settings', {
params: {
column: 'contact_email|google_analytics|default_max_storage_amount|storage_limitation|mimetypes_blacklist|upload_limit'
}
})
.then(response => {
this.isLoading = false
this.app = {
contactMail: response.data.contact_email,
googleAnalytics: response.data.google_analytics,
defaultStorage: response.data.default_max_storage_amount,
storageLimitation: parseInt(response.data.storage_limitation),
mimetypesBlacklist: response.data.mimetypes_blacklist,
uploadLimit: response.data.upload_limit,
}
})
}
}
this.app = {
contactMail: response.data.contact_email,
googleAnalytics: response.data.google_analytics,
defaultStorage: response.data.default_max_storage_amount,
storageLimitation: parseInt(response.data.storage_limitation),
mimetypesBlacklist: response.data.mimetypes_blacklist,
uploadLimit: response.data.upload_limit,
}
})
},
}
</script>

View File

@@ -1,300 +1,343 @@
<template>
<PageTab>
<!--User Login/Registration-->
<div v-if="app" class="card shadow-card">
<FormLabel>
{{ $t('User Login/Registration') }}
</FormLabel>
<!--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('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('admin_settings.others.allow_user_verification_help')" :is-last="true">
<SwitchInput
@input="$updateText('/admin/settings', 'user_verification', app.userVerification)"
v-model="app.userVerification"
class="switch"
:state="app.userVerification"
/>
</AppInputSwitch>
</div>
<AppInputSwitch :title="$t('Require Email Verification')" :description="$t('admin_settings.others.allow_user_verification_help')" :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" />
<!--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>
<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>
<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>
<div v-if="config.isFacebookLoginConfigured && facebook.allowedService" @click="facebook.isVisibleCredentialsForm = !facebook.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :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_login')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="rounded-xl p-5 shadow-lg"
>
<FormLabel v-if="!config.isFacebookLoginConfigured" icon="shield">
{{ $t('Configure Credentials') }}
</FormLabel>
<!--Set up facebook credentials-->
<ValidationObserver
v-if="(! config.isFacebookLoginConfigured || facebook.isVisibleCredentialsForm) && facebook.allowedService"
@submit.prevent="storeCredentials('facebook_login')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="p-5 shadow-lg rounded-xl"
>
<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-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<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-red': 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-red': 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-red': 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>
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit" class="w-full">
{{ $t('Store Credentials') }}
</ButtonBase>
</ValidationObserver>
<!--Google Social Authentication-->
<div class="card shadow-card">
<img :src="$getSocialLogo('google')" alt="Google" class="mb-8 h-7" />
</div>
<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>
<!--Google Social Authentication-->
<div class="card shadow-card">
<img :src="$getSocialLogo('google')" alt="Google" class="mb-8 h-7">
<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>
<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>
<!--Set up Google credentials-->
<ValidationObserver
v-if="(!config.isGoogleLoginConfigured || google.isVisibleCredentialsForm) && google.allowedService"
@submit.prevent="storeCredentials('google_login')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="rounded-xl p-5 shadow-lg"
>
<FormLabel v-if="!config.isGoogleLoginConfigured" icon="shield">
{{ $t('Configure Credentials') }}
</FormLabel>
<div v-if="config.isGoogleLoginConfigured && google.allowedService" @click="google.isVisibleCredentialsForm = !google.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :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>
<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-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<!--Set up Google credentials-->
<ValidationObserver
v-if="(! config.isGoogleLoginConfigured || google.isVisibleCredentialsForm) && google.allowedService"
@submit.prevent="storeCredentials('google_login')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="p-5 shadow-lg rounded-xl"
>
<FormLabel v-if="! config.isGoogleLoginConfigured" icon="shield">
{{ $t('Configure Credentials') }}
</FormLabel>
<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-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<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-red': 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>
<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-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Github Social Authentication-->
<div class="card shadow-card">
<img :src="$getSocialLogo('github')" alt="Github" class="mb-8 h-5" />
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit" class="w-full">
{{ $t('Store Credentials') }}
</ButtonBase>
</ValidationObserver>
<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>
</div>
<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>
<!--Github Social Authentication-->
<div class="card shadow-card">
<img :src="$getSocialLogo('github')" alt="Github" class="mb-8 h-5">
<!--Set up github credentials-->
<ValidationObserver
v-if="(!config.isGithubLoginConfigured || github.isVisibleCredentialsForm) && github.allowedService"
@submit.prevent="storeCredentials('github_login')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="rounded-xl p-5 shadow-lg"
>
<FormLabel v-if="!config.isGithubLoginConfigured" icon="shield">
{{ $t('Configure Credentials') }}
</FormLabel>
<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>
<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-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<div v-if="config.isGithubLoginConfigured && github.allowedService" @click="github.isVisibleCredentialsForm = !github.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :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>
<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-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<!--Set up github credentials-->
<ValidationObserver
v-if="(! config.isGithubLoginConfigured || github.isVisibleCredentialsForm) && github.allowedService"
@submit.prevent="storeCredentials('github_login')"
ref="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="p-5 shadow-lg rounded-xl"
>
<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-red': 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-red': 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>
<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/Others/Forms/SwitchInput";
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch"
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import AppInputText from "../../../../components/Admin/AppInputText"
import PageTab from "../../../../components/Others/Layout/PageTab";
import {required} from 'vee-validate/dist/rules'
import {events} from '../../../../bus'
import {mapGetters} from "vuex"
import axios from 'axios'
import { Edit2Icon } from 'vue-feather-icons'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SwitchInput from '../../../../components/Others/Forms/SwitchInput'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import AppInputText from '../../../../components/Admin/AppInputText'
import PageTab from '../../../../components/Others/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: {
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: {
async storeCredentials(service) {
export default {
name: 'SignInUp',
components: {
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: {
async storeCredentials(service) {
// Validate fields
const isValid = await this.$refs.credentialsForm.validate()
// Validate fields
const isValid = await this.$refs.credentialsForm.validate();
if (!isValid) return
if (!isValid) return;
// Start loading
this.isLoading = true
// 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)
// 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
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
// 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
}
}
this.app.userRegistration = this.config.userRegistration
this.app.userVerification = this.config.userVerification
},
}
</script>

View File

@@ -1,145 +1,139 @@
<template>
<div id="single-page">
<div id="page-content" v-if="! isLoading && data">
<div id="page-content" v-if="!isLoading && data">
<!--Headline-->
<div v-if="config.isAdminVueFileManagerBar" class="mb-4 hidden justify-between md:mb-6 md:block md:flex">
<!--VueFileManager logo-->
<a href="https://vuefilemanager.com" target="_blank">
<img src="/assets/images/vuefilemanager-horizontal-logo.svg" alt="VueFileManager" class="light-mode" />
</a>
<!--Headline-->
<div v-if="config.isAdminVueFileManagerBar" class="md:flex justify-between md:mb-6 mb-4 md:block hidden">
<!--VueFileManager logo-->
<a href="https://vuefilemanager.com" target="_blank">
<img src="/assets/images/vuefilemanager-horizontal-logo.svg" alt="VueFileManager" class="light-mode">
</a>
<!--App Info-->
<div class="flex items-center md:mt-0 mt-4">
<a href="https://gist.github.com/MakingCG/9c07f8af392081ae5d5290d920a79b5d" target="_blank" class="inline-block mr-4">
<span class="font-bold text-sm">
{{ $t('admin_page_dashboard.version') }}:
</span>
<!--App Info-->
<div class="mt-4 flex items-center md:mt-0">
<a href="https://gist.github.com/MakingCG/9c07f8af392081ae5d5290d920a79b5d" target="_blank" class="mr-4 inline-block">
<span class="text-sm font-bold"> {{ $t('admin_page_dashboard.version') }}: </span>
<ColorLabel color="purple">
{{ data.app.version }}
</ColorLabel>
</a>
<a href="https://codecanyon.net/item/vue-file-manager-with-laravel-backend/25815986" target="_blank" class="inline-block mr-4">
<span class="font-bold text-sm">
{{ $t('admin_page_dashboard.license') }}:
</span>
<a href="https://codecanyon.net/item/vue-file-manager-with-laravel-backend/25815986" target="_blank" class="mr-4 inline-block">
<span class="text-sm font-bold"> {{ $t('admin_page_dashboard.license') }}: </span>
<ColorLabel color="purple">
{{ data.app.license }}
</ColorLabel>
</a>
<a href="https://bit.ly/VueFileManager-survey" target="_blank" class="items-center inline-block rounded-lg py-1.5 px-3 ml-8 bg-theme-100 md:flex hidden">
<thumbs-up-icon size="15" class="vue-feather text-theme mr-2.5"/>
<span class="font-bold text-sm text-theme">
<a href="https://bit.ly/VueFileManager-survey" target="_blank" class="bg-theme-100 ml-8 inline-block hidden items-center rounded-lg py-1.5 px-3 md:flex">
<thumbs-up-icon size="15" class="vue-feather text-theme mr-2.5" />
<span class="text-theme text-sm font-bold">
{{ $t('Write a Feedback') }}
</span>
</a>
</div>
</div>
<!--Metric widgets-->
<div class="md:flex md:space-x-6 md:mb-6 mb-2">
<div class="w-full md:mb-0 mb-4 card shadow-card">
<FormLabel icon="users">
{{ $t('Total Users') }}
</FormLabel>
<!--Metric widgets-->
<div class="mb-2 md:mb-6 md:flex md:space-x-6">
<div class="card mb-4 w-full shadow-card md:mb-0">
<FormLabel icon="users">
{{ $t('Total Users') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ data.users.total }}
</b>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ data.users.total }}
</b>
<router-link :to="{name: 'Users'}" class="flex items-center mt-6">
<span class="text-xs font-bold mr-2 whitespace-nowrap">
{{ $t('admin_page_dashboard.w_total_space.link') }}
</span>
<chevron-right-icon size="16" class="text-theme vue-feather"/>
</router-link>
</div>
<div class="w-full md:mb-0 mb-4 card shadow-card">
<FormLabel icon="hard-drive">
{{ $t('Total Storage') }}
</FormLabel>
<router-link :to="{ name: 'Users' }" class="mt-6 flex items-center">
<span class="mr-2 whitespace-nowrap text-xs font-bold">
{{ $t('admin_page_dashboard.w_total_space.link') }}
</span>
<chevron-right-icon size="16" class="text-theme vue-feather" />
</router-link>
</div>
<div class="card mb-4 w-full shadow-card md:mb-0">
<FormLabel icon="hard-drive">
{{ $t('Total Storage') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ data.disk.used }}
</b>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ data.disk.used }}
</b>
<router-link :to="{name: 'Users'}" class="flex items-center mt-6">
<span class="text-xs font-bold mr-2 whitespace-nowrap">
{{ $t('admin_page_dashboard.w_total_space.link') }}
</span>
<chevron-right-icon size="16" class="text-theme vue-feather"/>
</router-link>
</div>
<div v-if="config.subscriptionType !== 'none'" class="w-full md:mb-0 mb-4 card shadow-card">
<FormLabel icon="dollar">
{{ $t('Earnings') }}
</FormLabel>
<router-link :to="{ name: 'Users' }" class="mt-6 flex items-center">
<span class="mr-2 whitespace-nowrap text-xs font-bold">
{{ $t('admin_page_dashboard.w_total_space.link') }}
</span>
<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="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ data.app.earnings }}
</b>
<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="flex items-center mt-6">
<span class="text-xs font-bold mr-2 whitespace-nowrap">
{{ $t('Show all transactions') }}
</span>
<chevron-right-icon size="16" class="text-theme vue-feather"/>
</router-link>
</div>
<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-->
<div class="card shadow-card md:mb-6 mb-4">
<FormLabel icon="hard-drive">
{{ $t('Upload') }}
</FormLabel>
<!--Upload bandwidth widgets-->
<div class="card mb-4 shadow-card md:mb-6">
<FormLabel icon="hard-drive">
{{ $t('Upload') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ data.disk.upload.total }}
</b>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ data.disk.upload.total }}
</b>
<b class="mb-3 block text-sm text-gray-400 mb-2">
{{ $t('In last 45 days') }}
</b>
<b class="mb-3 mb-2 block text-sm text-gray-400">
{{ $t('In last 45 days') }}
</b>
<BarChart :data="data.disk.upload.records" />
</div>
<BarChart :data="data.disk.upload.records" />
</div>
<!--Download bandwidth widgets-->
<div class="card shadow-card md:mb-6 mb-4">
<FormLabel icon="hard-drive">
{{ $t('Download') }}
</FormLabel>
<!--Download bandwidth widgets-->
<div class="card mb-4 shadow-card md:mb-6">
<FormLabel icon="hard-drive">
{{ $t('Download') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ data.disk.download.total }}
</b>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ data.disk.download.total }}
</b>
<b class="mb-3 block text-sm text-gray-400 mb-5">
{{ $t('In last 45 days') }}
</b>
<b class="mb-3 mb-5 block text-sm text-gray-400">
{{ $t('In last 45 days') }}
</b>
<BarChart :data="data.disk.download.records" />
</div>
<BarChart :data="data.disk.download.records" />
</div>
<!--Latest registration widgets-->
<div class="card shadow-card md:mb-6 mb-4">
<FormLabel icon="users">
{{ $t('Latest Registrations') }}
</FormLabel>
<!--Latest registration widgets-->
<div class="card mb-4 shadow-card md:mb-6">
<FormLabel icon="users">
{{ $t('Latest Registrations') }}
</FormLabel>
<WidgetLatestRegistrations />
</div>
<WidgetLatestRegistrations />
</div>
<!--Latest transactions widgets-->
<div v-if="['fixed', 'metered'].includes(this.config.subscriptionType)" class="card shadow-card md:mb-6 mb-4">
<FormLabel icon="dollar">
{{ $t('Latest Transactions') }}
</FormLabel>
<!--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>
<WidgetLatestTransactions />
</div>
</div>
<div id="loader" v-if="isLoading">
<Spinner></Spinner>
@@ -148,49 +142,48 @@
</template>
<script>
import WidgetLatestRegistrations from "../../components/Admin/WidgetLatestRegistrations";
import ColorLabel from "../../components/Others/ColorLabel";
import {ChevronRightIcon, ThumbsUpIcon} from "vue-feather-icons"
import WidgetWrapper from "../../components/Admin/WidgetWrapper"
import Spinner from "../../components/FilesView/Spinner";
import FormLabel from "../../components/Others/Forms/FormLabel"
import BarChart from "../../components/UI/BarChart"
import { mapGetters } from 'vuex'
import axios from 'axios'
import WidgetLatestTransactions from "../../components/Admin/WidgetLatestTransactions";
import WidgetLatestRegistrations from '../../components/Admin/WidgetLatestRegistrations'
import ColorLabel from '../../components/Others/ColorLabel'
import { ChevronRightIcon, ThumbsUpIcon } from 'vue-feather-icons'
import WidgetWrapper from '../../components/Admin/WidgetWrapper'
import Spinner from '../../components/FilesView/Spinner'
import FormLabel from '../../components/Others/Forms/FormLabel'
import BarChart from '../../components/UI/BarChart'
import { mapGetters } from 'vuex'
import axios from 'axios'
import WidgetLatestTransactions from '../../components/Admin/WidgetLatestTransactions'
export default {
name: 'Dashboard',
components: {
WidgetLatestTransactions,
WidgetLatestRegistrations,
ChevronRightIcon,
WidgetWrapper,
ThumbsUpIcon,
ColorLabel,
FormLabel,
BarChart,
Spinner,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isLoading: false,
data: undefined,
}
},
created() {
axios.get('/api/admin/dashboard')
.then(response => {
this.data = response.data
})
.finally(() => {
this.isLoading = false
})
export default {
name: 'Dashboard',
components: {
WidgetLatestTransactions,
WidgetLatestRegistrations,
ChevronRightIcon,
WidgetWrapper,
ThumbsUpIcon,
ColorLabel,
FormLabel,
BarChart,
Spinner,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
data: undefined,
}
}
},
created() {
axios
.get('/api/admin/dashboard')
.then((response) => {
this.data = response.data
})
.finally(() => {
this.isLoading = false
})
},
}
</script>

View File

@@ -1,81 +1,72 @@
<template>
<div>
<!--Datatable-->
<DatatableWrapper
v-if="! config.isEmptyTransactions" class="card shadow-card overflow-x-auto"
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" />
<!--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" />
<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>
<!--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 h-full">
<div class="text-center">
<img class="w-28 inline-block mb-6" src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f9ee.svg" alt="transaction">
<h1 class="text-2xl font-bold mb-1">
{{ $t("There is Nothing") }}
</h1>
<p class="text-sm text-gray-600">
{{ $t('All your transactions will be visible here') }}
</p>
</div>
</div>
<!--Empty State-->
<div v-if="config.isEmptyTransactions" class="flex h-full items-center justify-center">
<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('All your transactions will be visible 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/FilesView/MemberAvatar"
import DatatableWrapper from "../../components/Others/Tables/DatatableWrapper";
import ColorLabel from "../../components/Others/ColorLabel";
import {mapGetters} from 'vuex'
import FixedTransactionRow from '../../components/Subscription/FixedTransactionRow'
import MeteredTransactionDetailRow from '../../components/Subscription/MeteredTransactionDetailRow'
import MeteredTransactionRow from '../../components/Subscription/MeteredTransactionRow'
import MemberAvatar from '../../components/FilesView/MemberAvatar'
import DatatableWrapper from '../../components/Others/Tables/DatatableWrapper'
import ColorLabel from '../../components/Others/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))
}
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
}
}
}
return this.$store.getters.transactionColumns
},
},
data() {
return {
showedTransactionDetailById: undefined,
}
},
methods: {
showTransactionDetail(id) {
if (this.showedTransactionDetailById === id) this.showedTransactionDetailById = undefined
else this.showedTransactionDetailById = id
},
},
}
</script>

View File

@@ -1,269 +1,287 @@
<template>
<div>
<!--Mobile language navigation-->
<div v-if="languages" id="card-navigation" style="height: 62px" class="md:hidden block mb-24">
<div
class="bg-white z-20 sm:pt-5 pt-3"
:class="{
'fixed top-0 left-0 right-0 px-6 rounded-none backdrop-filter backdrop-blur-lg dark:bg-dark-foreground bg-white bg-opacity-70 z-10': fixedNav,
'card shadow-card py-0 sticky top-0 z-10 md:hidden block': ! fixedNav
}"
>
<SearchInput v-model="query" @reset-query="query = ''"/>
<div>
<!--Mobile language navigation-->
<div v-if="languages" id="card-navigation" style="height: 62px" class="mb-24 block md:hidden">
<div
class="z-20 bg-white pt-3 sm:pt-5"
:class="{
'fixed top-0 left-0 right-0 z-10 rounded-none bg-white bg-opacity-70 px-6 backdrop-blur-lg backdrop-filter dark:bg-dark-foreground': fixedNav,
'card sticky top-0 z-10 block py-0 shadow-card md:hidden': !fixedNav,
}"
>
<SearchInput v-model="query" @reset-query="query = ''" />
<div class="flex items-center">
<!--List of languages-->
<div
@click="getLanguage(language)"
v-for="language in languages"
:key="language.data.id"
class="inline-block text-sm font-bold px-4 py-5 border-b-2 border-transparent border-bottom-theme"
:class="{'text-theme router-link-active': selectedLanguage && selectedLanguage.data.attributes.locale === language.data.attributes.locale, 'text-gray-600': !selectedLanguage && selectedLanguage.data.attributes.locale !== language.data.attributes.locale}"
>
{{ language.data.attributes.name }}
</div>
<div class="flex items-center">
<!--List of languages-->
<div
@click="getLanguage(language)"
v-for="language in languages"
:key="language.data.id"
class="border-bottom-theme inline-block border-b-2 border-transparent px-4 py-5 text-sm font-bold"
:class="{
'text-theme router-link-active': selectedLanguage && selectedLanguage.data.attributes.locale === language.data.attributes.locale,
'text-gray-600': !selectedLanguage && selectedLanguage.data.attributes.locale !== language.data.attributes.locale,
}"
>
{{ language.data.attributes.name }}
</div>
<!--Add new language-->
<div @click="createLanguage" class="ml-2 cursor-pointer">
<plus-icon size="14" class="vue-feather text-gray-400"/>
</div>
</div>
</div>
<!--Add new language-->
<div @click="createLanguage" class="ml-2 cursor-pointer">
<plus-icon size="14" class="vue-feather text-gray-400" />
</div>
</div>
</div>
</div>
</div>
<div v-if="languages" class="flex md:space-x-6">
<!--Sidebar-->
<div class="hidden md:block">
<div class="card sticky top-0 shadow-card">
<label class="mb-2 text-xs font-bold text-gray-400">
{{ $t('languages') }}
</label>
<div v-if="languages" class="flex md:space-x-6">
<!-- Languages -->
<div
@click="getLanguage(language)"
v-for="language in languages"
:key="language.data.id"
class="group flex cursor-pointer items-center justify-between py-2 pr-4"
>
<label
class="text-base font-bold"
:class="{
'text-theme': selectedLanguage && selectedLanguage.data.attributes.locale === language.data.attributes.locale,
}"
>
{{ language.data.attributes.name }}
</label>
<x-icon v-if="language.data.attributes.locale !== 'en'" @click.stop="deleteLanguage(language)" class="opacity-0 group-hover:opacity-100" size="12" />
</div>
<!--Sidebar-->
<div class="md:block hidden">
<div class="card shadow-card sticky top-0">
<label class="mb-2 text-xs text-gray-400 font-bold">
{{ $t('languages') }}
</label>
<!-- Create Language button -->
<MobileActionButton @click.native="createLanguage" icon="plus" class="mt-5 whitespace-nowrap">
{{ $t('add_language') }}
</MobileActionButton>
</div>
</div>
<!-- Languages -->
<div @click="getLanguage(language)" v-for="language in languages" :key="language.data.id" class="flex items-center justify-between cursor-pointer py-2 pr-4 group">
<label class="font-bold text-base" :class="{'text-theme': selectedLanguage && selectedLanguage.data.attributes.locale === language.data.attributes.locale}">
{{ language.data.attributes.name }}
</label>
<x-icon
v-if="language.data.attributes.locale !== 'en'"
@click.stop="deleteLanguage(language)"
class="group-hover:opacity-100 opacity-0"
size="12"
/>
</div>
<!--Content-->
<div class="dynamic-content">
<Spinner v-if="!selectedLanguage" class="spinner" />
<!-- Create Language button -->
<MobileActionButton @click.native="createLanguage" icon="plus" class="mt-5 whitespace-nowrap">
{{ $t('add_language') }}
</MobileActionButton>
</div>
</div>
<div v-if="selectedLanguage">
<!--Language Settings-->
<div v-if="!isSearching" class="card shadow-card">
<FormLabel icon="settings">
{{ $t('language_settings') }}
</FormLabel>
<!--Content-->
<div class="dynamic-content">
<ValidationProvider tag="div" mode="passive" name="Language name" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('language_name')" :error="errors[0]">
<input
@input="$updateText(`/admin/languages/${selectedLanguage.data.id}`, 'name', selectedLanguage.data.attributes.name)"
v-model="selectedLanguage.data.attributes.name"
:placeholder="$t('admin_settings.appearance.description_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<Spinner v-if="! selectedLanguage" class="spinner" />
<AppInputSwitch
:title="$t('set_as_default_language')"
:description="$t('If this language is set as default, app will appear in this language for all users.')"
:is-last="true"
>
<SwitchInput
@input="setDefaultLanguage"
class="switch"
:class="{
'disable-switch': selectedLanguage.data.attributes.locale === this.defaultLanguageLocale,
}"
:state="selectedLanguage.data.attributes.locale === this.defaultLanguageLocale"
/>
</AppInputSwitch>
</div>
<div v-if="selectedLanguage">
<div v-if="selectedLanguage" class="card shadow-card">
<!--Translations-->
<FormLabel>
{{ $t('edit_translations') }}
</FormLabel>
<!--Language Settings-->
<div v-if="! isSearching" class="card shadow-card">
<FormLabel icon="settings">
{{ $t('language_settings') }}
</FormLabel>
<InfoBox>
<p>
Please preserve in your translations special string variables defined in format as
<b class="text-theme">:variable</b> or <b class="text-theme">{variable}</b>.
</p>
</InfoBox>
<ValidationProvider tag="div" mode="passive" name="Language name" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('language_name')" :error="errors[0]">
<input @input="$updateText(`/admin/languages/${selectedLanguage.data.id}`, 'name', selectedLanguage.data.attributes.name)" v-model="selectedLanguage.data.attributes.name" :placeholder="$t('admin_settings.appearance.description_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Inline Search for mobile-->
<div class="sticky top-0 z-10 mb-8 hidden md:block">
<SearchInput v-model="query" @reset-query="query = ''" />
</div>
<AppInputSwitch :title="$t('set_as_default_language')" :description="$t('If this language is set as default, app will appear in this language for all users.')" :is-last="true">
<SwitchInput
@input="setDefaultLanguage"
class="switch"
:class="{'disable-switch': selectedLanguage.data.attributes.locale === this.defaultLanguageLocale }"
:state="selectedLanguage.data.attributes.locale === this.defaultLanguageLocale"
/>
</AppInputSwitch>
</div>
<ValidationProvider tag="div" name="Language string" rules="required" v-slot="{ errors }">
<AppInputText :title="referenceTranslations[key]" :error="errors[0]" v-for="(translation, key) in translationList" :key="key">
<textarea
v-model="selectedLanguage.data.attributes.translations[key]"
@input="$updateText(`/admin/languages/${selectedLanguage.data.id}/strings`, key, selectedLanguage.data.attributes.translations[key])"
:rows="selectedLanguage.data.attributes.translations[key].length >= 80 ? 3 : 1"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
></textarea>
</AppInputText>
</ValidationProvider>
</div>
</div>
</div>
</div>
<div v-if="selectedLanguage" class="card shadow-card">
<!--Translations-->
<FormLabel>
{{ $t('edit_translations') }}
</FormLabel>
<InfoBox>
<p>Please preserve in your translations special string variables defined in format as <b class="text-theme">:variable</b> or <b class="text-theme">{variable}</b>.</p>
</InfoBox>
<!--Inline Search for mobile-->
<div class="sticky top-0 z-10 mb-8 md:block hidden">
<SearchInput v-model="query" @reset-query="query = ''" />
</div>
<ValidationProvider tag="div" name="Language string" rules="required" v-slot="{ errors }">
<AppInputText :title="referenceTranslations[key]" :error="errors[0]" v-for="(translation, key) in translationList" :key="key">
<textarea
v-model="selectedLanguage.data.attributes.translations[key]"
@input="$updateText(`/admin/languages/${selectedLanguage.data.id}/strings`, key, selectedLanguage.data.attributes.translations[key])"
:rows="selectedLanguage.data.attributes.translations[key].length >= 80 ? 3 : 1"
class="focus-border-theme input-dark"
:class="{'border-red': errors[0]}"
></textarea>
</AppInputText>
</ValidationProvider>
</div>
</div>
</div>
</div>
<Spinner v-if="! languages" />
</div>
<Spinner v-if="!languages" />
</div>
</template>
<script>
import AppInputSwitch from "../../../components/Admin/AppInputSwitch";
import AppInputText from "../../../components/Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import MobileActionButton from "../../../components/FilesView/MobileActionButton";
import SwitchInput from "../../../components/Others/Forms/SwitchInput";
import SearchInput from "../../../components/Others/Forms/SearchInput";
import FormLabel from "../../../components/Others/Forms/FormLabel";
import MobileHeader from "../../../components/Mobile/MobileHeader";
import ButtonBase from "../../../components/FilesView/ButtonBase";
import InfoBox from "../../../components/Others/Forms/InfoBox";
import PageHeader from "../../../components/Others/PageHeader";
import Spinner from "../../../components/FilesView/Spinner";
import {PlusIcon, XIcon} from 'vue-feather-icons'
import {debounce, omitBy} from 'lodash'
import {events} from '../../../bus'
import AppInputSwitch from '../../../components/Admin/AppInputSwitch'
import AppInputText from '../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import MobileActionButton from '../../../components/FilesView/MobileActionButton'
import SwitchInput from '../../../components/Others/Forms/SwitchInput'
import SearchInput from '../../../components/Others/Forms/SearchInput'
import FormLabel from '../../../components/Others/Forms/FormLabel'
import MobileHeader from '../../../components/Mobile/MobileHeader'
import ButtonBase from '../../../components/FilesView/ButtonBase'
import InfoBox from '../../../components/Others/Forms/InfoBox'
import PageHeader from '../../../components/Others/PageHeader'
import Spinner from '../../../components/FilesView/Spinner'
import { PlusIcon, XIcon } from 'vue-feather-icons'
import { debounce, omitBy } from 'lodash'
import { events } from '../../../bus'
export default {
name: 'Language',
components: {
ValidationProvider,
ValidationObserver,
MobileActionButton,
AppInputSwitch,
AppInputText,
MobileHeader,
SearchInput,
SwitchInput,
ButtonBase,
PageHeader,
FormLabel,
PlusIcon,
InfoBox,
Spinner,
XIcon
},
data() {
return {
searchedTranslationResults: undefined,
referenceTranslations: undefined,
export default {
name: 'Language',
components: {
ValidationProvider,
ValidationObserver,
MobileActionButton,
AppInputSwitch,
AppInputText,
MobileHeader,
SearchInput,
SwitchInput,
ButtonBase,
PageHeader,
FormLabel,
PlusIcon,
InfoBox,
Spinner,
XIcon,
},
data() {
return {
searchedTranslationResults: undefined,
referenceTranslations: undefined,
defaultLanguageLocale: undefined,
selectedLanguage: undefined,
languages: undefined,
query: '',
defaultLanguageLocale: undefined,
selectedLanguage: undefined,
languages: undefined,
query: '',
fixedNav: false,
}
},
watch: {
query: debounce(function (val) {
this.searchedTranslationResults = omitBy(this.selectedLanguage.data.attributes.translations, string => {
return !string.toLowerCase().includes(val.toLowerCase())
})
var container = document.getElementById('single-page')
container.scrollTop = 0
}, 300),
},
computed: {
isSearching() {
return this.searchedTranslationResults && this.query !== ''
},
translationList() {
return this.isSearching
? this.searchedTranslationResults
: this.selectedLanguage.data.attributes.translations
}
},
methods: {
setDefaultLanguage() {
this.$updateText('/admin/settings', 'language', this.selectedLanguage.data.attributes.locale)
this.defaultLanguageLocale = this.selectedLanguage.data.attributes.locale
setTimeout(() => location.reload(), 500)
},
getLanguages() {
axios
.get('/api/admin/languages')
.then(response => {
this.languages = response.data.data
this.referenceTranslations = response.data.meta.reference_translations
this.selectedLanguage = response.data.meta.current_language
this.defaultLanguageLocale = response.data.meta.current_language.data.attributes.locale
})
.catch(() => {
this.$isSomethingWrong()
})
},
getLanguage(language) {
this.selectedLanguage = undefined
axios
.get(`/api/admin/languages/${language.data.id}`)
.then(response => {
this.selectedLanguage = response.data
})
.catch(() => {
this.$isSomethingWrong()
})
},
deleteLanguage(language) {
events.$emit('confirm:open', {
title: `Delete "${language.data.attributes.name}" language?`,
message: 'Your language will be permanently deleted.',
buttonColor: 'danger-solid',
action: {
id: language.data.id,
operation: 'delete-language'
}
})
},
createLanguage() {
events.$emit('popup:open', {name: 'create-language'})
},
},
mounted() {
this.getLanguages()
events.$on('reload:languages', () => this.getLanguages())
events.$on('action:confirmed', data => {
if (data.operation === 'delete-language')
axios.delete(`/api/admin/languages/${data.id}`)
.then(() => this.getLanguages())
.catch(() => this.$isSomethingWrong())
fixedNav: false,
}
},
watch: {
query: debounce(function (val) {
this.searchedTranslationResults = omitBy(this.selectedLanguage.data.attributes.translations, (string) => {
return !string.toLowerCase().includes(val.toLowerCase())
})
// Handle fixed mobile navigation
window.addEventListener("scroll", () => {
let card = document.getElementById('card-navigation')
var container = document.getElementById('single-page')
this.fixedNav = card.getBoundingClientRect().top < 0;
});
container.scrollTop = 0
}, 300),
},
computed: {
isSearching() {
return this.searchedTranslationResults && this.query !== ''
},
destroyed() {
events.$off('action:confirmed')
translationList() {
return this.isSearching ? this.searchedTranslationResults : this.selectedLanguage.data.attributes.translations
},
}
},
methods: {
setDefaultLanguage() {
this.$updateText('/admin/settings', 'language', this.selectedLanguage.data.attributes.locale)
this.defaultLanguageLocale = this.selectedLanguage.data.attributes.locale
setTimeout(() => location.reload(), 500)
},
getLanguages() {
axios
.get('/api/admin/languages')
.then((response) => {
this.languages = response.data.data
this.referenceTranslations = response.data.meta.reference_translations
this.selectedLanguage = response.data.meta.current_language
this.defaultLanguageLocale = response.data.meta.current_language.data.attributes.locale
})
.catch(() => {
this.$isSomethingWrong()
})
},
getLanguage(language) {
this.selectedLanguage = undefined
axios
.get(`/api/admin/languages/${language.data.id}`)
.then((response) => {
this.selectedLanguage = response.data
})
.catch(() => {
this.$isSomethingWrong()
})
},
deleteLanguage(language) {
events.$emit('confirm:open', {
title: `Delete "${language.data.attributes.name}" language?`,
message: 'Your language will be permanently deleted.',
buttonColor: 'danger-solid',
action: {
id: language.data.id,
operation: 'delete-language',
},
})
},
createLanguage() {
events.$emit('popup:open', { name: 'create-language' })
},
},
mounted() {
this.getLanguages()
events.$on('reload:languages', () => this.getLanguages())
events.$on('action:confirmed', (data) => {
if (data.operation === 'delete-language')
axios
.delete(`/api/admin/languages/${data.id}`)
.then(() => this.getLanguages())
.catch(() => this.$isSomethingWrong())
})
// Handle fixed mobile navigation
window.addEventListener('scroll', () => {
let card = document.getElementById('card-navigation')
this.fixedNav = card.getBoundingClientRect().top < 0
})
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -1,93 +1,113 @@
<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="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-5 md:pr-1 pr-3">
<router-link :to="{name: 'PageEdit', params: {slug: row.data.attributes.slug}}" class="text-sm font-bold cursor-pointer" tag="div">
{{ row.data.attributes.title }}
</router-link>
</td>
<td class="md:px-1 px-3">
<span class="text-sm font-bold">
{{ row.data.attributes.slug }}
</span>
</td>
<td class="md:px-1 px-3">
<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="md:pl-1 pl-3 text-right">
<div class="flex space-x-2 w-full justify-end">
<router-link class="flex items-center justify-center w-8 h-8 rounded-md dark:bg-2x-dark-foreground hover:bg-green-100 bg-light-background transition-colors" :to="{name: 'PageEdit', params: {slug: row.data.attributes.slug}}">
<Edit2Icon size="15" class="opacity-75" />
</router-link>
</div>
</td>
</tr>
</template>
</DatatableWrapper>
</div>
</div>
<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/Others/Tables/DatatableWrapper";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import EmptyPageContent from "../../components/Others/EmptyPageContent";
import SwitchInput from "../../components/Others/Forms/SwitchInput";
import MobileHeader from "../../components/Mobile/MobileHeader";
import SectionTitle from "../../components/Others/SectionTitle";
import ButtonBase from "../../components/FilesView/ButtonBase";
import {Trash2Icon, Edit2Icon} from "vue-feather-icons";
import PageHeader from "../../components/Others/PageHeader";
import ColorLabel from "../../components/Others/ColorLabel";
import Spinner from "../../components/FilesView/Spinner";
import axios from 'axios'
import DatatableWrapper from '../../components/Others/Tables/DatatableWrapper'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import EmptyPageContent from '../../components/Others/EmptyPageContent'
import SwitchInput from '../../components/Others/Forms/SwitchInput'
import MobileHeader from '../../components/Mobile/MobileHeader'
import SectionTitle from '../../components/Others/SectionTitle'
import ButtonBase from '../../components/FilesView/ButtonBase'
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
import PageHeader from '../../components/Others/PageHeader'
import ColorLabel from '../../components/Others/ColorLabel'
import Spinner from '../../components/FilesView/Spinner'
import axios from 'axios'
export default {
name: 'Pages',
components: {
MobileActionButton,
EmptyPageContent,
DatatableWrapper,
SectionTitle,
MobileHeader,
SwitchInput,
Trash2Icon,
PageHeader,
ButtonBase,
ColorLabel,
Edit2Icon,
Spinner,
},
data() {
return {
isLoading: true,
columns: [
{
label: this.$t('admin_pages.table.page'),
field: 'title',
sortable: true
},
{
label: this.$t('admin_pages.table.slug'),
field: 'slug',
sortable: true
},
{
label: this.$t('admin_pages.table.status'),
field: 'visibility',
sortable: true
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false
},
],
}
},
}
export default {
name: 'Pages',
components: {
MobileActionButton,
EmptyPageContent,
DatatableWrapper,
SectionTitle,
MobileHeader,
SwitchInput,
Trash2Icon,
PageHeader,
ButtonBase,
ColorLabel,
Edit2Icon,
Spinner,
},
data() {
return {
isLoading: true,
columns: [
{
label: this.$t('admin_pages.table.page'),
field: 'title',
sortable: true,
},
{
label: this.$t('admin_pages.table.slug'),
field: 'slug',
sortable: true,
},
{
label: this.$t('admin_pages.table.status'),
field: 'visibility',
sortable: true,
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false,
},
],
}
},
}
</script>

View File

@@ -1,29 +1,34 @@
<template>
<div>
<div v-if="! isLoading && page" class="card shadow-card">
<FormLabel>
{{ page.data.attributes.title }}
</FormLabel>
<AppInputSwitch :title="$t('admin_pages.form.visibility')" :description="$t('admin_pages.form.visibility_help')">
<SwitchInput @input="changeStatus" class="switch" :state="page.data.attributes.visibility"/>
</AppInputSwitch>
<AppInputText :title="$t('admin_pages.form.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('admin_pages.form.slug')">
<input v-model="page.data.attributes.slug" type="text" class="focus-border-theme input-dark" disabled/>
</AppInputText>
<AppInputText :title="$t('admin_pages.form.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 v-if="!isLoading && page" class="card shadow-card">
<FormLabel>
{{ page.data.attributes.title }}
</FormLabel>
<AppInputSwitch :title="$t('admin_pages.form.visibility')" :description="$t('admin_pages.form.visibility_help')">
<SwitchInput @input="changeStatus" class="switch" :state="page.data.attributes.visibility" />
</AppInputSwitch>
<AppInputText :title="$t('admin_pages.form.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('admin_pages.form.slug')">
<input v-model="page.data.attributes.slug" type="text" class="focus-border-theme input-dark" disabled />
</AppInputText>
<AppInputText :title="$t('admin_pages.form.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></Spinner>
</div>
@@ -31,52 +36,51 @@
</template>
<script>
import AppInputSwitch from "../../../components/Admin/AppInputSwitch";
import AppInputText from "../../../components/Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import FormLabel from "../../../components/Others/Forms/FormLabel";
import {required} from 'vee-validate/dist/rules'
import SwitchInput from "../../../components/Others/Forms/SwitchInput";
import MobileHeader from "../../../components/Mobile/MobileHeader";
import SectionTitle from "../../../components/Others/SectionTitle";
import ButtonBase from "../../../components/FilesView/ButtonBase";
import PageHeader from "../../../components/Others/PageHeader";
import Spinner from "../../../components/FilesView/Spinner";
import axios from 'axios'
import AppInputSwitch from '../../../components/Admin/AppInputSwitch'
import AppInputText from '../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import FormLabel from '../../../components/Others/Forms/FormLabel'
import { required } from 'vee-validate/dist/rules'
import SwitchInput from '../../../components/Others/Forms/SwitchInput'
import MobileHeader from '../../../components/Mobile/MobileHeader'
import SectionTitle from '../../../components/Others/SectionTitle'
import ButtonBase from '../../../components/FilesView/ButtonBase'
import PageHeader from '../../../components/Others/PageHeader'
import Spinner from '../../../components/FilesView/Spinner'
import axios from 'axios'
export default {
name: 'PageEdit',
components: {
AppInputSwitch,
AppInputText,
ValidationProvider,
ValidationObserver,
FormLabel,
SectionTitle,
MobileHeader,
SwitchInput,
PageHeader,
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
})
export default {
name: 'PageEdit',
components: {
AppInputSwitch,
AppInputText,
ValidationProvider,
ValidationObserver,
FormLabel,
SectionTitle,
MobileHeader,
SwitchInput,
PageHeader,
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>

View File

@@ -1,39 +1,39 @@
<template>
<div>
<!--Page Tab links-->
<div class="card shadow-card z-10" style="padding-bottom: 0; padding-top: 0;">
<CardNavigation :pages="pages" class="-mx-1" />
</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 />
<!--Page Content-->
<router-view />
</div>
</template>
<script>
import CardNavigation from "../../../components/Admin/CardNavigation";
import CardNavigation from '../../../components/Admin/CardNavigation'
export default {
name: 'PaymentSettings',
components: {
CardNavigation,
},
data() {
return {
pages: [
{
title: this.$t('admin_settings.tabs.payments'),
route: 'AppPayments',
},
{
title: this.$t('admin_settings.tabs.billings'),
route: 'AppBillings',
},
]
}
},
mounted() {
this.$router.replace({name: 'AppPayments'})
}
}
export default {
name: 'PaymentSettings',
components: {
CardNavigation,
},
data() {
return {
pages: [
{
title: this.$t('admin_settings.tabs.payments'),
route: 'AppPayments',
},
{
title: this.$t('admin_settings.tabs.billings'),
route: 'AppBillings',
},
],
}
},
mounted() {
this.$router.replace({ name: 'AppPayments' })
},
}
</script>

View File

@@ -1,114 +1,161 @@
<template>
<PageTab :is-loading="isLoading">
<div v-if="billingInformation" class="card shadow-card">
<FormLabel>
{{ $t('admin_settings.billings.section_company') }}
</FormLabel>
<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.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.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('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('admin_settings.billings.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>
<AppInputText :title="$t('admin_settings.billings.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('admin_settings.billings.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>
<div class="flex space-x-4">
<AppInputText :title="$t('admin_settings.billings.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.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.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>
<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/Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import ImageInput from "../../../../components/Others/Forms/ImageInput";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import axios from 'axios'
import { mapGetters } from 'vuex'
import AppInputText from '../../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import ImageInput from '../../../../components/Others/Forms/ImageInput'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/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,
SetupBox,
required,
PageTab,
InfoBox,
},
computed: {
...mapGetters([
'countries'
]),
},
data() {
return {
isLoading: true,
billingInformation: undefined
}
},
mounted() {
axios.get('/api/admin/settings', {
export default {
name: 'AppAppearance',
components: {
ValidationObserver,
ValidationProvider,
AppInputText,
PageTabGroup,
SelectInput,
ImageInput,
ButtonBase,
FormLabel,
SetupBox,
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'
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,
}
})
.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>

View File

@@ -1,453 +1,562 @@
<template>
<PageTab>
<!--Global payment settings-->
<div class="card shadow-card">
<FormLabel icon="dollar">
{{ $t('Subscription Payments') }}
</FormLabel>
<!--Global payment settings-->
<div class="card shadow-card">
<FormLabel icon="dollar">
{{ $t('Subscription Payments') }}
</FormLabel>
<AppInputSwitch :title="$t('Allow Subscription Payments')" :description="$t('User can subscribe to fixed or metered plan')" :is-last="! allowedPayments">
<SwitchInput @input="$updateText('/admin/settings', 'allowed_payments', allowedPayments)" v-model="allowedPayments" :state="allowedPayments" />
</AppInputSwitch>
<AppInputSwitch :title="$t('Allow Subscription Payments')" :description="$t('User can subscribe to fixed or metered plan')" :is-last="!allowedPayments">
<SwitchInput @input="$updateText('/admin/settings', 'allowed_payments', allowedPayments)" v-model="allowedPayments" :state="allowedPayments" />
</AppInputSwitch>
<AppInputText v-if="allowedPayments" :title="$t('Subscription Type')" :is-last="true">
<SelectInput @change="subscriptionTypeChange" :default="config.subscriptionType" :options="subscriptionTypes" :placeholder="$t('Select your subscription type')"/>
</AppInputText>
</div>
<AppInputText v-if="allowedPayments" :title="$t('Subscription Type')" :is-last="true">
<SelectInput @change="subscriptionTypeChange" :default="config.subscriptionType" :options="subscriptionTypes" :placeholder="$t('Select your subscription type')" />
</AppInputText>
</div>
<!--Metered settings-->
<div v-if="config.subscriptionType === 'metered' && allowedPayments" class="card shadow-card">
<FormLabel icon="bar-chart">
{{ $t('Metered Billing Settings') }}
</FormLabel>
<!--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('Credit user automatically bonus to his balance after registration.')">
<SwitchInput @input="$updateText('/admin/settings', 'allowed_registration_bonus', allowedRegistrationBonus)" v-model="allowedRegistrationBonus" :state="allowedRegistrationBonus" />
</AppInputSwitch>
<AppInputSwitch :title="$t('Allow Registration Bonus')" :description="$t('Credit user automatically bonus to his balance after registration.')">
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_registration_bonus', allowedRegistrationBonus)"
v-model="allowedRegistrationBonus"
:state="allowedRegistrationBonus"
/>
</AppInputSwitch>
<AppInputText v-if="allowedRegistrationBonus" :title="$t('The Amount of Registration Bonus')" :description="$t('This bonus will be automatically added when user successfully register his account.')">
<input @input="$updateText('/admin/settings', 'registration_bonus_amount', registrationBonusAmount)" v-model="registrationBonusAmount" :placeholder="$t('Type registration bonus amount...')" type="number" class="focus-border-theme input-dark" />
</AppInputText>
<AppInputText
v-if="allowedRegistrationBonus"
:title="$t('The Amount of Registration Bonus')"
:description="$t('This bonus will be automatically added when user successfully register his account.')"
>
<input
@input="$updateText('/admin/settings', 'registration_bonus_amount', registrationBonusAmount)"
v-model="registrationBonusAmount"
:placeholder="$t('Type registration bonus amount...')"
type="number"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputButton :title="$t('Metered Plan')" :description="$t('Your price set up for billing multiple features by user usage.')" :is-last="true">
<router-link v-if="config.isCreatedMeteredPlan" :to="{name: 'PlanMeteredSettings', params: {id: config.meteredPlanId}}">
<ButtonBase v-if="config.isCreatedMeteredPlan" class="sm:w-auto w-full" button-style="theme">
{{ $t('Plan Details') }}
</ButtonBase>
</router-link>
<AppInputButton :title="$t('Metered Plan')" :description="$t('Your price set up for billing multiple features by user usage.')" :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="sm:w-auto w-full" button-style="theme-solid">
{{ $t('Create Plan') }}
</ButtonBase>
</router-link>
</AppInputButton>
</div>
<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">
<!--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 your users pay by their credit card')" :is-last="! stripe.allowedService">
<SwitchInput @input="$updateText('/admin/settings', 'allowed_stripe', stripe.allowedService)" v-model="stripe.allowedService" :state="stripe.allowedService" />
</AppInputSwitch>
<AppInputSwitch :title="$t('Allow Stripe Service')" :description="$t('Allow your users pay by their credit 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">
<div v-if="stripe.isConfigured">
<AppInputText @input="$updateText('/admin/settings', 'stripe_payment_description', stripe.paymentDescription)" :title="$t('Payment Description')" :description="$t('The description showed below user payment method selection.')">
<textarea rows="2" @input="$updateText('/admin/settings', 'stripe_payment_description', stripe.paymentDescription, true)" v-model="stripe.paymentDescription" :placeholder="$t('Describe in short which methods user can pay with this payment method...')" type="text" class="focus-border-theme input-dark" />
</AppInputText>
<!--Stripe credentials are set up-->
<div v-if="stripe.allowedService">
<div v-if="stripe.isConfigured">
<AppInputText
@input="$updateText('/admin/settings', 'stripe_payment_description', stripe.paymentDescription)"
:title="$t('Payment Description')"
:description="$t('The description showed below user payment method selection.')"
>
<textarea
rows="2"
@input="$updateText('/admin/settings', 'stripe_payment_description', stripe.paymentDescription, true)"
v-model="stripe.paymentDescription"
:placeholder="$t('Describe in short which methods user can pay with this payment method...')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('Your Webhook URL')" :description="$t('Please copy your url and paste it to the service webhook setup.')">
<CopyInput size="small" :str="getWebhookEndpoint('stripe')" />
</AppInputText>
<AppInputText :title="$t('Your Webhook URL')" :description="$t('Please copy your url and paste it to the service webhook setup.')">
<CopyInput size="small" :str="getWebhookEndpoint('stripe')" />
</AppInputText>
<div @click="stripe.isVisibleCredentialsForm = !stripe.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :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>
<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="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="p-5 shadow-lg rounded-xl"
>
<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-red': 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-red': 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 your webhook secret')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Set up Stripe credentials-->
<ValidationObserver
v-if="!stripe.isConfigured || stripe.isVisibleCredentialsForm"
@submit.prevent="storeCredentials('stripe')"
ref="credentialsForm"
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-red': 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-red': 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 your webhook secret')"
type="text"
:class="{ 'border-red': 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>
<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">
<!--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 your users pay by their credit card')" :is-last="! paystack.allowedService">
<SwitchInput @input="$updateText('/admin/settings', 'allowed_paystack', paystack.allowedService)" v-model="paystack.allowedService" :state="paystack.allowedService" />
</AppInputSwitch>
<AppInputSwitch :title="$t('Allow Paystack Service')" :description="$t('Allow your users pay by their credit 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">
<div v-if="paystack.isConfigured">
<AppInputText @input="$updateText('/admin/settings', 'paystack_payment_description', paystack.paymentDescription)" :title="$t('Payment Description')" :description="$t('The description showed below user payment method selection.')">
<textarea rows="2" @input="$updateText('/admin/settings', 'paystack_payment_description', paystack.paymentDescription, true)" v-model="paystack.paymentDescription" :placeholder="$t('Describe in short which methods user can pay with this payment method...')" type="text" class="focus-border-theme input-dark" />
</AppInputText>
<!--Paystack credentials are set up-->
<div v-if="paystack.allowedService">
<div v-if="paystack.isConfigured">
<AppInputText
@input="$updateText('/admin/settings', 'paystack_payment_description', paystack.paymentDescription)"
:title="$t('Payment Description')"
:description="$t('The description showed below user payment method selection.')"
>
<textarea
rows="2"
@input="$updateText('/admin/settings', 'paystack_payment_description', paystack.paymentDescription, true)"
v-model="paystack.paymentDescription"
:placeholder="$t('Describe in short which methods user can pay with this payment method...')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('Your Webhook URL')" :description="$t('Please copy your url and paste it to the service webhook setup.')">
<CopyInput size="small" :str="getWebhookEndpoint('paystack')" />
</AppInputText>
<AppInputText :title="$t('Your Webhook URL')" :description="$t('Please copy your url and paste it to the service webhook setup.')">
<CopyInput size="small" :str="getWebhookEndpoint('paystack')" />
</AppInputText>
<div @click="paystack.isVisibleCredentialsForm = !paystack.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :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>
<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="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="p-5 shadow-lg rounded-xl"
>
<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-red': 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-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Set up Paystack credentials-->
<ValidationObserver
v-if="!paystack.isConfigured || paystack.isVisibleCredentialsForm"
@submit.prevent="storeCredentials('paystack')"
ref="credentialsForm"
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-red': 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-red': 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>
<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">
<!--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 your users pay by their credit card')" :is-last="! paypal.allowedService">
<SwitchInput @input="$updateText('/admin/settings', 'allowed_paypal', paypal.allowedService)" v-model="paypal.allowedService" :state="paypal.allowedService" />
</AppInputSwitch>
<AppInputSwitch :title="$t('Allow PayPal Service')" :description="$t('Allow your users pay by their credit 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">
<div v-if="paypal.isConfigured">
<AppInputSwitch :title="$t('Live Mode')" :description="$t('Toggle amid live and sandbox mode')">
<SwitchInput @input="$updateText('/admin/settings', 'paypal_live', config.isPayPalLive)" v-model="config.isPayPalLive" :state="config.isPayPalLive" />
</AppInputSwitch>
<!--Stripe credentials are set up-->
<div v-if="paypal.allowedService">
<div v-if="paypal.isConfigured">
<AppInputSwitch :title="$t('Live Mode')" :description="$t('Toggle amid 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('The description showed below user payment method selection.')">
<textarea rows="2" @input="$updateText('/admin/settings', 'paypal_payment_description', paypal.paymentDescription, true)" v-model="paypal.paymentDescription" :placeholder="$t('Describe in short which methods user can pay with this payment method...')" type="text" class="focus-border-theme input-dark" />
</AppInputText>
<AppInputText
@input="$updateText('/admin/settings', 'paypal_payment_description', paypal.paymentDescription)"
:title="$t('Payment Description')"
:description="$t('The description showed below user payment method selection.')"
>
<textarea
rows="2"
@input="$updateText('/admin/settings', 'paypal_payment_description', paypal.paymentDescription, true)"
v-model="paypal.paymentDescription"
:placeholder="$t('Describe in short which methods user can pay with this payment method...')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('Your Webhook URL')" :description="$t('Please copy your url and paste it to the service webhook setup.')">
<CopyInput size="small" :str="getWebhookEndpoint('paypal')" />
</AppInputText>
<AppInputText :title="$t('Your Webhook URL')" :description="$t('Please copy your url and paste it to the service webhook setup.')">
<CopyInput size="small" :str="getWebhookEndpoint('paypal')" />
</AppInputText>
<div @click="paypal.isVisibleCredentialsForm = !paypal.isVisibleCredentialsForm" class="flex items-center cursor-pointer" :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>
<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="credentialsForm"
v-slot="{ invalid }"
tag="form"
class="p-5 shadow-lg rounded-xl"
>
<FormLabel v-if="! paypal.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="paypal.credentials.key" :placeholder="$t('admin_settings.payments.stripe_pub_key_plac')" type="text" :class="{'border-red': 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-red': 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-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Set up Paypal credentials-->
<ValidationObserver
v-if="!paypal.isConfigured || paypal.isVisibleCredentialsForm"
@submit.prevent="storeCredentials('paypal')"
ref="credentialsForm"
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 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-red': 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-red': 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-red': 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>
<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/Admin/AppInputButton";
import DatatableWrapper from "../../../../components/Others/Tables/DatatableWrapper";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import SwitchInput from "../../../../components/Others/Forms/SwitchInput";
import ImageInput from "../../../../components/Others/Forms/ImageInput";
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch"
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import CopyInput from "../../../../components/Others/Forms/CopyInput"
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import AppInputText from "../../../../components/Admin/AppInputText"
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import {events} from '../../../../bus'
import {mapGetters} from 'vuex'
import axios from 'axios'
import { Edit2Icon, Trash2Icon } from 'vue-feather-icons'
import AppInputButton from '../../../../components/Admin/AppInputButton'
import DatatableWrapper from '../../../../components/Others/Tables/DatatableWrapper'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import SwitchInput from '../../../../components/Others/Forms/SwitchInput'
import ImageInput from '../../../../components/Others/Forms/ImageInput'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import CopyInput from '../../../../components/Others/Forms/CopyInput'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import AppInputText from '../../../../components/Admin/AppInputText'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/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,
SetupBox,
required,
PageTab,
InfoBox,
},
computed: {
...mapGetters([
'subscriptionTypes',
'config',
]),
submitButtonText() {
return this.isLoading ? this.$t('admin_settings.payments.button_testing') : this.$t('admin_settings.payments.button_submit')
}
},
data() {
return {
allowedRegistrationBonus: true,
registrationBonusAmount: undefined,
export default {
name: 'AppPayments',
components: {
AppInputButton,
DatatableWrapper,
ValidationObserver,
ValidationProvider,
AppInputSwitch,
AppInputText,
PageTabGroup,
SwitchInput,
SelectInput,
ImageInput,
ButtonBase,
CopyInput,
FormLabel,
Trash2Icon,
Edit2Icon,
SetupBox,
required,
PageTab,
InfoBox,
},
computed: {
...mapGetters(['subscriptionTypes', 'config']),
submitButtonText() {
return this.isLoading ? this.$t('admin_settings.payments.button_testing') : this.$t('admin_settings.payments.button_submit')
},
},
data() {
return {
allowedRegistrationBonus: true,
registrationBonusAmount: undefined,
allowedPayments: false,
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,
}
},
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('admin_page_plans.table.subscribers'),
sortable: false
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false
},
]
}
},
methods: {
async storeCredentials(service) {
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,
},
},
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('admin_page_plans.table.subscribers'),
sortable: false,
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false,
},
],
}
},
methods: {
async storeCredentials(service) {
// Validate fields
const isValid = await this.$refs.credentialsForm.validate()
// Validate fields
const isValid = await this.$refs.credentialsForm.validate();
if (!isValid) return
if (!isValid) return;
// Start loading
this.isLoading = true
// 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,
})
.then(() => {
// Update Credentials
let commitKey = {
stripe: 'SET_STRIPE_CREDENTIALS',
paystack: 'SET_PAYSTACK_CREDENTIALS',
paypal: 'SET_PAYPAL_CREDENTIALS',
}[service]
// 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,
})
.then(() => {
// Commit credentials
this.$store.commit(commitKey, this[service].credentials)
// Update Credentials
let commitKey = {
stripe: 'SET_STRIPE_CREDENTIALS',
paystack: 'SET_PAYSTACK_CREDENTIALS',
paypal: 'SET_PAYPAL_CREDENTIALS',
}[service]
this[service].allowedService = true
this[service].isConfigured = true
this[service].isVisibleCredentialsForm = false
// Commit credentials
this.$store.commit(commitKey, this[service].credentials)
// 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))
},
subscriptionTypeChange(type) {
events.$emit('confirm:open', {
title: this.$t('Are you sure you want to change subscription type?'),
message: this.$t(
'We strongly do not recommend change this value if there is any subscribed user to prevent any failures. You can operate only with one type of subscription and you can not change it on the fly!'
),
action: {
type: type,
operation: 'change-subscription-type',
},
})
},
getWebhookEndpoint(service) {
return `${this.config.host}/api/subscriptions/${service}/webhook`
},
},
mounted() {
// 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
this[service].allowedService = true
this[service].isConfigured = true
this[service].isVisibleCredentialsForm = false
// Set if service is allowed
this.stripe.allowedService = this.config.isStripe
this.paystack.allowedService = this.config.isPaystack
this.paypal.allowedService = this.config.isPayPal
// Show toaster
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.credentials_set', {service: service}),
})
})
.catch(error => {
if (this.config.stripe_public_key) this.stripe.isConfigured = true
if (error.response.status === 500) {
this.isError = true
this.errorMessage = error.response.data.message
}
})
.finally(() => this.isLoading = false)
},
subscriptionTypeChange(type) {
events.$emit('confirm:open', {
title: this.$t('Are you sure you want to change subscription type?'),
message: this.$t('We strongly do not recommend change this value if there is any subscribed user to prevent any failures. You can operate only with one type of subscription and you can not change it on the fly!'),
action: {
type: type,
operation: 'change-subscription-type',
}
})
},
getWebhookEndpoint(service) {
return `${this.config.host}/api/subscriptions/${service}/webhook`
},
},
mounted() {
// 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
if (this.config.paystack_public_key) this.paystack.isConfigured = true
// 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.paypal_client_id) this.paypal.isConfigured = true
if (this.config.stripe_public_key)
this.stripe.isConfigured = true
this.allowedPayments = this.config.allowed_payments
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
},
created() {
events.$on('action:confirmed', data => {
if (data.operation === 'change-subscription-type')
this.$updateText('/admin/settings', 'subscription_type', data.type)
})
},
destroyed() {
events.$off('action:confirmed')
},
}
// Set metered
this.allowedRegistrationBonus = this.config.allowed_registration_bonus
this.registrationBonusAmount = this.config.registration_bonus_amount
},
created() {
events.$on('action:confirmed', (data) => {
if (data.operation === 'change-subscription-type') this.$updateText('/admin/settings', 'subscription_type', data.type)
})
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -1,103 +1,137 @@
<template>
<div>
<!--Plans-->
<div v-if="! config.isEmptyPlans" class="card shadow-card">
<!--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('admin_page_plans.create_plan_button') }}
</MobileActionButton>
</router-link>
</div>
<!--Create button-->
<div v-if="! config.isCreatedMeteredPlan || config.subscriptionType === 'fixed'" class="mb-6">
<router-link :to="{name: createPlanRoute}">
<MobileActionButton icon="plus">
{{ $t('admin_page_plans.create_plan_button') }}
</MobileActionButton>
</router-link>
</div>
<!--Datatable-->
<!--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="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-5 md:pr-1 pr-3">
<router-link class="text-sm font-bold" :to="{name: 'PlanMeteredSettings', params: {id: row.data.id}}">
{{ row.data.attributes.name }}
</router-link>
<!--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="md:px-1 px-3">
<ColorLabel :color="$getPlanStatusColor(row.data.attributes.status)">
{{ row.data.attributes.status }}
</ColorLabel>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<ColorLabel :color="$getPlanStatusColor(row.data.attributes.status)">
{{ row.data.attributes.status }}
</ColorLabel>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.currency }}
{{ row.data.attributes.currency }}
</span>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold capitalize">
{{ row.data.attributes.interval }}
{{ row.data.attributes.interval }}
</span>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.subscribers }}
{{ row.data.attributes.subscribers }}
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<div class="flex space-x-2 w-full justify-end">
<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 items-center justify-center w-8 h-8 rounded-md hover:bg-green-100 dark:bg-2x-dark-foreground bg-light-background transition-colors"
>
: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 items-center justify-center w-8 h-8 rounded-md hover:bg-red-100 dark:bg-2x-dark-foreground bg-light-background transition-colors"
>
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="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-5 md:pr-1 pr-3">
<SwitchInput @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="md:px-1 px-3">
<router-link class="text-sm font-bold" :to="{name: 'PlanFixedSettings', params: {id: row.data.id}}">
{{ row.data.attributes.name }}
</router-link>
<!--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">
<SwitchInput
@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="md:px-1 px-3">
<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">
<span class="text-sm font-bold">
{{ row.data.attributes.price }}
{{ row.data.attributes.price }}
</span>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold capitalize">
{{ row.data.attributes.interval }}
{{ row.data.attributes.interval }}
</span>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.subscribers }}
{{ row.data.attributes.subscribers }}
</span>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.features.max_storage_amount }} GB
{{ row.data.attributes.features.max_storage_amount }}
GB
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<div class="flex space-x-2 w-full justify-end">
<router-link class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-green-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'PlanFixedSettings', params: {id: row.data.id}}">
<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 items-center justify-center w-8 h-8 rounded-md hover:bg-red-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'PlanFixedDelete', params: {id: row.data.id}}">
<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>
@@ -105,129 +139,127 @@
</tr>
</template>
</DatatableWrapper>
</div>
</div>
<!--Empty State-->
<div v-if="config.isEmptyPlans" class="flex items-center justify-center h-full">
<div class="text-center">
<img class="w-28 inline-block mb-6" src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f9fe.svg" alt="transaction">
<!--Empty State-->
<div v-if="config.isEmptyPlans" class="flex h-full items-center justify-center">
<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="text-2xl font-bold mb-1">
{{ $t("There is Nothing") }}
</h1>
<h1 class="mb-1 text-2xl font-bold">
{{ $t('There is Nothing') }}
</h1>
<p class="text-sm text-gray-600">
{{ $t('All your plans will be visible here') }}
</p>
<p class="text-sm text-gray-600">
{{ $t('All your plans will be visible here') }}
</p>
<router-link :to="{name: createPlanRoute}" class="inline-block mt-6">
<ButtonBase class="action-confirm" button-style="theme">
{{ $t('Create First Plan') }}
</ButtonBase>
</router-link>
</div>
</div>
<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/Others/Tables/DatatableWrapper";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import SwitchInput from "../../components/Others/Forms/SwitchInput";
import ButtonBase from "../../components/FilesView/ButtonBase";
import ColorLabel from "../../components/Others/ColorLabel";
import {Trash2Icon, Edit2Icon} from "vue-feather-icons";
import { mapGetters } from 'vuex'
import DatatableWrapper from '../../components/Others/Tables/DatatableWrapper'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import SwitchInput from '../../components/Others/Forms/SwitchInput'
import ButtonBase from '../../components/FilesView/ButtonBase'
import ColorLabel from '../../components/Others/ColorLabel'
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'Plans',
components: {
MobileActionButton,
DatatableWrapper,
SwitchInput,
ColorLabel,
Trash2Icon,
ButtonBase,
Edit2Icon,
export default {
name: 'Plans',
components: {
MobileActionButton,
DatatableWrapper,
SwitchInput,
ColorLabel,
Trash2Icon,
ButtonBase,
Edit2Icon,
},
computed: {
...mapGetters(['config']),
createPlanRoute() {
return {
metered: 'CreateMeteredPlan',
fixed: 'CreateFixedPlan',
}[this.config.subscriptionType]
},
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('admin_page_plans.table.subscribers'),
sortable: false
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false
},
],
fixed: [
{
label: this.$t('Visibility'),
field: 'visible',
sortable: true
},
{
label: this.$t('Name'),
field: 'name',
sortable: true
},
{
label: this.$t('Price'),
field: 'amount',
sortable: true
},
{
label: this.$t('Interval'),
field: 'interval',
sortable: true
},
{
label: this.$t('admin_page_plans.table.subscribers'),
sortable: false
},
{
label: this.$t('Storage'),
sortable: false
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false
},
],
}[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('admin_page_plans.table.subscribers'),
sortable: false,
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false,
},
],
fixed: [
{
label: this.$t('Visibility'),
field: 'visible',
sortable: true,
},
{
label: this.$t('Name'),
field: 'name',
sortable: true,
},
{
label: this.$t('Price'),
field: 'amount',
sortable: true,
},
{
label: this.$t('Interval'),
field: 'interval',
sortable: true,
},
{
label: this.$t('admin_page_plans.table.subscribers'),
sortable: false,
},
{
label: this.$t('Storage'),
sortable: false,
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false,
},
],
}[this.config.subscriptionType]
},
}
},
}
</script>

View File

@@ -1,197 +1,227 @@
<template>
<ValidationObserver @submit.prevent="createPlan" ref="createPlan" v-slot="{ invalid }" tag="form">
<ValidationObserver @submit.prevent="createPlan" ref="createPlan" v-slot="{ invalid }" tag="form">
<div class="card shadow-card">
<FormLabel>
{{ $t('Details') }}
</FormLabel>
<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('admin_page_plans.form.name')">
<input
v-model="plan.name"
:placeholder="$t('admin_page_plans.form.name_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<!--Name-->
<ValidationProvider tag="div" mode="passive" name="Name" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_plans.form.name')">
<input v-model="plan.name" :placeholder="$t('admin_page_plans.form.name_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Description-->
<ValidationProvider tag="div" mode="passive" name="Description" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_plans.form.description')" :is-last="true">
<textarea
v-model="plan.description"
:placeholder="$t('admin_page_plans.form.description_plac')"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
maxlength="120"
></textarea>
</AppInputText>
</ValidationProvider>
</div>
<!--Description-->
<ValidationProvider tag="div" mode="passive" name="Description" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_plans.form.description')" :is-last="true">
<textarea v-model="plan.description" :placeholder="$t('admin_page_plans.form.description_plac')" :class="{'border-red': 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="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('admin_page_plans.form.price')" class="w-full">
<input
v-model="plan.amount"
:placeholder="$t('admin_page_plans.form.price_plac')"
type="number"
step="0.01"
min="1"
max="999999999999"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<div class="md:flex justify-items md:space-x-4">
<!--Price-->
<ValidationProvider tag="div" mode="passive" name="Price" rules="required" v-slot="{ errors }" class="w-full">
<AppInputText :title="$t('admin_page_plans.form.price')" class="w-full">
<input v-model="plan.amount" :placeholder="$t('admin_page_plans.form.price_plac')" type="number" step="0.01" min="1" max="999999999999" :class="{'border-red': 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>
<!--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>
<!--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>
<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-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<!--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-red': 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('To set unlimited team members, type -1 into form')" :is-last="true">
<input
v-model="plan.features.max_team_members"
:placeholder="$t('Add max team members in number')"
type="number"
min="1"
max="999999999"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
</div>
<!--Team Members-->
<ValidationProvider tag="div" mode="passive" name="Max Team Members" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('Team Members')" :description="$t('To set unlimited team members, type -1 into form')" :is-last="true">
<input v-model="plan.features.max_team_members" :placeholder="$t('Add max team members in number')" type="number" min="1" max="999999999" :class="{'border-red': 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>
<InfoBox v-if="isError" type="error" style="margin-top: 40px">
<p>{{ errorMessage }}</p>
</InfoBox>
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit">
{{ $t('admin_page_plans.create_plan_button') }}
</ButtonBase>
</ValidationObserver>
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit">
{{ $t('admin_page_plans.create_plan_button') }}
</ButtonBase>
</ValidationObserver>
</template>
<script>
import AppInputText from "../../../../components/Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import ImageInput from "../../../../components/Others/Forms/ImageInput";
import MobileHeader from "../../../../components/Mobile/MobileHeader";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import SectionTitle from "../../../../components/Others/SectionTitle";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import PageHeader from "../../../../components/Others/PageHeader";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '../../../../bus'
import axios from 'axios'
import AppInputText from '../../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import ImageInput from '../../../../components/Others/Forms/ImageInput'
import MobileHeader from '../../../../components/Mobile/MobileHeader'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import SectionTitle from '../../../../components/Others/SectionTitle'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import PageHeader from '../../../../components/Others/PageHeader'
import InfoBox from '../../../../components/Others/Forms/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,
PageHeader,
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() {
export default {
name: 'CreateFixedPlan',
components: {
ValidationProvider,
ValidationObserver,
SectionTitle,
AppInputText,
MobileHeader,
SelectInput,
ButtonBase,
ImageInput,
PageHeader,
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()
// Validate fields
const isValid = await this.$refs.createPlan.validate();
if (!isValid) return
if (!isValid) return;
// Start loading
this.isLoading = true
// 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'),
})
axios
.post('/api/subscriptions/admin/plans', this.plan)
.then(response => {
// Go to plan page
this.$router.push({
name: 'PlanFixedSettings',
params: { id: response.data.data.id },
})
// Show toaster
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.plan_created'),
})
// 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'),
})
}
}
// 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) {
this.isError = true
this.errorMessage = error.response.data.message
}
})
.finally(() => {
this.isLoading = false
})
}
},
}
if (error.response.status === 500) {
this.isError = true
this.errorMessage = error.response.data.message
}
})
.finally(() => {
this.isLoading = false
})
},
},
}
</script>

View File

@@ -1,243 +1,287 @@
<template>
<ValidationObserver @submit.prevent="createPlan" ref="createPlan" v-slot="{ invalid }" tag="form">
<ValidationObserver @submit.prevent="createPlan" ref="createPlan" v-slot="{ invalid }" tag="form">
<div class="card shadow-card">
<FormLabel>
{{ $t('Details') }}
</FormLabel>
<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('admin_page_plans.form.name')">
<input
v-model="plan.name"
:placeholder="$t('admin_page_plans.form.name_plac')"
type="text"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<!--Name-->
<ValidationProvider tag="div" mode="passive" name="Name" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_plans.form.name')">
<input v-model="plan.name" :placeholder="$t('admin_page_plans.form.name_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Description-->
<ValidationProvider tag="div" mode="passive" name="Description" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_plans.form.description')">
<textarea
v-model="plan.description"
:placeholder="$t('admin_page_plans.form.description_plac')"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
></textarea>
</AppInputText>
</ValidationProvider>
<!--Description-->
<ValidationProvider tag="div" mode="passive" name="Description" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_plans.form.description')">
<textarea v-model="plan.description" :placeholder="$t('admin_page_plans.form.description_plac')" :class="{'border-red': 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>
<!--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>
<div class="card shadow-card">
<FormLabel>
{{ $t('Charged Features') }}
</FormLabel>
<!--Bandwidth-->
<div>
<AppInputSwitch :title="$t('Bandwidth Price per 1GB')" :description="$t('Charge your user by the amount of data he upload or download.')">
<SwitchInput v-model="plan.features.bandwidth.active" class="switch" :state="plan.features.bandwidth.active" />
</AppInputSwitch>
<!--Bandwidth-->
<div>
<AppInputSwitch :title="$t('Bandwidth Price per 1GB')" :description="$t('Charge your user by the amount of data he upload or download.')">
<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 the price per 1GB...')"
type="number"
step="0.01"
min="0.01"
max="999999999999"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
</div>
<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 the price per 1GB...')" type="number" step="0.01" min="0.01" max="999999999999" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
</div>
<!--Storage-->
<div>
<AppInputSwitch :title="$t('Storage Price per 1GB')" :description="$t('Charge your user by the amount of data he has stored on the disk per 1GB.')">
<SwitchInput v-model="plan.features.storage.active" class="switch" :state="plan.features.storage.active" />
</AppInputSwitch>
</div>
<!--Storage-->
<div>
<AppInputSwitch :title="$t('Storage Price per 1GB')" :description="$t('Charge your user by the amount of data he has stored on the disk per 1GB.')">
<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 the price per 1GB...')"
type="number"
step="0.01"
min="0.01"
max="999999999999"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<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 the price per 1GB...')" type="number" step="0.01" min="0.01" max="999999999999" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Member-->
<div>
<AppInputSwitch :title="$t('Price per 1 Member')" :description="$t('Charge your user by the total members he use in his Team Folders.')">
<SwitchInput v-model="plan.features.member.active" class="switch" :state="plan.features.member.active" />
</AppInputSwitch>
</div>
<!--Member-->
<div>
<AppInputSwitch :title="$t('Price per 1 Member')" :description="$t('Charge your user by the total members he use in his Team Folders.')">
<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 the price per 1 member...')"
type="number"
step="0.01"
min="0.01"
max="999999999999"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<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 the price per 1 member...')" type="number" step="0.01" min="0.01" max="999999999999" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
<!--Flat Fee-->
<div>
<AppInputSwitch :title="$t('Flat Fee per Cycle')" :description="$t('Charge monthly flat fee.')" :is-last="!plan.features.flatFee.active">
<SwitchInput v-model="plan.features.flatFee.active" class="switch" :state="plan.features.flatFee.active" />
</AppInputSwitch>
<!--Flat Fee-->
<div>
<AppInputSwitch :title="$t('Flat Fee per Cycle')" :description="$t('Charge monthly flat fee.')" :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 the price...')"
type="number"
step="0.01"
min="0.01"
max="999999999999"
:class="{ 'border-red': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
</div>
</div>
<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 the price...')" type="number" step="0.01" min="0.01" max="999999999999" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
</AppInputText>
</ValidationProvider>
</div>
</div>
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit">
{{ $t('admin_page_plans.create_plan_button') }}
</ButtonBase>
</ValidationObserver>
<ButtonBase :disabled="isLoading" :loading="isLoading" button-style="theme" type="submit">
{{ $t('admin_page_plans.create_plan_button') }}
</ButtonBase>
</ValidationObserver>
</template>
<script>
import SwitchInput from "../../../../components/Others/Forms/SwitchInput"
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch"
import AppInputText from "../../../../components/Admin/AppInputText"
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import ImageInput from "../../../../components/Others/Forms/ImageInput";
import MobileHeader from "../../../../components/Mobile/MobileHeader";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import SectionTitle from "../../../../components/Others/SectionTitle";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import PageHeader from "../../../../components/Others/PageHeader";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '../../../../bus'
import axios from 'axios'
import SwitchInput from '../../../../components/Others/Forms/SwitchInput'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import AppInputText from '../../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import ImageInput from '../../../../components/Others/Forms/ImageInput'
import MobileHeader from '../../../../components/Mobile/MobileHeader'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import SectionTitle from '../../../../components/Others/SectionTitle'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import PageHeader from '../../../../components/Others/PageHeader'
import InfoBox from '../../../../components/Others/Forms/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,
PageHeader,
FormLabel,
required,
InfoBox,
},
computed: {
...mapGetters([
'currencyList',
'intervalList',
])
},
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() {
export default {
name: 'CreateMeteredPlan',
components: {
ValidationProvider,
ValidationObserver,
AppInputSwitch,
SwitchInput,
SectionTitle,
AppInputText,
MobileHeader,
SelectInput,
ButtonBase,
ImageInput,
PageHeader,
FormLabel,
required,
InfoBox,
},
computed: {
...mapGetters(['currencyList', 'intervalList']),
},
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 = []
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,
},
],
})
}
})
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()
// Validate fields
const isValid = await this.$refs.createPlan.validate();
if (!isValid) return
if (!isValid) return;
// Start loading
this.isLoading = true
// 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'),
})
axios
.post('/api/subscriptions/admin/plans', {
type: 'metered',
name: this.plan.name,
description: this.plan.description,
currency: this.plan.currency,
meters: tiers
})
.then(response => {
// Go to plan page
this.$router.push({
name: 'PlanMeteredSettings',
params: { id: response.data.data.id },
})
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
})
}
},
}
// 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>

View File

@@ -1,20 +1,20 @@
<template>
<div>
<div v-if="plan" class="card shadow-card sticky top-0 z-10" style="padding-bottom: 0">
<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 text-gray-500 sm:text-sm">
{{ plan.attributes.price }} /
{{ $t(`interval.${plan.attributes.interval}`) }}
</small>
</div>
<div class="mb-2">
<h1 class="font-bold sm:text-xl text-lg">
{{ plan.attributes.name }}
</h1>
<small class="sm:text-sm text-xs font-bold text-gray-500">
{{ plan.attributes.price }} / {{ $t(`interval.${plan.attributes.interval}`) }}
</small>
</div>
<CardNavigation :pages="pages" class="-mx-1.5" />
</div>
<CardNavigation :pages="pages" class="-mx-1.5" />
</div>
<router-view v-if="! isLoading" :plan="plan" />
<router-view v-if="!isLoading" :plan="plan" />
<div id="loader" v-if="isLoading">
<Spinner></Spinner>
@@ -23,44 +23,45 @@
</template>
<script>
import CardNavigation from "../../../components/Admin/CardNavigation"
import Spinner from "../../../components/FilesView/Spinner";
import axios from 'axios'
import CardNavigation from '../../../components/Admin/CardNavigation'
import Spinner from '../../../components/FilesView/Spinner'
import axios from 'axios'
export default {
name: 'FixedPlan',
components: {
CardNavigation,
Spinner,
},
data() {
return {
isLoading: true,
plan: undefined,
pages: [
{
title: this.$t('admin_page_plans.tabs.settings'),
route: 'PlanFixedSettings',
},
{
title: this.$t('admin_page_plans.tabs.subscribers'),
route: 'PlanFixedSubscribers',
},
{
title: this.$t('admin_page_plans.tabs.delete'),
route: 'PlanFixedDelete',
},
]
}
},
created() {
axios.get('/api/subscriptions/admin/plans/' + this.$route.params.id)
.then(response => {
this.plan = response.data.data
})
.finally(() => {
this.isLoading = false
})
}
}
export default {
name: 'FixedPlan',
components: {
CardNavigation,
Spinner,
},
data() {
return {
isLoading: true,
plan: undefined,
pages: [
{
title: this.$t('admin_page_plans.tabs.settings'),
route: 'PlanFixedSettings',
},
{
title: this.$t('admin_page_plans.tabs.subscribers'),
route: 'PlanFixedSubscribers',
},
{
title: this.$t('admin_page_plans.tabs.delete'),
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>

View File

@@ -1,21 +1,20 @@
<template>
<div>
<div v-if="plan" class="card shadow-card sticky top-0 z-10" style="padding-bottom: 0;">
<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 text-gray-500 sm:text-sm">
{{ $t('30 Days intervals') }}
</small>
</div>
<div class="mb-2">
<h1 class="font-bold sm:text-xl text-lg">
{{ plan.attributes.name }}
</h1>
<small class="sm:text-sm text-xs font-bold text-gray-500">
{{ $t('30 Days intervals') }}
</small>
</div>
<!--Navigation-->
<CardNavigation :pages="pages" class="-mx-1" />
</div>
<!--Navigation-->
<CardNavigation :pages="pages" class="-mx-1" />
</div>
<router-view v-if="! isLoading" :plan="plan" />
<router-view v-if="!isLoading" :plan="plan" />
<div id="loader" v-if="isLoading">
<Spinner></Spinner>
@@ -24,57 +23,56 @@
</template>
<script>
import CardNavigation from "../../../components/Admin/CardNavigation"
import Spinner from "../../../components/FilesView/Spinner";
import axios from 'axios'
import {mapGetters} from "vuex";
import CardNavigation from '../../../components/Admin/CardNavigation'
import Spinner from '../../../components/FilesView/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('admin_page_plans.tabs.settings'),
route: 'PlanMeteredSettings',
},
{
title: this.$t('admin_page_plans.tabs.subscribers'),
route: 'PlanMeteredSubscribers',
},
]
export default {
name: 'MeteredPlan',
components: {
CardNavigation,
Spinner,
},
computed: {
...mapGetters(['config']),
pages() {
let pages = [
{
title: this.$t('admin_page_plans.tabs.settings'),
route: 'PlanMeteredSettings',
},
{
title: this.$t('admin_page_plans.tabs.subscribers'),
route: 'PlanMeteredSubscribers',
},
]
if (this.plan && this.plan.attributes.status === 'active') {
pages.push({
title: this.$t('admin_page_plans.tabs.delete'),
route: 'PlanMeteredDelete',
})
}
if (this.plan && this.plan.attributes.status === 'active') {
pages.push({
title: this.$t('admin_page_plans.tabs.delete'),
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
})
}
}
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>

View File

@@ -1,86 +1,98 @@
<template>
<div class="card shadow-card">
<FormLabel>
{{ $t('admin_page_plans.form.title_delete') }}
</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="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
<input v-model="planName" :placeholder="$t('admin_page_plans.form.name_delete_plac')" type="text" :class="{'border-red': errors[0]}" class="focus-border-theme input-dark" />
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="danger" class="sm:w-auto w-full">
{{ $t('admin_page_plans.delete_plan_button') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
<div class="card shadow-card">
<FormLabel>
{{ $t('admin_page_plans.form.title_delete') }}
</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('admin_page_plans.form.name_delete_plac')"
type="text"
:class="{ 'border-red': 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('admin_page_plans.delete_plan_button') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
</template>
<script>
import AppInputText from "../../../../components/Admin/AppInputText";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import {required, is} from 'vee-validate/dist/rules'
import {events} from '../../../../bus'
import axios from 'axios'
import AppInputText from '../../../../components/Admin/AppInputText'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import ButtonBase from '../../../../components/FilesView/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(() => {
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
})
}
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(() => {
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>

View File

@@ -1,75 +1,107 @@
<template>
<div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Details') }}
</FormLabel>
<div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Details') }}
</FormLabel>
<!--Visible-->
<AppInputSwitch :title="$t('admin_page_plans.form.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>
<!--Visible-->
<AppInputSwitch :title="$t('admin_page_plans.form.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('admin_page_plans.form.name')">
<input @input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'name', plan.attributes.name)" v-model="plan.attributes.name" :placeholder="$t('admin_page_plans.form.name_plac')" type="text" class="focus-border-theme input-dark"/>
</AppInputText>
<!--Name-->
<AppInputText :title="$t('admin_page_plans.form.name')">
<input
@input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'name', plan.attributes.name)"
v-model="plan.attributes.name"
:placeholder="$t('admin_page_plans.form.name_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<!--Description-->
<AppInputText :title="$t('admin_page_plans.form.description')">
<textarea @input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'description', plan.attributes.description)" v-model="plan.attributes.description" :placeholder="$t('admin_page_plans.form.description_plac')" class="focus-border-theme input-dark"></textarea>
</AppInputText>
<!--Description-->
<AppInputText :title="$t('admin_page_plans.form.description')">
<textarea
@input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'description', plan.attributes.description)"
v-model="plan.attributes.description"
:placeholder="$t('admin_page_plans.form.description_plac')"
class="focus-border-theme input-dark"
></textarea>
</AppInputText>
<InfoBox style="margin-bottom: 0">
<p>{{ $t('Price change is not possible. If you would like to change your price or currency, please feel free to create a new plan.') }}</p>
</InfoBox>
</div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Features') }}
</FormLabel>
<InfoBox style="margin-bottom: 0">
<p>
{{ $t('Price change is not possible. If you would like to change your price or currency, please feel free to create a new plan.') }}
</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>
<!--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')" 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 in number')" type="number" min="1" max="999999999" class="focus-border-theme input-dark"/>
</AppInputText>
</div>
</div>
<!--Team Members-->
<AppInputText :title="$t('Max Team 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 in number')"
type="number"
min="1"
max="999999999"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
</div>
</template>
<script>
import SwitchInput from "../../../../components/Others/Forms/SwitchInput";
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch"
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import AppInputText from "../../../../components/Admin/AppInputText"
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import SwitchInput from '../../../../components/Others/Forms/SwitchInput'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import AppInputText from '../../../../components/Admin/AppInputText'
import InfoBox from '../../../../components/Others/Forms/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
}
}
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>

View File

@@ -1,91 +1,137 @@
<template>
<div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Details') }}
</FormLabel>
<div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Details') }}
</FormLabel>
<!--Name-->
<AppInputText :title="$t('admin_page_plans.form.name')">
<input @input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'name', plan.attributes.name)" v-model="plan.attributes.name" :placeholder="$t('admin_page_plans.form.name_plac')" type="text" class="focus-border-theme input-dark"/>
</AppInputText>
<!--Name-->
<AppInputText :title="$t('admin_page_plans.form.name')">
<input
@input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'name', plan.attributes.name)"
v-model="plan.attributes.name"
:placeholder="$t('admin_page_plans.form.name_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<!--Description-->
<AppInputText :title="$t('admin_page_plans.form.description')" :is-last="true">
<textarea @input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'description', plan.attributes.description)" v-model="plan.attributes.description" :placeholder="$t('admin_page_plans.form.description_plac')" class="focus-border-theme input-dark"></textarea>
</AppInputText>
</div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Charged Features') }}
</FormLabel>
<!--Description-->
<AppInputText :title="$t('admin_page_plans.form.description')" :is-last="true">
<textarea
@input="$updateInput('/subscriptions/admin/plans/' + $route.params.id, 'description', plan.attributes.description)"
v-model="plan.attributes.description"
:placeholder="$t('admin_page_plans.form.description_plac')"
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 Price per 1GB')" :description="$t('Charge your user by the amount of data he upload or download.')" 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>
<!--Bandwidth-->
<AppInputText
v-if="plan.attributes.features.bandwidth"
:title="$t('Bandwidth Price per 1GB')"
:description="$t('Charge your user by the amount of data he upload or download.')"
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 Price per 1GB')" :description="$t('Charge your user by the amount of data he has stored on the disk per 1GB.')" 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>
<!--Storage-->
<AppInputText
v-if="plan.attributes.features.storage"
:title="$t('Storage Price per 1GB')"
:description="$t('Charge your user by the amount of data he has stored on the disk per 1GB.')"
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('Price per 1 Member')" :description="$t('Charge your user by the total members he use in his Team Folders.')" 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>
<!--Member-->
<AppInputText
v-if="plan.attributes.features.member"
:title="$t('Price per 1 Member')"
:description="$t('Charge your user by the total members he use in his Team Folders.')"
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 per Cycle')" :description="$t('Charge monthly flat fee.')" 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>
<!--Flat Fee-->
<AppInputText v-if="plan.attributes.features.flatFee" :title="$t('Flat Fee per Cycle')" :description="$t('Charge monthly flat fee.')" 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 is not possible. If you would like to change your price or currency, please feel free to create a new plan.') }}</p>
</InfoBox>
</div>
</div>
<InfoBox style="margin-bottom: 0">
<p>
{{ $t('Price change is not possible. If you would like to change your price or currency, please feel free to create a new plan.') }}
</p>
</InfoBox>
</div>
</div>
</template>
<script>
import SwitchInput from "../../../../components/Others/Forms/SwitchInput";
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch"
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import AppInputText from "../../../../components/Admin/AppInputText"
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import SwitchInput from '../../../../components/Others/Forms/SwitchInput'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import AppInputText from '../../../../components/Admin/AppInputText'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
export default {
name: 'PlanMeteredSettings',
props: [
'plan'
],
components: {
AppInputSwitch,
AppInputText,
SwitchInput,
SelectInput,
FormLabel,
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)
},
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
}
}
},
created() {
this.visible = this.plan.attributes.visible
},
}
</script>

View File

@@ -1,179 +1,191 @@
<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 shadow-card overflow-x-auto">
<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)">
{{ 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>
<!--Table data content-->
<template slot-scope="{ row }">
<tr v-if="config.subscriptionType === 'metered'" class="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-3 md:pr-1 pr-3">
<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="text-sm font-bold block max-w-1 overflow-hidden text-ellipsis whitespace-nowrap" style="max-width: 155px;">
{{ row.data.relationships.user.data.attributes.name }}
</b>
<span class="block text-xs dark:text-gray-500 text-gray-600">
{{ row.data.relationships.user.data.attributes.email }}
</span>
</div>
</router-link>
</td>
<td class="md:px-1 px-3">
<span class="text-sm font-bold whitespace-nowrap">
{{ row.data.attributes.renews_at }}
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<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="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-3 md:pr-1 pr-3">
<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="text-sm font-bold block max-w-1 overflow-hidden text-ellipsis whitespace-nowrap" style="max-width: 155px;">
{{ row.data.relationships.user.data.attributes.name }}
</b>
<span class="block text-xs dark:text-gray-500 text-gray-600">
{{ row.data.relationships.user.data.attributes.email }}
</span>
</div>
</router-link>
</td>
<td class="md:px-1 px-3">
<ColorLabel :color="$getSubscriptionStatusColor(row.data.attributes.status)">
{{ row.data.attributes.status }}
</ColorLabel>
</td>
<td class="md:px-1 px-3">
<span class="text-sm font-bold capitalize text-limit" style="max-width: 160px">
{{ row.data.attributes.name }}
</span>
</td>
<td class="md:px-1 px-3">
<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="md:px-1 px-3">
<span class="text-sm font-bold">
{{ row.data.attributes.ends_at ? row.data.attributes.ends_at : '-' }}
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<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>
<!--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/Others/ColorLabel";
import MemberAvatar from "../../../../components/FilesView/MemberAvatar";
import DatatableCellImage from "../../../../components/Others/Tables/DatatableCellImage";
import {DownloadCloudIcon, Edit2Icon, Trash2Icon} from "vue-feather-icons"
import DatatableWrapper from "../../../../components/Others/Tables/DatatableWrapper";
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {mapGetters} from "vuex";
import ColorLabel from '../../../../components/Others/ColorLabel'
import MemberAvatar from '../../../../components/FilesView/MemberAvatar'
import DatatableCellImage from '../../../../components/Others/Tables/DatatableCellImage'
import { DownloadCloudIcon, Edit2Icon, Trash2Icon } from 'vue-feather-icons'
import DatatableWrapper from '../../../../components/Others/Tables/DatatableWrapper'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/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('admin_page_user.table.name'),
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('admin_page_user.table.name'),
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() {
export default {
name: 'PlanSubscribers',
components: {
DatatableCellImage,
DownloadCloudIcon,
DatatableWrapper,
PageTabGroup,
MemberAvatar,
ColorLabel,
Trash2Icon,
Edit2Icon,
PageTab,
InfoBox,
},
computed: {
...mapGetters(['config']),
columns() {
return {
subscribers: undefined,
isLoading: true,
}
metered: [
{
label: this.$t('admin_page_user.table.name'),
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('admin_page_user.table.name'),
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>

View File

@@ -1,130 +1,140 @@
<template>
<div>
<!--Datatable-->
<DatatableWrapper v-if="! config.isEmptySubscriptions" @init="isLoading = false" api="/api/subscriptions/admin" :paginator="true" :columns="columns" class="card shadow-card overflow-x-auto">
<!--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)">
{{ 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>
<!--Table data content-->
<template slot-scope="{ row }">
<tr class="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-5 md:pr-1 pr-3">
<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="text-sm font-bold block max-w-1 overflow-hidden text-ellipsis whitespace-nowrap" style="max-width: 155px;">
{{ row.data.relationships.user.data.attributes.name }}
</b>
<span class="block text-xs dark:text-gray-500 text-gray-600">
{{ row.data.relationships.user.data.attributes.email }}
</span>
</div>
</router-link>
</td>
<td class="md:px-1 px-3">
<ColorLabel :color="$getSubscriptionStatusColor(row.data.attributes.status)">
{{ row.data.attributes.status }}
</ColorLabel>
</td>
<td class="md:px-1 px-3">
<span class="text-sm font-bold capitalize text-limit" 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="md:px-1 px-3">
<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="md:px-1 px-3">
<span class="text-sm font-bold">
{{ row.data.attributes.ends_at ? row.data.attributes.ends_at : '-' }}
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<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 h-full items-center justify-center">
<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" />
<!--Empty State-->
<div v-if="config.isEmptySubscriptions" class="flex items-center justify-center h-full">
<div class="text-center">
<img class="w-28 inline-block mb-6" 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>
<h1 class="text-2xl font-bold mb-1">
{{ $t("There is Nothing") }}
</h1>
<p class="text-sm text-gray-600">
{{ $t('All your subscriptions will be visible here') }}
</p>
</div>
</div>
<p class="text-sm text-gray-600">
{{ $t('All your subscriptions will be visible here') }}
</p>
</div>
</div>
</div>
</template>
<script>
import ColorLabel from "../../components/Others/ColorLabel";
import MemberAvatar from "../../components/FilesView/MemberAvatar";
import DatatableWrapper from "../../components/Others/Tables/DatatableWrapper";
import { mapGetters } from 'vuex'
import ColorLabel from '../../components/Others/ColorLabel'
import MemberAvatar from '../../components/FilesView/MemberAvatar'
import DatatableWrapper from '../../components/Others/Tables/DatatableWrapper'
import { mapGetters } from 'vuex'
export default {
name: 'Subscriptions',
components: {
ColorLabel,
MemberAvatar,
DatatableWrapper,
},
computed: {
...mapGetters([
'config',
]),
},
data() {
return {
isLoading: true,
columns: [
{
label: this.$t('admin_page_user.table.name'),
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
},
],
}
export default {
name: 'Subscriptions',
components: {
ColorLabel,
MemberAvatar,
DatatableWrapper,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: true,
columns: [
{
label: this.$t('admin_page_user.table.name'),
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>

View File

@@ -2,190 +2,221 @@
<div>
<div class="card shadow-card">
<div class="mb-6">
<router-link :to="{name: 'UserCreate'}">
<router-link :to="{ name: 'UserCreate' }">
<MobileActionButton icon="user-plus">
{{ $t('admin_page_user.create_user.submit') }}
</MobileActionButton>
</router-link>
<MobileActionButton @click.native="$openSpotlight('users')" icon="search">
{{ $t('Search') }}
</MobileActionButton>
<MobileActionButton @click.native="$openSpotlight('users')" icon="search">
{{ $t('Search') }}
</MobileActionButton>
</div>
<!--Datatable-->
<!--Datatable-->
<DatatableWrapper @init="isLoading = false" api="/api/admin/users" :paginator="true" :columns="columns" class="overflow-x-auto">
<template slot-scope="{ row }">
<!--Not a subscription-->
<tr v-if="config.subscriptionType === 'none'" class="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-3 md:pr-1 pr-3">
<router-link :to="{name: 'UserDetail', params: {id: row.data.id}}">
<div class="flex items-center">
<MemberAvatar
:is-border="false"
:size="44"
:member="row.data.relationships.settings"
/>
<div class="ml-3 pr-10">
<b class="text-sm font-bold block max-w-1 overflow-hidden text-ellipsis whitespace-nowrap" style="max-width: 155px;">
{{ row.data.relationships.settings.data.attributes.name }}
</b>
<span class="block text-xs dark:text-gray-500 text-gray-600">
{{ row.data.attributes.email }}
</span>
</div>
</div>
</router-link>
<!--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">
<router-link
:to="{
name: 'UserDetail',
params: { id: row.data.id },
}"
>
<div class="flex items-center">
<MemberAvatar :is-border="false" :size="44" :member="row.data.relationships.settings" />
<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="md:px-1 px-3">
<td class="px-3 md:px-1">
<ColorLabel :color="$getUserRoleColor(row.data.attributes.role)">
{{ row.data.attributes.role }}
</ColorLabel>
</td>
<td class="md:px-1 px-3">
<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">
-
{{ 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="md:px-1 px-3" v-if="config.storageLimit">
<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">
-
{{ 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="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.created_at }}
{{ row.data.attributes.created_at }}
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<div class="flex space-x-2 w-full justify-end">
<router-link class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-green-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'UserDetail', params: {id: row.data.id}}">
<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 items-center justify-center w-8 h-8 rounded-md hover:bg-red-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'UserDelete', params: {id: row.data.id}}">
<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>
<!--Fixed subscription-->
<tr v-if="config.subscriptionType === 'fixed'" class="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-3 md:pr-1 pr-3">
<router-link :to="{name: 'UserDetail', params: {id: row.data.id}}">
<div class="flex items-center">
<MemberAvatar
:is-border="false"
:size="44"
:member="row.data.relationships.settings"
/>
<div class="ml-3 pr-10">
<b class="text-sm font-bold block max-w-1 overflow-hidden text-ellipsis whitespace-nowrap" style="max-width: 155px;">
{{ row.data.relationships.settings.data.attributes.name }}
</b>
<span class="block text-xs dark:text-gray-500 text-gray-600">
{{ row.data.attributes.email }}
</span>
</div>
</div>
</router-link>
<!--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.data.relationships.settings" />
<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="md:px-1 px-3">
<td class="px-3 md:px-1">
<ColorLabel :color="$getUserRoleColor(row.data.attributes.role)">
{{ row.data.attributes.role }}
</ColorLabel>
</td>
<td class="md:px-1 px-3" v-if="config.isSaaS">
<td class="px-3 md:px-1" v-if="config.isSaaS">
<span class="text-sm font-bold">
{{ row.data.relationships.subscription ? $t('global.premium') : $t('global.free') }}
{{ row.data.relationships.subscription ? $t('global.premium') : $t('global.free') }}
</span>
</td>
<td class="md:px-1 px-3">
<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">
-
{{ 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="md:px-1 px-3" v-if="config.storageLimit">
<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">
-
{{ 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="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.created_at }}
{{ row.data.attributes.created_at }}
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<div class="flex space-x-2 w-full justify-end">
<router-link class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-green-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'UserDetail', params: {id: row.data.id}}">
<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 items-center justify-center w-8 h-8 rounded-md hover:bg-red-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'UserDelete', params: {id: row.data.id}}">
<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="border-b dark:border-opacity-5 border-light border-dashed whitespace-nowrap">
<td class="py-3 md:pr-1 pr-3">
<router-link :to="{name: 'UserDetail', params: {id: row.data.id}}">
<div class="flex items-center">
<MemberAvatar
:is-border="false"
:size="44"
:member="row.data.relationships.settings"
/>
<div class="ml-3 pr-10">
<b class="text-sm font-bold block max-w-1 overflow-hidden text-ellipsis whitespace-nowrap" style="max-width: 155px;">
{{ row.data.relationships.settings.data.attributes.name }}
</b>
<span class="block text-xs dark:text-gray-500 text-gray-600">
{{ row.data.attributes.email }}
</span>
</div>
</div>
</router-link>
<!--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.data.relationships.settings" />
<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="md:px-1 px-3">
<td class="px-3 md:px-1">
<ColorLabel :color="$getUserRoleColor(row.data.attributes.role)">
{{ row.data.attributes.role }}
</ColorLabel>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.meta.usages.featureEstimates.storage.usage }}
{{ row.data.meta.usages.featureEstimates.storage.usage }}
</span>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.meta.usages.costEstimate }}
{{ row.data.meta.usages.costEstimate }}
</span>
</td>
<td class="md:px-1 px-3">
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.created_at }}
{{ row.data.attributes.created_at }}
</span>
</td>
<td class="md:pl-1 pl-3 text-right">
<div class="flex space-x-2 w-full justify-end">
<router-link class="flex items-center justify-center w-8 h-8 rounded-md hover:bg-green-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'UserDetail', params: {id: row.data.id}}">
<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 items-center justify-center w-8 h-8 rounded-md hover:bg-red-100 dark:bg-2x-dark-foreground bg-light-background transition-colors" :to="{name: 'UserDelete', params: {id: row.data.id}}">
<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>
@@ -201,142 +232,140 @@
</template>
<script>
import MemberAvatar from "../../components/FilesView/MemberAvatar";
import DatatableCellImage from "../../components/Others/Tables/DatatableCellImage";
import DatatableWrapper from "../../components/Others/Tables/DatatableWrapper";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileHeader from "../../components/Mobile/MobileHeader";
import SectionTitle from "../../components/Others/SectionTitle";
import ButtonBase from "../../components/FilesView/ButtonBase";
import PageHeader from "../../components/Others/PageHeader";
import ColorLabel from "../../components/Others/ColorLabel";
import Spinner from "../../components/FilesView/Spinner";
import {Trash2Icon, Edit2Icon} from "vue-feather-icons";
import {mapGetters} from "vuex"
import axios from 'axios'
import MemberAvatar from '../../components/FilesView/MemberAvatar'
import DatatableCellImage from '../../components/Others/Tables/DatatableCellImage'
import DatatableWrapper from '../../components/Others/Tables/DatatableWrapper'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileHeader from '../../components/Mobile/MobileHeader'
import SectionTitle from '../../components/Others/SectionTitle'
import ButtonBase from '../../components/FilesView/ButtonBase'
import PageHeader from '../../components/Others/PageHeader'
import ColorLabel from '../../components/Others/ColorLabel'
import Spinner from '../../components/FilesView/Spinner'
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'Users',
components: {
DatatableCellImage,
MobileActionButton,
DatatableWrapper,
MemberAvatar,
SectionTitle,
MobileHeader,
Trash2Icon,
PageHeader,
ButtonBase,
ColorLabel,
Edit2Icon,
Spinner,
},
computed: {
...mapGetters([
'config'
]),
columns() {
return {
metered: [
{
label: this.$t('admin_page_user.table.name'),
field: 'email',
sortable: true
},
{
label: this.$t('admin_page_user.table.role'),
field: 'role',
sortable: true
},
{
label: this.$t('admin_page_user.table.storage_used'),
sortable: false
},
{
label: this.$t('Billing Est.'),
sortable: false,
},
{
label: this.$t('admin_page_user.table.created_at'),
field: 'created_at',
sortable: true
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false
},
],
fixed: [
{
label: this.$t('admin_page_user.table.name'),
field: 'email',
sortable: true
},
{
label: this.$t('admin_page_user.table.role'),
field: 'role',
sortable: true
},
{
label: this.$t('admin_page_user.table.plan'),
sortable: false,
},
{
label: this.$t('admin_page_user.table.storage_used'),
sortable: false
},
{
label: this.$t('Max Storage'),
sortable: false,
hidden: ! this.config.storageLimit,
},
{
label: this.$t('admin_page_user.table.created_at'),
field: 'created_at',
sortable: true
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false
},
],
none: [
{
label: this.$t('admin_page_user.table.name'),
field: 'email',
sortable: true
},
{
label: this.$t('admin_page_user.table.role'),
field: 'role',
sortable: true
},
{
label: this.$t('admin_page_user.table.storage_used'),
sortable: false
},
{
label: this.$t('Max Storage'),
sortable: false,
hidden: ! this.config.storageLimit,
},
{
label: this.$t('admin_page_user.table.created_at'),
field: 'created_at',
sortable: true
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false
},
],
}[this.config.subscriptionType]
}
},
data() {
export default {
name: 'Users',
components: {
DatatableCellImage,
MobileActionButton,
DatatableWrapper,
MemberAvatar,
SectionTitle,
MobileHeader,
Trash2Icon,
PageHeader,
ButtonBase,
ColorLabel,
Edit2Icon,
Spinner,
},
computed: {
...mapGetters(['config']),
columns() {
return {
isLoading: true,
}
metered: [
{
label: this.$t('admin_page_user.table.name'),
field: 'email',
sortable: true,
},
{
label: this.$t('admin_page_user.table.role'),
field: 'role',
sortable: true,
},
{
label: this.$t('admin_page_user.table.storage_used'),
sortable: false,
},
{
label: this.$t('Billing Est.'),
sortable: false,
},
{
label: this.$t('admin_page_user.table.created_at'),
field: 'created_at',
sortable: true,
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false,
},
],
fixed: [
{
label: this.$t('admin_page_user.table.name'),
field: 'email',
sortable: true,
},
{
label: this.$t('admin_page_user.table.role'),
field: 'role',
sortable: true,
},
{
label: this.$t('admin_page_user.table.plan'),
sortable: false,
},
{
label: this.$t('admin_page_user.table.storage_used'),
sortable: false,
},
{
label: this.$t('Max Storage'),
sortable: false,
hidden: !this.config.storageLimit,
},
{
label: this.$t('admin_page_user.table.created_at'),
field: 'created_at',
sortable: true,
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false,
},
],
none: [
{
label: this.$t('admin_page_user.table.name'),
field: 'email',
sortable: true,
},
{
label: this.$t('admin_page_user.table.role'),
field: 'role',
sortable: true,
},
{
label: this.$t('admin_page_user.table.storage_used'),
sortable: false,
},
{
label: this.$t('Max Storage'),
sortable: false,
hidden: !this.config.storageLimit,
},
{
label: this.$t('admin_page_user.table.created_at'),
field: 'created_at',
sortable: true,
},
{
label: this.$t('admin_page_user.table.action'),
sortable: false,
},
],
}[this.config.subscriptionType]
},
}
},
data() {
return {
isLoading: true,
}
},
}
</script>

View File

@@ -1,39 +1,38 @@
<template>
<div>
<div id="page-content" v-if="! isLoading">
<!--Page Tab links-->
<div class="card shadow-card pt-4 sticky top-0 z-10" style="padding-bottom: 0;">
<div id="page-content" v-if="!isLoading">
<!--Page Tab links-->
<div class="card sticky top-0 z-10 pt-4 shadow-card" style="padding-bottom: 0">
<!--User thumbnail-->
<div class="mb-3 flex items-center">
<!--Image input for replace avatar-->
<img
:src="user.data.relationships.settings.data.attributes.avatar.sm"
:alt="user.data.relationships.settings.data.attributes.name"
class="relative z-0 h-14 w-14 cursor-pointer rounded-xl object-cover shadow-lg md:h-16 md:w-16"
/>
<!--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 }}
{{ user.data.relationships.settings.data.attributes.last_name }}
<!--User thumbnail-->
<div class="flex items-center mb-3">
<ColorLabel color="purple">
{{ user.data.attributes.role }}
</ColorLabel>
</b>
<small class="block text-xs text-gray-600 sm:text-sm">
{{ user.data.attributes.email }}
</small>
</div>
</div>
<!--Image input for replace avatar-->
<img
:src="user.data.relationships.settings.data.attributes.avatar.sm" :alt="user.data.relationships.settings.data.attributes.name"
class="md:w-16 w-14 md:h-16 h-14 object-cover rounded-xl relative z-0 shadow-lg cursor-pointer"
/>
<CardNavigation :pages="pages" class="-mx-1" />
</div>
<!--User name & email-->
<div class="ml-4">
<b class="sm:text-lg text-md font-bold block">
{{ user.data.relationships.settings.data.attributes.first_name }} {{ user.data.relationships.settings.data.attributes.last_name }}
<ColorLabel color="purple">
{{ user.data.attributes.role }}
</ColorLabel>
</b>
<small class="sm:text-sm text-xs text-gray-600 block">
{{ user.data.attributes.email }}
</small>
</div>
</div>
<CardNavigation :pages="pages" class="-mx-1" />
</div>
<!--Router Content-->
<router-view :user="user" @reload-user="fetchUser"/>
<!--Router Content-->
<router-view :user="user" @reload-user="fetchUser" />
</div>
<div id="loader" v-if="isLoading">
<Spinner />
@@ -42,110 +41,107 @@
</template>
<script>
import CardNavigation from "../../../components/Admin/CardNavigation";
import {UserIcon, HardDriveIcon, LockIcon, Trash2Icon, FileTextIcon, CreditCardIcon} from 'vue-feather-icons'
import MobileHeader from "../../../components/Mobile/MobileHeader";
import SectionTitle from "../../../components/Others/SectionTitle";
import PageHeader from "../../../components/Others/PageHeader";
import ColorLabel from "../../../components/Others/ColorLabel";
import Spinner from "../../../components/FilesView/Spinner";
import {events} from '../../../bus'
import {mapGetters} from 'vuex'
import axios from 'axios'
import CardNavigation from '../../../components/Admin/CardNavigation'
import { UserIcon, HardDriveIcon, LockIcon, Trash2Icon, FileTextIcon, CreditCardIcon } from 'vue-feather-icons'
import MobileHeader from '../../../components/Mobile/MobileHeader'
import SectionTitle from '../../../components/Others/SectionTitle'
import PageHeader from '../../../components/Others/PageHeader'
import ColorLabel from '../../../components/Others/ColorLabel'
import Spinner from '../../../components/FilesView/Spinner'
import { events } from '../../../bus'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'Profile',
components: {
CardNavigation,
CreditCardIcon,
HardDriveIcon,
SectionTitle,
FileTextIcon,
MobileHeader,
PageHeader,
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('admin_page_user.tabs.detail'),
route: 'UserDetail',
},
{
title: this.$t('Storage'),
route: 'UserStorage',
},
{
title: this.$t('admin_page_user.tabs.password'),
route: 'UserPassword',
},
{
title: this.$t('Delete Account'),
route: 'UserDelete',
},
]
}
return [
{
title: this.$t('admin_page_user.tabs.detail'),
route: 'UserDetail',
},
{
title: this.$t('Storage'),
route: 'UserStorage',
},
{
title: this.$t('Billing'),
route: 'UserSubscription',
},
{
title: this.$t('admin_page_user.tabs.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() {
export default {
name: 'Profile',
components: {
CardNavigation,
CreditCardIcon,
HardDriveIcon,
SectionTitle,
FileTextIcon,
MobileHeader,
PageHeader,
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('admin_page_user.tabs.detail'),
route: 'UserDetail',
},
{
title: this.$t('Storage'),
route: 'UserStorage',
},
{
title: this.$t('admin_page_user.tabs.password'),
route: 'UserPassword',
},
{
title: this.$t('Delete Account'),
route: 'UserDelete',
},
]
}
events.$on('reload:user', () => this.fetchUser())
return [
{
title: this.$t('admin_page_user.tabs.detail'),
route: 'UserDetail',
},
{
title: this.$t('Storage'),
route: 'UserStorage',
},
{
title: this.$t('Billing'),
route: 'UserSubscription',
},
{
title: this.$t('admin_page_user.tabs.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())
},
}
</script>

View File

@@ -3,37 +3,61 @@
<div class="card shadow-card">
<FormLabel>{{ $t('admin_page_user.create_user.group_details') }}</FormLabel>
<!--Avatar-->
<ValidationProvider tag="div" mode="passive" name="avatar" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_user.create_user.avatar')" :error="errors[0]">
<ImageInput v-model="user.avatar" :error="errors[0]" />
<!--Avatar-->
<ValidationProvider tag="div" mode="passive" name="avatar" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_user.create_user.avatar')" :error="errors[0]">
<ImageInput v-model="user.avatar" :error="errors[0]" />
</AppInputText>
</ValidationProvider>
</ValidationProvider>
<!--Email-->
<!--Email-->
<ValidationProvider tag="div" mode="passive" name="email" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('page_registration.label_email')" :error="errors[0]">
<input v-model="user.email" :placeholder="$t('admin_page_user.create_user.label_email')" type="email" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}"/>
<input
v-model="user.email"
:placeholder="$t('admin_page_user.create_user.label_email')"
type="email"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<!--Name-->
<!--Name-->
<ValidationProvider tag="div" mode="passive" name="user name" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('page_registration.label_name')" :error="errors[0]">
<input v-model="user.name" :placeholder="$t('admin_page_user.create_user.label_name')" type="text" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}"/>
<input
v-model="user.name"
:placeholder="$t('admin_page_user.create_user.label_name')"
type="text"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<!--Password-->
<!--Password-->
<div class="flex space-x-4">
<ValidationProvider tag="div" mode="passive" name="password" rules="required" v-slot="{ errors }" class="w-full">
<AppInputText :title="$t('page_registration.label_pass')" :error="errors[0]">
<input v-model="user.password" :placeholder="$t('page_registration.placeholder_pass')" type="password" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}"/>
<input
v-model="user.password"
:placeholder="$t('page_registration.placeholder_pass')"
type="password"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="password confirm" rules="required" v-slot="{ errors }" class="w-full">
<AppInputText :title="$t('page_registration.label_confirm_pass')" :error="errors[0]">
<input v-model="user.password_confirmation" :placeholder="$t('admin_page_user.create_user.label_conf_pass')" type="password" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}"/>
<input
v-model="user.password_confirmation"
:placeholder="$t('admin_page_user.create_user.label_conf_pass')"
type="password"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
</div>
@@ -41,17 +65,25 @@
<div class="card shadow-card">
<FormLabel>{{ $t('admin_page_user.create_user.group_settings') }}</FormLabel>
<!--User Role-->
<!--User Role-->
<ValidationProvider tag="div" mode="passive" name="permission" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_user.select_role')" :error="errors[0]">
<SelectInput v-model="user.role" :options="$translateSelectOptions(roles)" :placeholder="$t('admin_page_user.select_role')" :isError="errors[0]"/>
<SelectInput v-model="user.role" :options="$translateSelectOptions(roles)" :placeholder="$t('admin_page_user.select_role')" :isError="errors[0]" />
</AppInputText>
</ValidationProvider>
<!--Storage Capacity-->
<!--Storage Capacity-->
<ValidationProvider tag="div" mode="passive" name="storage capacity" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_page_user.label_change_capacity')" :error="errors[0]">
<input v-model="user.max_storage_amount" min="1" max="999999999" :placeholder="$t('admin_page_user.label_change_capacity')" type="number" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}"/>
<input
v-model="user.max_storage_amount"
min="1"
max="999999999"
:placeholder="$t('admin_page_user.label_change_capacity')"
type="number"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
</div>
@@ -64,139 +96,133 @@
</template>
<script>
import AppInputText from "../../../components/Admin/AppInputText";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import SelectInput from "../../../components/Others/Forms/SelectInput";
import ImageInput from "../../../components/Others/Forms/ImageInput";
import FormLabel from "../../../components/Others/Forms/FormLabel";
import MobileHeader from "../../../components/Mobile/MobileHeader";
import SectionTitle from "../../../components/Others/SectionTitle";
import ButtonBase from "../../../components/FilesView/ButtonBase";
import PageHeader from "../../../components/Others/PageHeader";
import {required} from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import {events} from '../../../bus'
import axios from 'axios'
import AppInputText from '../../../components/Admin/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SelectInput from '../../../components/Others/Forms/SelectInput'
import ImageInput from '../../../components/Others/Forms/ImageInput'
import FormLabel from '../../../components/Others/Forms/FormLabel'
import MobileHeader from '../../../components/Mobile/MobileHeader'
import SectionTitle from '../../../components/Others/SectionTitle'
import ButtonBase from '../../../components/FilesView/ButtonBase'
import PageHeader from '../../../components/Others/PageHeader'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import { events } from '../../../bus'
import axios from 'axios'
export default {
name: 'Profile',
components: {
AppInputText,
ValidationProvider,
ValidationObserver,
SectionTitle,
MobileHeader,
SelectInput,
ButtonBase,
ImageInput,
PageHeader,
FormLabel,
required,
},
computed: {
...mapGetters(['roles']),
},
data() {
return {
isLoading: false,
user: {
role: '',
avatar: undefined,
name: '',
email: '',
password: '',
password_confirmation: '',
max_storage_amount: 5,
},
}
},
methods: {
async createUser() {
export default {
name: 'Profile',
components: {
AppInputText,
ValidationProvider,
ValidationObserver,
SectionTitle,
MobileHeader,
SelectInput,
ButtonBase,
ImageInput,
PageHeader,
FormLabel,
required,
},
computed: {
...mapGetters(['roles']),
},
data() {
return {
isLoading: false,
user: {
role: '',
avatar: undefined,
name: '',
email: '',
password: '',
password_confirmation: '',
max_storage_amount: 5,
},
}
},
methods: {
async createUser() {
// Validate fields
const isValid = await this.$refs.createUser.validate()
// Validate fields
const isValid = await this.$refs.createUser.validate();
if (!isValid) return
if (!isValid) return;
// Start loading
this.isLoading = true
// Start loading
this.isLoading = true
// Create form
let formData = new FormData()
// Create form
let formData = new FormData()
// Add image to form
formData.append('name', this.user.name)
formData.append('role', this.user.role)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('max_storage_amount', this.user.max_storage_amount)
formData.append('password_confirmation', this.user.password_confirmation)
// Add image to form
formData.append('name', this.user.name)
formData.append('role', this.user.role)
formData.append('email', this.user.email)
formData.append('password', this.user.password)
formData.append('max_storage_amount', this.user.max_storage_amount)
formData.append('password_confirmation', this.user.password_confirmation)
// Append avatar if exist
if (this.user.avatar) formData.append('avatar', this.user.avatar)
// Append avatar if exist
if (this.user.avatar)
formData.append('avatar', this.user.avatar)
// Send request to get user token
axios
.post('/api/admin/users', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => {
// End loading
this.isLoading = false
// Send request to get user token
axios
.post('/api/admin/users', formData, {
headers: {
'Content-Type': 'multipart/form-data',
}
// Show toaster
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.created_user'),
})
.then(response => {
// End loading
this.isLoading = false
// Show toaster
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.created_user'),
})
// Go to User page
this.$router.push({name: 'UserDetail', params: {id: response.data.data.id}})
// Go to User page
this.$router.push({
name: 'UserDetail',
params: { id: response.data.data.id },
})
.catch(error => {
// Validation errors
if (error.response.status == 422) {
// Email validation error
if (error.response.data.errors['email']) {
this.$refs.createUser.setErrors({
'email': error.response.data.errors['email']
});
}
// Password validation error
if (error.response.data.errors['password']) {
this.$refs.createUser.setErrors({
'password': error.response.data.errors['password']
});
}
// Password validation error
if (error.response.data.errors['max_storage_amount']) {
this.$refs.createUser.setErrors({
'storage capacity': this.$t('errors.capacity_digit')
});
}
} else {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
.catch((error) => {
// Validation errors
if (error.response.status == 422) {
// Email validation error
if (error.response.data.errors['email']) {
this.$refs.createUser.setErrors({
email: error.response.data.errors['email'],
})
}
// End loading
this.isLoading = false
})
}
// Password validation error
if (error.response.data.errors['password']) {
this.$refs.createUser.setErrors({
password: error.response.data.errors['password'],
})
}
// Password validation error
if (error.response.data.errors['max_storage_amount']) {
this.$refs.createUser.setErrors({
'storage capacity': this.$t('errors.capacity_digit'),
})
}
} else {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
}
// End loading
this.isLoading = false
})
},
}
},
}
</script>

View File

@@ -1,123 +1,125 @@
<template>
<div v-if="user" class="card shadow-card">
<FormLabel>
{{ $t('user_box_delete.title') }}
</FormLabel>
<ValidationObserver ref="deleteUser" @submit.prevent="deleteUser" v-slot="{ invalid }" tag="form">
<ValidationProvider tag="div" v-slot="{ errors }" mode="passive" name="User name" rules="required">
<AppInputText :title="$t('admin_page_user.label_delete_user', {user: user.data.relationships.settings.data.attributes.name})" :description="$t('user_box_delete.description')" :error="errors[0]" :is-last="true">
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
<input v-model="userName"
:placeholder="$t('admin_page_user.placeholder_delete_user')"
type="text"
class="focus-border-theme input-dark"
:class="{'border-red': errors[0]}"
/>
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="danger" class="sm:w-auto w-full">
{{ $t('admin_page_user.delete_user') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
<div v-if="user" class="card shadow-card">
<FormLabel>
{{ $t('user_box_delete.title') }}
</FormLabel>
<ValidationObserver ref="deleteUser" @submit.prevent="deleteUser" v-slot="{ invalid }" tag="form">
<ValidationProvider tag="div" v-slot="{ errors }" mode="passive" name="User name" rules="required">
<AppInputText
:title="
$t('admin_page_user.label_delete_user', {
user: user.data.relationships.settings.data.attributes.name,
})
"
:description="$t('user_box_delete.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="userName"
:placeholder="$t('admin_page_user.placeholder_delete_user')"
type="text"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="danger" class="w-full sm:w-auto">
{{ $t('admin_page_user.delete_user') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
</template>
<script>
import AppInputText from "../../../../components/Admin/AppInputText";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import AppInputText from '../../../../components/Admin/AppInputText'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import PageTab from "../../../../components/Others/Layout/PageTab";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import {required, is} from 'vee-validate/dist/rules'
import {events} from '../../../../bus'
import axios from 'axios'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import PageTab from '../../../../components/Others/Layout/PageTab'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import { required, is } from 'vee-validate/dist/rules'
import { events } from '../../../../bus'
import axios from 'axios'
export default {
name: 'UserDelete',
props: [
'user'
],
components: {
AppInputText,
FormLabel,
InfoBox,
PageTabGroup,
PageTab,
ValidationProvider,
ValidationObserver,
ButtonBase,
SetupBox,
required,
},
data() {
return {
isSendingRequest: false,
isLoading: false,
userName: '',
export default {
name: 'UserDelete',
props: ['user'],
components: {
AppInputText,
FormLabel,
InfoBox,
PageTabGroup,
PageTab,
ValidationProvider,
ValidationObserver,
ButtonBase,
SetupBox,
required,
},
data() {
return {
isSendingRequest: false,
isLoading: false,
userName: '',
}
},
methods: {
async deleteUser() {
// Validate fields
const isValid = await this.$refs.deleteUser.validate()
if (!isValid) return
if (this.userName !== this.user.data.relationships.settings.data.attributes.name) {
this.$refs.deleteUser.setErrors({
'User name': 'The user name is not the same.',
})
return
}
},
methods: {
async deleteUser() {
// Validate fields
const isValid = await this.$refs.deleteUser.validate();
if (!isValid) return;
if (this.userName !== this.user.data.relationships.settings.data.attributes.name) {
this.$refs.deleteUser.setErrors({
'User name': 'The user name is not the same.'
});
return
}
this.isSendingRequest = true
axios
.post(this.$store.getters.api + '/admin/users/' + this.$route.params.id + '/delete',
{
name: this.userName,
_method: 'delete'
}
)
.then((response) => {
if (response.status === 202) {
events.$emit('alert:open', {
emoji: '☹️',
title: this.$t('popup_deleted_user_aborted.title'),
message: this.$t('popup_deleted_user_aborted.message'),
})
}
if (response.status === 204) {
events.$emit('success:open', {
emoji: '👍',
title: this.$t('popup_deleted_user.title'),
message: this.$t('popup_deleted_user.message'),
})
this.$router.push({name: 'Users'})
}
})
.catch(() => {
this.isSendingRequest = true
axios
.post(this.$store.getters.api + '/admin/users/' + this.$route.params.id + '/delete', {
name: this.userName,
_method: 'delete',
})
.then((response) => {
if (response.status === 202) {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
emoji: '☹️',
title: this.$t('popup_deleted_user_aborted.title'),
message: this.$t('popup_deleted_user_aborted.message'),
})
}
if (response.status === 204) {
events.$emit('success:open', {
emoji: '👍',
title: this.$t('popup_deleted_user.title'),
message: this.$t('popup_deleted_user.message'),
})
this.$router.push({ name: 'Users' })
}
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
.finally(() => {
this.isSendingRequest = false
})
}
})
.finally(() => {
this.isSendingRequest = false
})
},
}
},
}
</script>

View File

@@ -7,193 +7,197 @@
</FormLabel>
<ValidationObserver ref="changeRole" @submit.prevent="changeRole" v-slot="{ invalid }" tag="form">
<ValidationProvider tag="div" v-slot="{ errors }" mode="passive" name="Role" rules="required">
<AppInputText :title="$t('admin_page_user.select_role')" :description="$t('user_box_role.description')" :error="errors[0]" :is-last="true">
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
<SelectInput v-model="userRole" :options="$translateSelectOptions(roles)" :placeholder="$t('admin_page_user.select_role')" :isError="errors[0]" />
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="theme" class="sm:w-auto w-full">
{{ $t('admin_page_user.save_role') }}
</ButtonBase>
</div>
</AppInputText>
<AppInputText :title="$t('admin_page_user.select_role')" :description="$t('user_box_role.description')" :error="errors[0]" :is-last="true">
<div class="space-y-4 sm:flex sm:space-x-4 sm:space-y-0">
<SelectInput v-model="userRole" :options="$translateSelectOptions(roles)" :placeholder="$t('admin_page_user.select_role')" :isError="errors[0]" />
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="theme" class="w-full sm:w-auto">
{{ $t('admin_page_user.save_role') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
<div class="card shadow-card">
<FormLabel>
{{ $t('admin_page_user.label_person_info') }}
</FormLabel>
{{ $t('admin_page_user.label_person_info') }}
</FormLabel>
<!--Name-->
<div class="md:flex justify-items md:space-x-4">
<AppInputText :title="$t('First Name')" class="w-full">
<input
disabled
:value="user.data.relationships.settings.data.attributes.first_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('Last Name')" class="w-full">
<input
disabled
:value="user.data.relationships.settings.data.attributes.last_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
</AppInputText>
</div>
<!--Name-->
<div class="justify-items md:flex md:space-x-4">
<AppInputText :title="$t('First Name')" class="w-full">
<input
disabled
:value="user.data.relationships.settings.data.attributes.first_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
/>
</AppInputText>
<AppInputText :title="$t('Last Name')" class="w-full">
<input
disabled
:value="user.data.relationships.settings.data.attributes.last_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
/>
</AppInputText>
</div>
<AppInputText :title="$t('page_registration.label_name')" :is-last="true">
<input :value="user.data.relationships.settings.data.attributes.name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
disabled
/>
<input
:value="user.data.relationships.settings.data.attributes.name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme input-dark disabled:text-gray-900 disabled:opacity-100"
disabled
/>
</AppInputText>
</div>
<div class="card shadow-card">
<FormLabel>{{ $t('user_settings.title_billing') }}</FormLabel>
<AppInputText :title="$t('user_settings.name')">
<input :value="user.data.relationships.settings.data.attributes.name"
type="text"
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
disabled
/>
<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('user_settings.address')">
<input :value="user.data.relationships.settings.data.attributes.address"
type="text"
disabled
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
<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('user_settings.country')">
<input :value="user.data.relationships.settings.data.attributes.country"
type="text"
disabled
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
<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('user_settings.city')" class="w-full">
<input :value="user.data.relationships.settings.data.attributes.city"
type="text"
disabled
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
<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('user_settings.postal_code')" class="w-full">
<input :value="user.data.relationships.settings.data.attributes.postal_code"
type="text"
disabled
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
<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('user_settings.state')">
<input :value="user.data.relationships.settings.data.attributes.state"
type="text"
disabled
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
<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('user_settings.phone_number')" :is-last="true">
<input :value="user.data.relationships.settings.data.attributes.phone_number"
type="text"
disabled
class="disabled:text-gray-900 disabled:opacity-100 focus-border-theme input-dark"
/>
<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>
<script>
import AppInputText from "../../../../components/Admin/AppInputText";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import PageTab from "../../../../components/Others/Layout/PageTab";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import SelectInput from "../../../../components/Others/Forms/SelectInput";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '../../../../bus'
import axios from 'axios'
import AppInputText from '../../../../components/Admin/AppInputText'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import PageTab from '../../../../components/Others/Layout/PageTab'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SelectInput from '../../../../components/Others/Forms/SelectInput'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import { events } from '../../../../bus'
import axios from 'axios'
export default {
name: 'UserDetail',
props: [
'user'
],
components: {
AppInputText,
PageTabGroup,
PageTab,
InfoBox,
FormLabel,
ValidationProvider,
ValidationObserver,
SelectInput,
ButtonBase,
SetupBox,
required,
},
computed: {
...mapGetters(['roles', 'config']),
},
data() {
return {
isLoading: false,
isSendingRequest: false,
userRole: undefined,
}
},
methods: {
async changeRole() {
export default {
name: 'UserDetail',
props: ['user'],
components: {
AppInputText,
PageTabGroup,
PageTab,
InfoBox,
FormLabel,
ValidationProvider,
ValidationObserver,
SelectInput,
ButtonBase,
SetupBox,
required,
},
computed: {
...mapGetters(['roles', 'config']),
},
data() {
return {
isLoading: false,
isSendingRequest: false,
userRole: undefined,
}
},
methods: {
async changeRole() {
// Validate fields
const isValid = await this.$refs.changeRole.validate()
// Validate fields
const isValid = await this.$refs.changeRole.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 + '/role', {
attributes: {
role: this.userRole,
},
_method: 'patch',
})
.then(() => {
// Reset errors
this.$refs.changeRole.reset()
// Send request to get user reset link
axios
.post(this.$store.getters.api + '/admin/users/' + this.$route.params.id + '/role', {
attributes: {
role: this.userRole,
},
_method: 'patch'
this.$emit('reload-user')
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.changed_user'),
})
.then(() => {
// Reset errors
this.$refs.changeRole.reset()
this.$emit('reload-user')
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.changed_user'),
})
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
.finally(() => {
this.isSendingRequest = false
})
}
})
.finally(() => {
this.isSendingRequest = false
})
},
}
},
}
</script>

View File

@@ -1,91 +1,85 @@
<template>
<div class="card shadow-card">
<FormLabel>
{{ $t('Subscription') }}
</FormLabel>
<div class="card shadow-card">
<FormLabel>
{{ $t('Subscription') }}
</FormLabel>
<b class="sm:text-3xl text-xl font-extrabold -mt-3 block mb-0.5">
{{ status }}
</b>
<b class="-mt-3 mb-0.5 block text-xl font-extrabold sm:text-3xl">
{{ status }}
</b>
<b class="mb-3 block text-sm text-gray-400 mb-8">
{{ subscription.relationships.plan.data.attributes.name }} / {{ price }}
</b>
<b class="mb-3 mb-8 block text-sm 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 text-gray-400">
{{ limit.message }}
</b>
<ProgressLine :data="limit.distribution" />
</div>
</div>
<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 text-gray-400">
{{ limit.message }}
</b>
<ProgressLine :data="limit.distribution" />
</div>
</div>
</template>
<script>
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ProgressLine from "../../../../components/Admin/ProgressLine"
import {mapGetters} from "vuex";
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ProgressLine from '../../../../components/Admin/ProgressLine'
import { mapGetters } from 'vuex'
export default {
name: 'UserFixedSubscription',
props: [
'subscription',
'user',
],
components: {
ProgressLine,
FormLabel,
},
computed: {
status() {
return {
'active': `Active until ${this.subscription.attributes.renews_at}`,
'cancelled': `Active until ${this.subscription.attributes.ends_at}`,
}[this.subscription.attributes.status]
},
price() {
return {
'month': `${this.subscription.relationships.plan.data.attributes.price} Per Month`,
'year': `${this.subscription.relationships.plan.data.attributes.price} Per Year`,
}[this.subscription.relationships.plan.data.attributes.interval]
},
},
data() {
return {
limitations: []
}
},
created() {
Object
.entries(this.user.data.meta.limitations)
.map(([key, item]) => {
export default {
name: 'UserFixedSubscription',
props: ['subscription', 'user'],
components: {
ProgressLine,
FormLabel,
},
computed: {
status() {
return {
active: `Active until ${this.subscription.attributes.renews_at}`,
cancelled: `Active until ${this.subscription.attributes.ends_at}`,
}[this.subscription.attributes.status]
},
price() {
return {
month: `${this.subscription.relationships.plan.data.attributes.price} Per Month`,
year: `${this.subscription.relationships.plan.data.attributes.price} Per Year`,
}[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: `Total ${item.use} of ${item.total} Used`,
max_team_members: `Total ${item.use} of ${item.total} Members`,
},
title: {
max_storage_amount: `Storage`,
max_team_members: `Team Members`,
},
}
let payload = {
color: {
'max_storage_amount': 'warning',
'max_team_members': 'purple',
},
message: {
'max_storage_amount': `Total ${item.use} of ${item.total} Used`,
'max_team_members': `Total ${item.use} of ${item.total} Members`,
},
title: {
'max_storage_amount': `Storage`,
'max_team_members': `Team Members`,
}
}
this.limitations.push({
message: payload.message[key],
distribution: [
{
progress: item.percentage,
color: payload.color[key],
title: payload.title[key],
}
]
})
})
}
}
</script>
this.limitations.push({
message: payload.message[key],
distribution: [
{
progress: item.percentage,
color: payload.color[key],
title: payload.title[key],
},
],
})
})
},
}
</script>

View File

@@ -1,150 +1,145 @@
<template>
<div>
<!--Balance-->
<div class="card shadow-card">
<FormLabel icon="hard-drive">
{{ $t('Balance') }}
</FormLabel>
<div>
<!--Balance-->
<div class="card shadow-card">
<FormLabel icon="hard-drive">
{{ $t('Balance') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ user.data.relationships.balance.data.attributes.formatted }}
</b>
<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('User balance will be increased for the amount above.')" :error="errors[0]" :is-last="true">
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
<input v-model="balanceAmount"
:placeholder="$t('Increase user balance for...')"
type="number"
min="1"
max="999999999"
class="focus-border-theme input-dark"
:class="{'border-red': errors[0]}"
/>
<ButtonBase type="submit" button-style="theme" class="sm:w-auto w-full"
:loading="isUpdatingBalanceAmount"
:disabled="isUpdatingBalanceAmount"
>
{{ $t('Increase Balance') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
<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('User balance will be increased for the amount above.')" :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 user balance for...')"
type="number"
min="1"
max="999999999"
class="focus-border-theme input-dark"
:class="{ 'border-red': 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>
<!--Usage Estimates-->
<div class="card shadow-card">
<FormLabel icon="hard-drive">
{{ $t('Usage Estimates') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ user.data.meta.usages.costEstimate }}
</b>
<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 block text-sm text-gray-400 mb-5">
{{ user.data.relationships.subscription.data.attributes.updated_at }} {{ $t('till now') }}
</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 py-2 border-b dark:border-opacity-5 border-light border-dashed" 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="text-xs text-gray-500 pt-2 leading-none block">
{{ $t(`feature_usage_desc_${usage.feature}`) }}
</small>
</div>
<div class="text-left w-1/4">
<span class="text-sm font-bold text-gray-560">
{{ usage.usage }}
</span>
</div>
<div class="text-right w-1/4">
<span class="text-sm font-bold text-theme">
{{ usage.cost }}
</span>
</div>
</div>
</div>
</div>
</div>
<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 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/Admin/AppInputText"
import FormLabel from "../../../../components/Others/Forms/FormLabel"
import ButtonBase from "../../../../components/FilesView/ButtonBase"
import ColorLabel from "../../../../components/Others/ColorLabel"
import {mapGetters} from "vuex";
import axios from "axios";
import {events} from "../../../../bus";
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AppInputText from '../../../../components/Admin/AppInputText'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import ColorLabel from '../../../../components/Others/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([
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()
]),
},
data() {
return {
balanceAmount: undefined,
isUpdatingBalanceAmount: false,
}
},
methods: {
async increaseBalance() {
// Validate fields
const isValid = await this.$refs.creditUserBalance.validate();
if (!isValid) return
if (!isValid) return;
this.isUpdatingBalanceAmount = true
this.isUpdatingBalanceAmount = true
axios
.post(`/api/subscriptions/admin/users/${this.user.data.id}/credit`, {
amount: this.balanceAmount,
})
.then(() => {
events.$emit('reload:user')
axios
.post(`/api/subscriptions/admin/users/${this.user.data.id}/credit`, {
amount: this.balanceAmount
})
.then(() => {
events.$emit('reload:user')
this.balanceAmount = undefined
this.balanceAmount = undefined
events.$emit('toaster', {
type: 'success',
message: this.$t('User balance was successfully increased'),
})
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
.finally(() => {
this.isUpdatingBalanceAmount = false
})
}
},
created() {
}
}
</script>
events.$emit('toaster', {
type: 'success',
message: this.$t('User balance was successfully increased'),
})
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
.finally(() => {
this.isUpdatingBalanceAmount = false
})
},
},
created() {},
}
</script>

View File

@@ -1,77 +1,74 @@
<template>
<PageTab>
<div class="card shadow-card">
<div class="card shadow-card">
<FormLabel>
{{ $t('user_box_password.title') }}
</FormLabel>
<AppInputText :title="$t('Reset User Password')" :description="$t('user_box_password.description')" :is-last="true">
<ButtonBase @click.native="requestPasswordResetEmail" :loading="isSendingRequest" :disabled="isSendingRequest" class="sm:w-auto w-full" button-style="theme">
{{ $t('admin_page_user.send_password_link') }}
</ButtonBase>
</AppInputText>
</div>
<AppInputText :title="$t('Reset User Password')" :description="$t('user_box_password.description')" :is-last="true">
<ButtonBase @click.native="requestPasswordResetEmail" :loading="isSendingRequest" :disabled="isSendingRequest" class="w-full sm:w-auto" button-style="theme">
{{ $t('admin_page_user.send_password_link') }}
</ButtonBase>
</AppInputText>
</div>
</PageTab>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import AppInputSwitch from "../../../../components/Admin/AppInputSwitch"
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import {required} from 'vee-validate/dist/rules'
import {events} from '../../../../bus'
import axios from 'axios'
import AppInputText from "../../../../components/Admin/AppInputText";
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import AppInputSwitch from '../../../../components/Admin/AppInputSwitch'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import { required } from 'vee-validate/dist/rules'
import { events } from '../../../../bus'
import axios from 'axios'
import AppInputText from '../../../../components/Admin/AppInputText'
export default {
name: 'UserPassword',
components: {
AppInputText,
ValidationProvider,
ValidationObserver,
AppInputSwitch,
PageTabGroup,
ButtonBase,
FormLabel,
SetupBox,
required,
InfoBox,
PageTab,
},
data() {
return {
isLoading: false,
isSendingRequest: false,
}
},
methods: {
requestPasswordResetEmail() {
this.isSendingRequest = true
axios
.post(`${this.$store.getters.api}/admin/users/${this.$route.params.id}/reset-password`,
{}
)
.then(() => {
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.sended_password'),
})
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
.finally(() => this.isSendingRequest = false)
}
export default {
name: 'UserPassword',
components: {
AppInputText,
ValidationProvider,
ValidationObserver,
AppInputSwitch,
PageTabGroup,
ButtonBase,
FormLabel,
SetupBox,
required,
InfoBox,
PageTab,
},
data() {
return {
isLoading: false,
isSendingRequest: false,
}
}
},
methods: {
requestPasswordResetEmail() {
this.isSendingRequest = true
axios
.post(`${this.$store.getters.api}/admin/users/${this.$route.params.id}/reset-password`, {})
.then(() => {
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.sended_password'),
})
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
.finally(() => (this.isSendingRequest = false))
},
},
}
</script>

View File

@@ -1,79 +1,80 @@
<template>
<PageTab :is-loading="isLoading" v-if="storage">
<!--Storage Usage-->
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
<!--Storage Usage-->
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
{{ $t('Storage Usage') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ storage.data.attributes.used }}
</b>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ storage.data.attributes.used }}
</b>
<b v-if="['fixed', 'none'].includes(config.subscriptionType)" class="mt-0.5 block text-sm text-gray-400">
{{ $t('Total of') }} {{ storage.data.attributes.capacity }} {{ $t('Used') }}
</b>
<b v-if="['fixed', 'none'].includes(config.subscriptionType)" class="mt-0.5 block text-sm text-gray-400">
{{ $t('Total of') }} {{ storage.data.attributes.capacity }}
{{ $t('Used') }}
</b>
<ProgressLine v-if="storage.data.attributes.used !== '0B'" :data="distribution" class="mt-5" />
</div>
<ProgressLine v-if="storage.data.attributes.used !== '0B'" :data="distribution" class="mt-5" />
</div>
<!--Upload-->
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
<!--Upload-->
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
{{ $t('Upload') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ storage.data.meta.traffic.upload }}
</b>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ storage.data.meta.traffic.upload }}
</b>
<b class="mb-3 block text-sm text-gray-400 mb-5">
{{ $t('In last 45 days') }}
</b>
<b class="mb-3 mb-5 block text-sm text-gray-400">
{{ $t('In last 45 days') }}
</b>
<BarChart :data="storage.data.meta.traffic.chart.upload" color="#FFBD2D" />
</div>
<BarChart :data="storage.data.meta.traffic.chart.upload" color="#FFBD2D" />
</div>
<!--Download-->
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
<!--Download-->
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
{{ $t('Download') }}
</FormLabel>
<b class="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ storage.data.meta.traffic.download }}
</b>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ storage.data.meta.traffic.download }}
</b>
<b class="mb-3 block text-sm text-gray-400 mb-5">
{{ $t('In last 45 days') }}
</b>
<b class="mb-3 mb-5 block text-sm text-gray-400">
{{ $t('In last 45 days') }}
</b>
<BarChart :data="storage.data.meta.traffic.chart.download" color="#9d66fe" />
</div>
<BarChart :data="storage.data.meta.traffic.chart.download" color="#9d66fe" />
</div>
<!--Set Storage Size-->
<div v-if="config.storageLimit && ! user.data.attributes.subscription && config.subscriptionType !== 'metered'" class="card shadow-card">
<!--Set Storage Size-->
<div v-if="config.storageLimit && !user.data.attributes.subscription && config.subscriptionType !== 'metered'" class="card shadow-card">
<FormLabel>
{{ $t('user_box_storage.title') }}
</FormLabel>
<ValidationObserver 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">
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
<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-red': errors[0]}"
/>
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="theme" class="sm:w-auto w-full">
{{ $t('admin_page_user.change_capacity') }}
</ButtonBase>
</div>
</AppInputText>
<AppInputText :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-red': errors[0] }"
/>
<ButtonBase :loading="isSendingRequest" :disabled="isSendingRequest" type="submit" button-style="theme" class="w-full sm:w-auto">
{{ $t('admin_page_user.change_capacity') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
@@ -81,119 +82,110 @@
</template>
<script>
import ProgressLine from "../../../../components/Admin/ProgressLine";
import AppInputText from "../../../../components/Admin/AppInputText";
import FormLabel from "../../../../components/Others/Forms/FormLabel";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import PageTabGroup from "../../../../components/Others/Layout/PageTabGroup";
import PageTab from "../../../../components/Others/Layout/PageTab";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import ButtonBase from "../../../../components/FilesView/ButtonBase";
import SetupBox from "../../../../components/Others/Forms/SetupBox";
import {required} from 'vee-validate/dist/rules'
import BarChart from "../../../../components/UI/BarChart"
import {events} from '../../../../bus'
import {mapGetters} from "vuex"
import axios from 'axios'
import ProgressLine from '../../../../components/Admin/ProgressLine'
import AppInputText from '../../../../components/Admin/AppInputText'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import InfoBox from '../../../../components/Others/Forms/InfoBox'
import PageTabGroup from '../../../../components/Others/Layout/PageTabGroup'
import PageTab from '../../../../components/Others/Layout/PageTab'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import ButtonBase from '../../../../components/FilesView/ButtonBase'
import SetupBox from '../../../../components/Others/Forms/SetupBox'
import { required } from 'vee-validate/dist/rules'
import BarChart from '../../../../components/UI/BarChart'
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,
SetupBox,
required,
BarChart,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: true,
isSendingRequest: false,
capacity: undefined,
storage: undefined,
distribution: undefined,
}
},
methods: {
async changeStorageCapacity() {
export default {
name: 'UserStorage',
props: ['user'],
components: {
ProgressLine,
AppInputText,
PageTabGroup,
FormLabel,
PageTab,
InfoBox,
ValidationProvider,
ValidationObserver,
ButtonBase,
SetupBox,
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()
// 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'
this.isSendingRequest = false
this.getStorageDetails()
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.changed_capacity'),
})
.then(() => {
})
.catch((error) => {
this.isSendingRequest = false
// Reset errors
this.$refs.changeStorageCapacity.reset()
this.isSendingRequest = false
this.getStorageDetails()
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'),
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'),
})
}
})
},
getStorageDetails() {
axios.get('/api/admin/users/' + this.$route.params.id + '/storage')
.then(response => {
this.distribution = this.$mapStorageUsage(response.data)
this.storage = response.data
this.isLoading = false
})
}
} else {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
}
})
},
created() {
this.getStorageDetails()
}
}
getStorageDetails() {
axios.get('/api/admin/users/' + this.$route.params.id + '/storage').then((response) => {
this.distribution = this.$mapStorageUsage(response.data)
this.storage = response.data
this.isLoading = false
})
},
},
created() {
this.getStorageDetails()
},
}
</script>

View File

@@ -1,136 +1,121 @@
<template>
<PageTab :is-loading="isLoading">
<UserMeteredSubscription v-if="subscription && config.subscriptionType === 'metered'" :subscription="subscription" :user="user" />
<UserMeteredSubscription
v-if="subscription && config.subscriptionType === 'metered'"
:subscription="subscription"
:user="user"
/>
<UserFixedSubscription v-if="subscription && config.subscriptionType === 'fixed'" :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>
<!--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="sm:text-3xl text-2xl font-extrabold -mt-3 block mb-0.5">
{{ $t('Free Plan') }}
</b>
<b class="block text-sm text-gray-400">
{{ $t('1GB Free storage space with 5 Team members') }}
</b>
</div>
<b class="block text-sm text-gray-400">
{{ $t('1GB Free storage space with 5 Team members') }}
</b>
</div>
<!--Transactions-->
<div class="card shadow-card">
<FormLabel icon="file-text">
<!--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"
>
<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-->
<!--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" />
<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-->
<!--Empty page-->
<template v-slot:empty-page>
<InfoBox style="margin-bottom: 0">
<p>{{ $t("User doesn't have any transactions yet.") }}</p>
<p>
{{ $t("User doesn't have any transactions yet.") }}
</p>
</InfoBox>
</template>
</DatatableWrapper>
</div>
</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/Others/Tables/DatatableWrapper";
import FormLabel from "../../../../components/Others/Forms/FormLabel"
import PageTab from "../../../../components/Others/Layout/PageTab";
import InfoBox from "../../../../components/Others/Forms/InfoBox";
import UserMeteredSubscription from "./UserMeteredSubscription"
import UserFixedSubscription from "./UserFixedSubscription"
import {mapGetters} from "vuex"
import axios from 'axios'
import MeteredTransactionDetailRow from '../../../../components/Subscription/MeteredTransactionDetailRow'
import MeteredTransactionRow from '../../../../components/Subscription/MeteredTransactionRow'
import FixedTransactionRow from '../../../../components/Subscription/FixedTransactionRow'
import DatatableWrapper from '../../../../components/Others/Tables/DatatableWrapper'
import FormLabel from '../../../../components/Others/Forms/FormLabel'
import PageTab from '../../../../components/Others/Layout/PageTab'
import InfoBox from '../../../../components/Others/Forms/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'],
}
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
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
})
}
}
this.isLoading = false
})
.catch((error) => {
if (error.response.status === 404) this.isLoading = false
})
},
}
</script>