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,8 +1,8 @@
<template>
<div class="lg:flex md:h-screen md:overflow-hidden dark:bg-dark-background bg-light-background">
<!--On Top of App Components-->
<div class="bg-light-background dark:bg-dark-background md:h-screen md:overflow-hidden lg:flex">
<!--On Top of App Components-->
<FilePreview />
<Spotlight />
<Spotlight />
<!--Mobile Navigation-->
<MobileNavigation />
@@ -11,176 +11,187 @@
<ConfirmPopup />
<!-- Create language popup -->
<CreateLanguage/>
<CreateLanguage />
<!--Navigation Sidebar-->
<SidebarNavigation/>
<SidebarNavigation />
<MobileNavigationToolbar />
<MobileNavigationToolbar />
<ContentSidebar>
<ContentGroup v-for="(menu, i) in nav" :key="i" :title="menu.groupTitle" :slug="menu.groupTitle" :can-collapse="false">
<router-link v-for="(item, i) in menu.groupLinks" :key="i" :to="{name: item.route}" class="flex items-center py-2.5" :class="{'router-link-active': item.linkActivation && item.linkActivation.includes($router.currentRoute.fullPath.split('/')[2])}">
<box-icon v-if="item.icon === 'box'" size="17" class="mr-2.5 vue-feather icon-active" />
<users-icon v-if="item.icon === 'users'" size="17" class="mr-2.5 vue-feather icon-active" />
<settings-icon v-if="item.icon === 'settings'" size="17" class="mr-2.5 vue-feather icon-active" />
<monitor-icon v-if="item.icon === 'monitor'" size="17" class="mr-2.5 vue-feather icon-active" />
<globe-icon v-if="item.icon === 'globe'" size="17" class="mr-2.5 vue-feather icon-active" />
<credit-card-icon v-if="item.icon === 'card'" size="17" class="mr-2.5 vue-feather icon-active" />
<database-icon v-if="item.icon === 'database'" size="17" class="mr-2.5 vue-feather icon-active" />
<dollar-sign-icon v-if="item.icon === 'dollar'" size="17" class="mr-2.5 vue-feather icon-active" />
<file-text-icon v-if="item.icon === 'file-text'" size="17" class="mr-2.5 vue-feather icon-active" />
<ContentGroup v-for="(menu, i) in nav" :key="i" :title="menu.groupTitle" :slug="menu.groupTitle" :can-collapse="false">
<router-link
v-for="(item, i) in menu.groupLinks"
:key="i"
:to="{ name: item.route }"
class="flex items-center py-2.5"
:class="{
'router-link-active': item.linkActivation && item.linkActivation.includes($router.currentRoute.fullPath.split('/')[2]),
}"
>
<box-icon v-if="item.icon === 'box'" size="17" class="vue-feather icon-active mr-2.5" />
<users-icon v-if="item.icon === 'users'" size="17" class="vue-feather icon-active mr-2.5" />
<settings-icon v-if="item.icon === 'settings'" size="17" class="vue-feather icon-active mr-2.5" />
<monitor-icon v-if="item.icon === 'monitor'" size="17" class="vue-feather icon-active mr-2.5" />
<globe-icon v-if="item.icon === 'globe'" size="17" class="vue-feather icon-active mr-2.5" />
<credit-card-icon v-if="item.icon === 'card'" size="17" class="vue-feather icon-active mr-2.5" />
<database-icon v-if="item.icon === 'database'" size="17" class="vue-feather icon-active mr-2.5" />
<dollar-sign-icon v-if="item.icon === 'dollar'" size="17" class="vue-feather icon-active mr-2.5" />
<file-text-icon v-if="item.icon === 'file-text'" size="17" class="vue-feather icon-active mr-2.5" />
<b class="font-bold text-xs text-active">
{{ item.title }}
</b>
</router-link>
<b class="text-active text-xs font-bold">
{{ item.title }}
</b>
</router-link>
</ContentGroup>
</ContentSidebar>
<router-view class="lg:pt-6 md:px-6 px-2.5 lg:pb-0 pb-12 w-full overflow-x-hidden relative" />
<router-view class="relative w-full overflow-x-hidden px-2.5 pb-12 md:px-6 lg:pt-6 lg:pb-0" />
</div>
</template>
<script>
import MobileNavigationToolbar from "./MobileNavigationToolbar";
import FilePreview from "../components/FilePreview/FilePreview";
import Spotlight from "../components/Spotlight/Spotlight";
import { DollarSignIcon, HelpCircleIcon, RefreshCwIcon, UsersIcon, SettingsIcon, FileTextIcon, CreditCardIcon, DatabaseIcon, BoxIcon, MonitorIcon, GlobeIcon } from 'vue-feather-icons'
import SidebarNavigation from "../components/Sidebar/SidebarNavigation";
import MobileNavigation from "../components/Others/MobileNavigation";
import ContentSidebar from "../components/Sidebar/ContentSidebar";
import CreateLanguage from "../components/Others/CreateLanguage";
import ContentGroup from "../components/Sidebar/ContentGroup";
import ConfirmPopup from "../components/Others/Popup/ConfirmPopup";
import { mapGetters } from 'vuex'
import FilePreview from '../components/FilePreview/FilePreview'
import CreateLanguage from '../components/Others/CreateLanguage'
import MobileNavigation from '../components/Others/MobileNavigation'
import ConfirmPopup from '../components/Others/Popup/ConfirmPopup'
import ContentGroup from '../components/Sidebar/ContentGroup'
import ContentSidebar from '../components/Sidebar/ContentSidebar'
import SidebarNavigation from '../components/Sidebar/SidebarNavigation'
import Spotlight from '../components/Spotlight/Spotlight'
import MobileNavigationToolbar from './MobileNavigationToolbar'
import {
BoxIcon,
CreditCardIcon,
DatabaseIcon,
DollarSignIcon,
FileTextIcon,
GlobeIcon,
HelpCircleIcon,
MonitorIcon,
RefreshCwIcon,
SettingsIcon,
UsersIcon,
} from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'Admin',
computed: {
...mapGetters([
'isVisibleNavigationBars',
'config',
]),
nav() {
let subscriptionLinks = {
metered: [
{
title: this.$t('Payments'),
route: 'PaymentSettings',
icon: 'card',
},
{
title: this.$t('admin_menu.plans'),
route: 'Plans',
icon: 'database',
linkActivation: [
'plans', 'plan'
],
},
{
title: this.$t('Transactions'),
route: 'Invoices',
icon: 'file-text',
},
],
fixed: [
{
title: this.$t('Payments'),
route: 'PaymentSettings',
icon: 'card',
},
{
title: this.$t('Subscriptions'),
route: 'Subscriptions',
icon: 'dollar',
},
{
title: this.$t('admin_menu.plans'),
route: 'Plans',
icon: 'database',
linkActivation: [
'plans', 'plan'
],
},
{
title: this.$t('Transactions'),
route: 'Invoices',
icon: 'file-text',
},
],
}[this.config.subscriptionType]
export default {
name: 'Admin',
computed: {
...mapGetters(['isVisibleNavigationBars', 'config']),
nav() {
let subscriptionLinks = {
metered: [
{
title: this.$t('Payments'),
route: 'PaymentSettings',
icon: 'card',
},
{
title: this.$t('admin_menu.plans'),
route: 'Plans',
icon: 'database',
linkActivation: ['plans', 'plan'],
},
{
title: this.$t('Transactions'),
route: 'Invoices',
icon: 'file-text',
},
],
fixed: [
{
title: this.$t('Payments'),
route: 'PaymentSettings',
icon: 'card',
},
{
title: this.$t('Subscriptions'),
route: 'Subscriptions',
icon: 'dollar',
},
{
title: this.$t('admin_menu.plans'),
route: 'Plans',
icon: 'database',
linkActivation: ['plans', 'plan'],
},
{
title: this.$t('Transactions'),
route: 'Invoices',
icon: 'file-text',
},
],
}[this.config.subscriptionType]
return [
{
groupCollapsable: false,
groupTitle: this.$t('global.admin'),
groupLinks: [
{
title: this.$t('admin_menu.dashboard'),
route: 'Dashboard',
icon: 'box',
},
{
title: this.$t('admin_menu.users'),
route: 'Users',
icon: 'users',
linkActivation: [
'users', 'user'
],
},
{
title: this.$t('admin_menu.settings'),
route: 'AppSettings',
icon: 'settings',
},
],
},
{
groupCollapsable: false,
groupTitle: this.$t('Content'),
groupLinks: [
{
title: this.$t('admin_menu.pages'),
route: 'Pages',
icon: 'monitor',
},
{
title: this.$t('admin_menu.languages'),
route: 'Language',
icon: 'globe',
},
],
},
{
groupCollapsable: false,
groupTitle: this.$t('Subscription'),
groupLinks: subscriptionLinks,
},
]
}
return [
{
groupCollapsable: false,
groupTitle: this.$t('global.admin'),
groupLinks: [
{
title: this.$t('admin_menu.dashboard'),
route: 'Dashboard',
icon: 'box',
},
{
title: this.$t('admin_menu.users'),
route: 'Users',
icon: 'users',
linkActivation: ['users', 'user'],
},
{
title: this.$t('admin_menu.settings'),
route: 'AppSettings',
icon: 'settings',
},
],
},
{
groupCollapsable: false,
groupTitle: this.$t('Content'),
groupLinks: [
{
title: this.$t('admin_menu.pages'),
route: 'Pages',
icon: 'monitor',
},
{
title: this.$t('admin_menu.languages'),
route: 'Language',
icon: 'globe',
},
],
},
{
groupCollapsable: false,
groupTitle: this.$t('Subscription'),
groupLinks: subscriptionLinks,
},
]
},
components: {
MobileNavigationToolbar,
FilePreview,
Spotlight,
SidebarNavigation,
MobileNavigation,
CreateLanguage,
ContentSidebar,
DollarSignIcon,
HelpCircleIcon,
RefreshCwIcon,
CreditCardIcon,
FileTextIcon,
ContentGroup,
DatabaseIcon,
SettingsIcon,
MonitorIcon,
UsersIcon,
GlobeIcon,
ConfirmPopup,
BoxIcon,
},
}
},
components: {
MobileNavigationToolbar,
FilePreview,
Spotlight,
SidebarNavigation,
MobileNavigation,
CreateLanguage,
ContentSidebar,
DollarSignIcon,
HelpCircleIcon,
RefreshCwIcon,
CreditCardIcon,
FileTextIcon,
ContentGroup,
DatabaseIcon,
SettingsIcon,
MonitorIcon,
UsersIcon,
GlobeIcon,
ConfirmPopup,
BoxIcon,
},
}
</script>

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>

View File

@@ -1,53 +1,66 @@
<template>
<AuthContentWrapper ref="auth">
<!--Create new password-->
<AuthContent name="create-new-password" :visible="true">
<Headline
:title="$t('page_create_password.title')"
:description="$t('page_create_password.subtitle')"
/>
<Headline :title="$t('page_create_password.title')" :description="$t('page_create_password.subtitle')" />
<ValidationObserver @submit.prevent="createNewPassword" ref="create_new_password" v-slot="{ invalid }" tag="form" class="space-y-4 mb-12 text-left">
<div class="md:flex md:items-center mb-5 md:max-w-lg mx-auto">
<label class="md:w-72 md:text-right md:pr-4 font-bold md:mb-0 mb-1.5 block">
{{ $t('page_create_password.label_email') }}:
</label>
<ValidationObserver @submit.prevent="createNewPassword" ref="create_new_password" v-slot="{ invalid }" tag="form" class="mb-12 space-y-4 text-left">
<div class="mx-auto mb-5 md:flex md:max-w-lg md:items-center">
<label class="mb-1.5 block font-bold md:mb-0 md:w-72 md:pr-4 md:text-right"> {{ $t('page_create_password.label_email') }}: </label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="E-Mail" rules="required" v-slot="{ errors }">
<input v-model="recoverPassword.email" :placeholder="$t('page_login.placeholder_email')" type="email" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}"/>
<input
v-model="recoverPassword.email"
:placeholder="$t('page_login.placeholder_email')"
type="email"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="md:flex md:items-center mb-5 md:max-w-lg mx-auto">
<label class="md:w-72 md:text-right md:pr-4 font-bold md:mb-0 mb-1.5 block">
{{ $t('page_create_password.label_new_pass') }}:
</label>
<div class="mx-auto mb-5 md:flex md:max-w-lg md:items-center">
<label class="mb-1.5 block font-bold md:mb-0 md:w-72 md:pr-4 md:text-right"> {{ $t('page_create_password.label_new_pass') }}: </label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="New Password" rules="required" v-slot="{ errors }">
<input v-model="recoverPassword.newPassword" :placeholder="$t('page_create_password.label_new_pass')" type="password" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}"/>
<input
v-model="recoverPassword.newPassword"
:placeholder="$t('page_create_password.label_new_pass')"
type="password"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="md:flex md:items-center mb-5 md:max-w-lg mx-auto">
<label class="md:w-72 md:text-right md:pr-4 font-bold md:mb-0 mb-1.5 block">
{{ $t('page_create_password.label_confirm_pass') }}:
</label>
<div class="mx-auto mb-5 md:flex md:max-w-lg md:items-center">
<label class="mb-1.5 block font-bold md:mb-0 md:w-72 md:pr-4 md:text-right"> {{ $t('page_create_password.label_confirm_pass') }}: </label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Confirm Password" rules="required" v-slot="{ errors }">
<input v-model="recoverPassword.newPasswordConfirm" :placeholder="$t('page_create_password.label_confirm_pass')" type="password" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}"/>
<input
v-model="recoverPassword.newPasswordConfirm"
:placeholder="$t('page_create_password.label_confirm_pass')"
type="password"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div>
<AuthButton class="md:w-min w-full justify-center mt-12" icon="chevron-right" :text="$t('page_create_password.button_update')" :loading="isLoading" :disabled="isLoading"/>
</div>
<div>
<AuthButton
class="mt-12 w-full justify-center md:w-min"
icon="chevron-right"
:text="$t('page_create_password.button_update')"
:loading="isLoading"
:disabled="isLoading"
/>
</div>
</ValidationObserver>
<span class="block">
{{ $t('page_forgotten_password.password_remember_text') }}
<router-link :to="{name: 'SignIn'}" class="font-bold text-theme">
{{ $t('page_forgotten_password.password_remember_text') }}
<router-link :to="{ name: 'SignIn' }" class="text-theme font-bold">
{{ $t('page_forgotten_password.password_remember_button') }}
</router-link>
</span>
@@ -55,122 +68,112 @@
<!--Password reset successfully-->
<AuthContent name="password-reset-successfully" :visible="false">
<img v-if="config.app_logo" class="logo mx-auto" :src="$getImage(config.app_logo)" :alt="config.app_name">
<b v-if="! config.app_logo" class="auth-logo-text">{{ config.app_name }}</b>
<img v-if="config.app_logo" class="logo mx-auto" :src="$getImage(config.app_logo)" :alt="config.app_name" />
<b v-if="!config.app_logo" class="auth-logo-text">{{ config.app_name }}</b>
<h1>{{ $t('page_forgotten_password.pass_reseted_title') }}</h1>
<h2>{{ $t('page_forgotten_password.pass_reseted_subtitle') }}</h2>
<router-link :to="{name: 'SignIn'}">
<AuthButton icon="chevron-right" :text="$t('page_forgotten_password.pass_reseted_signin')"/>
<router-link :to="{ name: 'SignIn' }">
<AuthButton icon="chevron-right" :text="$t('page_forgotten_password.pass_reseted_signin')" />
</router-link>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {required} from 'vee-validate/dist/rules'
import Headline from "./Headline";
import {mapGetters} from 'vuex'
import axios from 'axios'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { required } from 'vee-validate/dist/rules'
import Headline from './Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'CreateNewPassword',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
Headline,
required,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
recoverPassword: {
token: undefined,
email: '',
newPassword: '',
newPasswordConfirm: '',
},
}
},
methods: {
goToAuthPage(slug) {
this.$refs.auth.$children.forEach(page => {
// Hide current step
page.isVisible = false
if (page.$props.name === slug) {
// Go to next step
page.isVisible = true
}
})
export default {
name: 'CreateNewPassword',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
Headline,
required,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
recoverPassword: {
token: undefined,
email: '',
newPassword: '',
newPasswordConfirm: '',
},
async createNewPassword() {
}
},
methods: {
goToAuthPage(slug) {
this.$refs.auth.$children.forEach((page) => {
// Hide current step
page.isVisible = false
// Validate fields
const isValid = await this.$refs.create_new_password.validate();
if (page.$props.name === slug) {
// Go to next step
page.isVisible = true
}
})
},
async createNewPassword() {
// Validate fields
const isValid = await this.$refs.create_new_password.validate()
if (!isValid) return;
if (!isValid) return
// Start loading
this.isLoading = true
// Start loading
this.isLoading = true
// Send request to get user reset link
axios
.post(this.$store.getters.api + '/password/reset', {
email: this.recoverPassword.email,
token: this.recoverPassword.token,
password: this.recoverPassword.newPassword,
password_confirmation: this.recoverPassword.newPasswordConfirm,
})
.then(() => {
// Send request to get user reset link
axios
.post(this.$store.getters.api + '/password/reset', {
email: this.recoverPassword.email,
token: this.recoverPassword.token,
password: this.recoverPassword.newPassword,
password_confirmation: this.recoverPassword.newPasswordConfirm,
})
.then(() => {
// End loading
this.isLoading = false
// End loading
this.isLoading = false
this.goToAuthPage('password-reset-successfully')
})
.catch(error => {
if (error.response.status === 422) {
if (error.response.data.error) {
this.$refs.create_new_password.setErrors({
'E-Mail': error.response.data.error
});
}
if (error.response.data.errors['password']) {
this.$refs.create_new_password.setErrors({
'New Password': error.response.data.errors['password']
});
}
this.goToAuthPage('password-reset-successfully')
})
.catch((error) => {
if (error.response.status === 422) {
if (error.response.data.error) {
this.$refs.create_new_password.setErrors({
'E-Mail': error.response.data.error,
})
}
// End loading
this.isLoading = false
})
},
},
created() {
if (error.response.data.errors['password']) {
this.$refs.create_new_password.setErrors({
'New Password': error.response.data.errors['password'],
})
}
}
// Get token
this.recoverPassword.token = this.$route.query.token
}
}
</script>
// End loading
this.isLoading = false
})
},
},
created() {
// Get token
this.recoverPassword.token = this.$route.query.token
},
}
</script>

View File

@@ -1,25 +1,39 @@
<template>
<AuthContentWrapper ref="auth" class="h-screen">
<!--Forgotten your password?-->
<AuthContent name="forgotten-password" :visible="true">
<Headline
:title="$t('page_forgotten_password.title')"
:description="$t('page_forgotten_password.subtitle')"
/>
<Headline :title="$t('page_forgotten_password.title')" :description="$t('page_forgotten_password.subtitle')" />
<ValidationObserver @submit.prevent="forgottenPassword" ref="forgotten_password" v-slot="{ invalid }" tag="form" class="md:flex items-start md:space-x-4 md:space-y-0 space-y-4 mb-12">
<ValidationProvider tag="div" mode="passive" class="w-full text-left relative" name="E-Mail" rules="required" v-slot="{ errors }">
<input v-model="recoverEmail" :placeholder="$t('page_login.placeholder_email')" type="email" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}"/>
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
<ValidationObserver
@submit.prevent="forgottenPassword"
ref="forgotten_password"
v-slot="{ invalid }"
tag="form"
class="mb-12 items-start space-y-4 md:flex md:space-x-4 md:space-y-0"
>
<ValidationProvider tag="div" mode="passive" class="relative w-full text-left" name="E-Mail" rules="required" v-slot="{ errors }">
<input
v-model="recoverEmail"
:placeholder="$t('page_login.placeholder_email')"
type="email"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<AuthButton class="md:w-min w-full justify-center" icon="chevron-right" :text="$t('page_forgotten_password.button_get_link')" :loading="isLoading" :disabled="isLoading"/>
<AuthButton
class="w-full justify-center md:w-min"
icon="chevron-right"
:text="$t('page_forgotten_password.button_get_link')"
:loading="isLoading"
:disabled="isLoading"
/>
</ValidationObserver>
<span class="block">
{{ $t('page_forgotten_password.password_remember_text') }}
<router-link :to="{name: 'SignIn'}" class="font-bold text-theme">
{{ $t('page_forgotten_password.password_remember_text') }}
<router-link :to="{ name: 'SignIn' }" class="text-theme font-bold">
{{ $t('page_forgotten_password.password_remember_button') }}
</router-link>
</span>
@@ -27,14 +41,11 @@
<!--Password reset link send-->
<AuthContent name="password-reset-link-sended" :visible="false">
<Headline
:title="$t('page_forgotten_password.pass_sennded_title')"
:description="$t('page_forgotten_password.pass_sennded_subtitle')"
/>
<Headline :title="$t('page_forgotten_password.pass_sennded_title')" :description="$t('page_forgotten_password.pass_sennded_subtitle')" />
<span class="block">
{{ $t('page_forgotten_password.password_remember_text') }}
<router-link :to="{name: 'SignIn'}" class="font-bold text-theme">
{{ $t('page_forgotten_password.password_remember_text') }}
<router-link :to="{ name: 'SignIn' }" class="text-theme font-bold">
{{ $t('page_forgotten_password.password_remember_button') }}
</router-link>
</span>
@@ -43,78 +54,73 @@
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {required} from 'vee-validate/dist/rules'
import Headline from "./Headline";
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { required } from 'vee-validate/dist/rules'
import Headline from './Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'ForgottenPassword',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
required,
Headline,
export default {
name: 'ForgottenPassword',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
required,
Headline,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
recoverEmail: '',
}
},
methods: {
goToAuthPage(slug) {
this.$refs.auth.$children.forEach((page) => {
// Hide current step
page.isVisible = false
if (page.$props.name === slug) {
// Go to next step
page.isVisible = true
}
})
},
computed: {
...mapGetters([
'config',
]),
},
data() {
return {
isLoading: false,
recoverEmail: '',
}
},
methods: {
goToAuthPage(slug) {
this.$refs.auth.$children.forEach(page => {
async forgottenPassword() {
// Validate fields
const isValid = await this.$refs.forgotten_password.validate()
// Hide current step
page.isVisible = false
if (!isValid) return
if (page.$props.name === slug) {
// Start loading
this.isLoading = true
// Go to next step
page.isVisible = true
// Send request to get user reset link
axios
.post('/api/password/email', {
email: this.recoverEmail,
})
.then(() => {
this.goToAuthPage('password-reset-link-sended')
})
.catch((error) => {
if (error.response.status === 422) {
this.$refs.forgotten_password.setErrors({
'E-Mail': error.response.data.message,
})
}
})
},
async forgottenPassword() {
// Validate fields
const isValid = await this.$refs.forgotten_password.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Send request to get user reset link
axios
.post('/api/password/email', {
email: this.recoverEmail
})
.then(() => {
this.goToAuthPage('password-reset-link-sended')
}).catch(error => {
if (error.response.status === 422) {
this.$refs.forgotten_password.setErrors({
'E-Mail': error.response.data.message
});
}
}).finally(() => this.isLoading = false)
},
}
}
.finally(() => (this.isLoading = false))
},
},
}
</script>

View File

@@ -1,42 +1,36 @@
<template>
<div class="mb-14">
<!--Custom content-->
<slot></slot>
<div class="mb-14">
<!--Custom content-->
<slot></slot>
<!--Default application logo-->
<div v-if="! $slots.default">
<!--Default application logo-->
<div v-if="!$slots.default">
<!--Image logo-->
<img v-if="config.app_logo" class="mx-auto mb-6 w-28 md:w-32" :src="$getImage(config.app_logo)" :alt="config.app_name" />
<!--Image logo-->
<img v-if="config.app_logo" class="md:w-32 w-28 mb-6 mx-auto" :src="$getImage(config.app_logo)" :alt="config.app_name">
<!--Text logo if image isn't available-->
<b v-if="!config.app_logo" class="mb-10 block text-xl font-bold">
{{ config.app_name }}
</b>
</div>
<!--Text logo if image isn't available-->
<b v-if="! config.app_logo" class="font-bold text-xl mb-10 block">
{{ config.app_name }}
</b>
</div>
<h1 class="mb-0.5 text-3xl font-extrabold md:text-4xl">
{{ title }}
</h1>
<h1 class="md:text-4xl text-3xl font-extrabold mb-0.5">
{{ title }}
</h1>
<h2 class="md:text-2xl text-xl font-normal">
{{ description }}
</h2>
</div>
<h2 class="text-xl font-normal md:text-2xl">
{{ description }}
</h2>
</div>
</template>
<script>
import {mapGetters} from "vuex";
import { mapGetters } from 'vuex'
export default {
name: 'Headline',
props: [
'description',
'title',
],
computed: {
...mapGetters([
'config',
])
}
}
</script>
export default {
name: 'Headline',
props: ['description', 'title'],
computed: {
...mapGetters(['config']),
},
}
</script>

View File

@@ -1,130 +1,137 @@
<template>
<AuthContentWrapper ref="auth" class="h-screen">
<!--Log In by Email-->
<AuthContent name="log-in" :visible="true">
<Headline
:title="$t('page_login.title')"
:description="$t('page_login.subtitle')"
/>
<Headline :title="$t('page_login.title')" :description="$t('page_login.subtitle')" />
<ValidationObserver @submit.prevent="logIn" ref="log_in" v-slot="{ invalid }" tag="form" class="md:flex items-start md:space-x-4 md:space-y-0 space-y-4 mb-12">
<ValidationObserver @submit.prevent="logIn" ref="log_in" v-slot="{ invalid }" tag="form" class="mb-12 items-start space-y-4 md:flex md:space-x-4 md:space-y-0">
<ValidationProvider class="w-full text-left" tag="div" mode="passive" name="E-Mail" rules="required" v-slot="{ errors }">
<input class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}" v-model="loginEmail" :placeholder="$t('page_login.placeholder_email')" type="email" />
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
<input
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
v-model="loginEmail"
:placeholder="$t('page_login.placeholder_email')"
type="email"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<AuthButton class="md:w-min w-full justify-center" icon="chevron-right" :text="$t('page_login.button_next')" :loading="isLoading" :disabled="isLoading" />
<AuthButton class="w-full justify-center md:w-min" icon="chevron-right" :text="$t('page_login.button_next')" :loading="isLoading" :disabled="isLoading" />
</ValidationObserver>
<SocialiteAuthenticationButtons />
<span v-if="config.userRegistration" class="block">
{{ $t('page_login.registration_text') }}
<router-link class="font-bold text-theme" :to="{name: 'SignUp'}">
<router-link class="text-theme font-bold" :to="{ name: 'SignUp' }">
{{ $t('page_login.registration_button') }}
</router-link>
</span>
</AuthContent>
<!--Log in By Password-->
<!--Log in By Password-->
<AuthContent name="sign-in" :visible="false">
<Headline
v-if="checkedAccount"
:title="$t('page_sign_in.title', {name: checkedAccount.name})"
:description="$t('page_sign_in.subtitle')"
>
<img class="user-avatar mx-auto rounded-xl w-28 mb-6 shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name">
</Headline>
<Headline v-if="checkedAccount" :title="$t('page_sign_in.title', { name: checkedAccount.name })" :description="$t('page_sign_in.subtitle')">
<img class="user-avatar mx-auto mb-6 w-28 rounded-xl shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name" />
</Headline>
<ValidationObserver @submit.prevent="singIn" ref="sign_in" v-slot="{ invalid }" tag="form" class="md:flex items-start md:space-x-4 md:space-y-0 space-y-4 mb-12">
<ValidationObserver @submit.prevent="singIn" ref="sign_in" v-slot="{ invalid }" tag="form" class="mb-12 items-start space-y-4 md:flex md:space-x-4 md:space-y-0">
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="User Password" rules="required" v-slot="{ errors }">
<input v-model="loginPassword" :placeholder="$t('page_sign_in.placeholder_password')" type="password" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full h-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}" />
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
<input
v-model="loginPassword"
:placeholder="$t('page_sign_in.placeholder_password')"
type="password"
class="focus-border-theme h-full w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<AuthButton class="md:w-min w-full justify-center" icon="chevron-right" :text="$t('page_sign_in.button_log_in')" :loading="isLoading" :disabled="isLoading" />
<AuthButton class="w-full justify-center md:w-min" icon="chevron-right" :text="$t('page_sign_in.button_log_in')" :loading="isLoading" :disabled="isLoading" />
</ValidationObserver>
<span class="block">
{{ $t('page_sign_in.password_reset_text') }}
<router-link :to="{name: 'ForgottenPassword'}" class="font-bold text-theme">
{{ $t('page_sign_in.password_reset_text') }}
<router-link :to="{ name: 'ForgottenPassword' }" class="text-theme font-bold">
{{ $t('page_sign_in.password_reset_button') }}
</router-link>
</span>
</AuthContent>
<!--Resend verification email-->
<!--Resend verification email-->
<AuthContent name="not-verified" :visible="false">
<Headline
v-if="checkedAccount"
:title="$t('page_sign_in_2fa_title', {name: checkedAccount.name})"
:description="$t('page_not_verified.subtitle')"
>
<img class="user-avatar mx-auto rounded-xl w-28 mb-6 shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name">
</Headline>
<Headline v-if="checkedAccount" :title="$t('page_sign_in_2fa_title', { name: checkedAccount.name })" :description="$t('page_not_verified.subtitle')">
<img class="user-avatar mx-auto mb-6 w-28 rounded-xl shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name" />
</Headline>
<span class="block">
{{ $t('page_not_verified.resend_text') }}
{{ $t('page_not_verified.resend_text') }}
<b @click="resendEmail" class="text-theme cursor-pointer">
{{ $t('page_not_verified.resend_button') }}
</b>
{{ $t('page_not_verified.resend_button') }}
</b>
</span>
</AuthContent>
<!-- Log in by 2fa -->
<!-- Log in by 2fa -->
<AuthContent name="two-factor-authentication" :visible="false">
<Headline
v-if="checkedAccount"
:title="$t('page_sign_in_2fa_title', {name: checkedAccount.name})"
:description="$t('page_sign_in_2fa_subtitle')"
>
<img class="user-avatar mx-auto rounded-xl w-28 mb-6 shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name">
</Headline>
<Headline v-if="checkedAccount" :title="$t('page_sign_in_2fa_title', { name: checkedAccount.name })" :description="$t('page_sign_in_2fa_subtitle')">
<img class="user-avatar mx-auto mb-6 w-28 rounded-xl shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name" />
</Headline>
<ValidationObserver ref="two_factor_authentication" v-slot="{ invalid }" tag="form" class="mb-12">
<ValidationProvider tag="div" mode="passive" class="mx-auto" name="Two Factor Authentication" rules="required" v-slot="{ errors }">
<input v-model="twoFactorCode" ref="twoFactorCodeInput" :placeholder="$t('page_sign_in.placeholder_2fa')" @input="twoFactorChallenge(false)" type="text" maxlength="6" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background text-center md:w-80 w-full h-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}" />
<span class="text-red-600 text-xs mt-2 text-center block" v-if="errors[0]">{{ errors[0] }}</span>
<input
v-model="twoFactorCode"
ref="twoFactorCodeInput"
:placeholder="$t('page_sign_in.placeholder_2fa')"
@input="twoFactorChallenge(false)"
type="text"
maxlength="6"
class="focus-border-theme h-full w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 text-center font-bold dark:bg-2x-dark-foreground md:w-80"
:class="{ 'border-red': errors[0] }"
/>
<span class="mt-2 block text-center text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<span class="block">
{{ $t('page_sign_in.2fa_recovery_text') }}
<span class="block">
{{ $t('page_sign_in.2fa_recovery_text') }}
<b @click="goToAuthPage('two-factor-recovery')" class="text-theme cursor-pointer cursor-pointer">
{{ $t('page_sign_in.2fa_recovery_button') }}
{{ $t('page_sign_in.2fa_recovery_button') }}
</b>
</span>
<div class="relative h-12 mt-10 w-full">
<div class="relative mt-10 h-12 w-full">
<Spinner v-if="isLoading" class="spinner" />
</div>
</AuthContent>
<!-- Log in by 2fa with recovery code -->
<!-- Log in by 2fa with recovery code -->
<AuthContent name="two-factor-recovery" :visible="false">
<Headline
v-if="checkedAccount"
:title="$t('page_sign_in_2fa_title', {name: checkedAccount.name})"
:description="$t('page_sign_in.2fa_recovery_subtitle')"
>
<img class="user-avatar mx-auto rounded-xl w-28 mb-6 shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name">
</Headline>
<Headline v-if="checkedAccount" :title="$t('page_sign_in_2fa_title', { name: checkedAccount.name })" :description="$t('page_sign_in.2fa_recovery_subtitle')">
<img class="user-avatar mx-auto mb-6 w-28 rounded-xl shadow-xl" :src="checkedAccount.avatar.md" :alt="checkedAccount.name" />
</Headline>
<ValidationObserver ref="two_factor_recovery" v-slot="{ invalid }" tag="form" class="mb-12">
<ValidationProvider tag="div" mode="passive" class="mx-auto" name="Two Factor Recovery" rules="required" v-slot="{ errors }">
<input v-model="twoFactorRecoveryCode" :placeholder="$t('page_sign_in.placeholder_2fa_recovery')" @input="twoFactorChallenge(true)" type="text" maxlength="21" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background text-center md:w-80 w-full h-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}" />
<span class="text-red-600 text-xs mt-2 text-center block" v-if="errors[0]">{{ errors[0] }}</span>
<input
v-model="twoFactorRecoveryCode"
:placeholder="$t('page_sign_in.placeholder_2fa_recovery')"
@input="twoFactorChallenge(true)"
type="text"
maxlength="21"
class="focus-border-theme h-full w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 text-center font-bold dark:bg-2x-dark-foreground md:w-80"
:class="{ 'border-red': errors[0] }"
/>
<span class="mt-2 block text-center text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
<b @click="goToAuthPage('two-factor-authentication')" class="text-theme block cursor-pointer">
{{ $t('2fa.i_have_2fa_app') }}
</b>
<b @click="goToAuthPage('two-factor-authentication')" class="text-theme block cursor-pointer">
{{ $t('2fa.i_have_2fa_app') }}
</b>
<div v-if="isLoading" class="relative h-12 mt-10 w-full">
<div v-if="isLoading" class="relative mt-10 h-12 w-full">
<Spinner class="spinner" />
</div>
</AuthContent>
@@ -132,248 +139,222 @@
</template>
<script>
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import {ValidationObserver, ValidationProvider} from 'vee-validate/dist/vee-validate.full'
import SocialiteAuthenticationButtons from "../../components/Auth/SocialiteAuthenticationButtons";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import Spinner from "../../components/FilesView/Spinner";
import {mapGetters} from 'vuex'
import {events} from '../../bus'
import axios from 'axios'
import Headline from "./Headline";
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import { ValidationObserver, ValidationProvider } from 'vee-validate/dist/vee-validate.full'
import SocialiteAuthenticationButtons from '../../components/Auth/SocialiteAuthenticationButtons'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import Spinner from '../../components/FilesView/Spinner'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
import axios from 'axios'
import Headline from './Headline'
export default {
name: 'SignIn',
components: {
SocialiteAuthenticationButtons,
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
Headline,
Spinner,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isLoading: false,
validSignIn: false,
checkedAccount: undefined,
loginPassword: '',
loginEmail: '',
twoFactorCode: '',
twoFactorRecoveryCode: '',
}
},
methods: {
goToAuthPage(slug) {
export default {
name: 'SignIn',
components: {
SocialiteAuthenticationButtons,
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
Headline,
Spinner,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
validSignIn: false,
checkedAccount: undefined,
loginPassword: '',
loginEmail: '',
twoFactorCode: '',
twoFactorRecoveryCode: '',
}
},
methods: {
goToAuthPage(slug) {
this.$refs.auth.$children.forEach((page) => {
// Hide current step
page.isVisible = page.$props.name === slug
})
},
resendEmail() {
axios
.post('/api/user/email/verify/resend', {
email: this.loginEmail,
})
.then(() => {
this.$router.push({ name: 'SuccessfullySend' })
})
.catch(() => {
this.$isSomethingWrong()
})
},
async logIn() {
// Validate fields
const isValid = await this.$refs.log_in.validate()
this.$refs.auth.$children.forEach(page => {
if (!isValid) return
// Hide current step
page.isVisible = page.$props.name === slug;
})
},
resendEmail() {
axios.post('/api/user/email/verify/resend', {
email: this.loginEmail
})
.then(() => {
this.$router.push({name: 'SuccessfullySend'})
})
.catch(() => {
this.$isSomethingWrong()
})
},
async logIn() {
// Start loading
this.isLoading = true
// Validate fields
const isValid = await this.$refs.log_in.validate();
// Send request to get verify account
axios
.post('/api/user/check', {
email: this.loginEmail,
})
.then((response) => {
// End loading
this.isLoading = false
if (!isValid) return;
this.checkedAccount = response.data
// Start loading
this.isLoading = true
if (response.data.oauth_provider) {
// Redirect user to socialite login if he's accout is registered by socialite
this.$store.dispatch('socialiteRedirect', response.data.oauth_provider)
} else {
// Show sign in password page
this.goToAuthPage('sign-in')
}
})
.catch((error) => {
if (error.response.status == 404) {
this.$refs.log_in.setErrors({
'E-Mail': [error.response.data],
})
}
// Send request to get verify account
axios
.post('/api/user/check', {
email: this.loginEmail,
})
.then(response => {
if (error.response.status == 500) {
events.$emit('alert:open', {
emoji: '🤔',
title: this.$t('popup_signup_error.title'),
message: this.$t('popup_signup_error.message'),
})
}
// End loading
this.isLoading = false
// End loading
this.isLoading = false
})
},
async singIn() {
// Validate fields
const isValid = this.validSignIn ? this.validSignIn : await this.$refs.sign_in.validate()
this.checkedAccount = response.data
if (!isValid) return
if (response.data.oauth_provider) {
// Redirect user to socialite login if he's accout is registered by socialite
this.$store.dispatch('socialiteRedirect', response.data.oauth_provider)
if (!this.checkedAccount.verified) {
this.goToAuthPage('not-verified')
} else {
// Show sign in password page
this.goToAuthPage('sign-in')
return
}
}
})
.catch(error => {
// Start loading
this.isLoading = true
if (error.response.status == 404) {
// Send request to get user token
axios
.post('/login', {
email: this.loginEmail,
password: this.loginPassword,
})
.then((response) => {
// End loading
this.isLoading = false
this.$refs.log_in.setErrors({
'E-Mail': [error.response.data]
});
}
// If is enabled two factor authentication
if (response.data.two_factor && !this.validSignIn) {
this.validSignIn = true
if (error.response.status == 500) {
this.goToAuthPage('two-factor-authentication')
events.$emit('alert:open', {
emoji: '🤔',
title: this.$t('popup_signup_error.title'),
message: this.$t('popup_signup_error.message')
})
}
// Autofocus to input
this.$nextTick(() => this.$refs.twoFactorCodeInput.focus())
}
// End loading
this.isLoading = false
})
},
async singIn() {
// If is disabled two factor authentication
if (!response.data.two_factor) {
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Validate fields
const isValid = this.validSignIn ? this.validSignIn : await this.$refs.sign_in.validate();
// Go to files page
this.proceedToAccount()
}
})
.catch((error) => {
if (error.response.status == 422) {
this.$refs.sign_in.setErrors({
'User Password': [this.$t('validation_errors.incorrect_password')],
})
}
if (!isValid) return;
// End loading
this.isLoading = false
})
},
async twoFactorChallenge(recovery) {
// Check if is normal authentication or recovery
if ((!recovery && this.twoFactorCode.length === 6) || (recovery && this.twoFactorRecoveryCode.length === 21)) {
this.isLoading = true
if (!this.checkedAccount.verified) {
let payload = recovery ? { recovery_code: this.twoFactorRecoveryCode } : { code: this.twoFactorCode }
this.goToAuthPage('not-verified')
axios
.post('/two-factor-challenge', payload)
.then(() => {
this.isLoading = false
return
}
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Start loading
this.isLoading = true
// Go to files page
this.proceedToAccount()
})
.catch((error) => {
if (error.response.status == 422) {
//Authentication bad input
if (!recovery) {
this.$refs.two_factor_authentication.setErrors({
'Two Factor Authentication': this.$t('validation_errors.incorrect_2fa_code'),
})
}
// Send request to get user token
axios
.post('/login', {
email: this.loginEmail,
password: this.loginPassword,
})
.then((response) => {
// Recovery bad input
if (recovery) {
this.$refs.two_factor_recovery.setErrors({
'Two Factor Recovery': this.$t('validation_errors.incorrect_2fa_recovery_code'),
})
}
}
// End loading
this.isLoading = false
// Repeat the login for next try to type right 2fa code / recovery code
this.singIn()
// If is enabled two factor authentication
if (response.data.two_factor && !this.validSignIn) {
this.isLoading = false
})
}
},
proceedToAccount() {
if (this.$route.query.redirect) {
this.$router.push(this.$route.query.redirect)
} else {
this.$router.push({ name: 'Files' })
}
},
},
created() {
this.$scrollTop()
this.$store.commit('PROCESSING_POPUP', undefined)
this.validSignIn = true
this.goToAuthPage('two-factor-authentication')
// Autofocus to input
this.$nextTick(() => this.$refs.twoFactorCodeInput.focus())
}
// If is disabled two factor authentication
if (!response.data.two_factor) {
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Go to files page
this.proceedToAccount()
}
})
.catch(error => {
if (error.response.status == 422) {
this.$refs.sign_in.setErrors({
'User Password': [this.$t('validation_errors.incorrect_password')]
});
}
// End loading
this.isLoading = false
})
},
async twoFactorChallenge(recovery) {
// Check if is normal authentication or recovery
if (!recovery && this.twoFactorCode.length === 6 || recovery && this.twoFactorRecoveryCode.length === 21) {
this.isLoading = true
let payload = recovery
? {recovery_code: this.twoFactorRecoveryCode}
: {code: this.twoFactorCode}
axios.post('/two-factor-challenge', payload)
.then(() => {
this.isLoading = false
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Go to files page
this.proceedToAccount()
})
.catch(error => {
if (error.response.status == 422) {
//Authentication bad input
if (!recovery) {
this.$refs.two_factor_authentication.setErrors({
'Two Factor Authentication': this.$t('validation_errors.incorrect_2fa_code')
})
}
// Recovery bad input
if (recovery) {
this.$refs.two_factor_recovery.setErrors({
'Two Factor Recovery': this.$t('validation_errors.incorrect_2fa_recovery_code')
})
}
}
// Repeat the login for next try to type right 2fa code / recovery code
this.singIn()
this.isLoading = false
})
}
},
proceedToAccount() {
if (this.$route.query.redirect) {
this.$router.push(this.$route.query.redirect)
} else {
this.$router.push({name: 'Files'})
}
}
},
created() {
this.$scrollTop()
this.$store.commit('PROCESSING_POPUP', undefined)
if (this.config.isDemo || this.config.isDev) {
this.loginEmail = 'howdy@hi5ve.digital'
this.loginPassword = 'vuefilemanager'
}
}
}
if (this.config.isDemo || this.config.isDev) {
this.loginEmail = 'howdy@hi5ve.digital'
this.loginPassword = 'vuefilemanager'
}
},
}
</script>

View File

@@ -1,72 +1,104 @@
<template>
<AuthContentWrapper ref="auth">
<!--Registration-->
<AuthContent name="sign-up" :visible="true" class="mt-4 mb-12">
<Headline
:title="$t('page_registration.title')"
:description="$t('page_registration.subtitle')"
/>
<Headline :title="$t('page_registration.title')" :description="$t('page_registration.subtitle')" />
<ValidationObserver @submit.prevent="signUp" ref="sign_up" v-slot="{ invalid }" tag="form" class="space-y-4 mb-12 text-left">
<ValidationObserver @submit.prevent="signUp" ref="sign_up" v-slot="{ invalid }" tag="form" class="mb-12 space-y-4 text-left">
<div class="mx-auto mb-5 md:flex md:max-w-lg md:items-center">
<label class="mb-1.5 block font-bold md:mb-0 md:w-72 md:pr-4 md:text-right"> {{ $t('page_registration.label_email') }}: </label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="E-Mail" rules="required" v-slot="{ errors }">
<input
v-model="register.email"
:placeholder="$t('page_registration.placeholder_email')"
type="email"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="md:flex md:items-center mb-5 md:max-w-lg mx-auto">
<label class="md:w-72 md:text-right md:pr-4 font-bold md:mb-0 mb-1.5 block">
{{ $t('page_registration.label_email') }}:
</label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="E-Mail" rules="required" v-slot="{ errors }">
<input v-model="register.email" :placeholder="$t('page_registration.placeholder_email')" type="email" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}"/>
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="mx-auto mb-5 md:flex md:max-w-lg md:items-center">
<label class="mb-1.5 block font-bold md:mb-0 md:w-72 md:pr-4 md:text-right"> {{ $t('page_registration.label_name') }}: </label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Full Name" rules="required" v-slot="{ errors }">
<input
v-model="register.name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="md:flex md:items-center mb-5 md:max-w-lg mx-auto">
<label class="md:w-72 md:text-right md:pr-4 font-bold md:mb-0 mb-1.5 block">
{{ $t('page_registration.label_name') }}:
</label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Full Name" rules="required" v-slot="{ errors }">
<input v-model="register.name" :placeholder="$t('page_registration.placeholder_name')" type="text" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}"/>
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="mx-auto mb-5 md:flex md:max-w-lg md:items-center">
<label class="mb-1.5 block font-bold md:mb-0 md:w-72 md:pr-4 md:text-right"> {{ $t('page_registration.label_pass') }}: </label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Your New Password" rules="required" v-slot="{ errors }">
<input
v-model="register.password"
:placeholder="$t('page_registration.placeholder_pass')"
type="password"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="md:flex md:items-center mb-5 md:max-w-lg mx-auto">
<label class="md:w-72 md:text-right md:pr-4 font-bold md:mb-0 mb-1.5 block">
{{ $t('page_registration.label_pass') }}:
</label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Your New Password" rules="required" v-slot="{ errors }">
<input v-model="register.password" :placeholder="$t('page_registration.placeholder_pass')" type="password" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}"/>
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="md:flex md:items-center mb-5 md:max-w-lg mx-auto">
<label class="md:w-72 md:text-right md:pr-4 font-bold md:mb-0 mb-1.5 block">
{{ $t('page_registration.label_confirm_pass') }}:
</label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Confirm Your Password" rules="required" v-slot="{ errors }">
<input v-model="register.password_confirmation" :placeholder="$t('page_registration.placeholder_confirm_pass')" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" type="password" :class="{'border-red': errors[0]}"/>
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="mx-auto mb-5 md:flex md:max-w-lg md:items-center">
<label class="mb-1.5 block font-bold md:mb-0 md:w-72 md:pr-4 md:text-right"> {{ $t('page_registration.label_confirm_pass') }}: </label>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Confirm Your Password" rules="required" v-slot="{ errors }">
<input
v-model="register.password_confirmation"
:placeholder="$t('page_registration.placeholder_confirm_pass')"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
type="password"
:class="{ 'border-red': errors[0] }"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="text-center">
<i18n path="page_registration.agreement" tag="p" class="mx-auto mt-12 mb-6 w-96 font-bold">
<router-link :to="{name: 'DynamicPage', params: {slug: 'terms-of-service'}}" target="_blank" class="text-theme">
{{ termsOfService.title }}
</router-link>
<router-link :to="{name: 'DynamicPage', params: {slug: 'privacy-policy'}}" target="_blank" class="text-theme">
{{ privacyPolicy.title }}
</router-link>
<router-link
:to="{
name: 'DynamicPage',
params: { slug: 'terms-of-service' },
}"
target="_blank"
class="text-theme"
>
{{ termsOfService.title }}
</router-link>
<router-link
:to="{
name: 'DynamicPage',
params: { slug: 'privacy-policy' },
}"
target="_blank"
class="text-theme"
>
{{ privacyPolicy.title }}
</router-link>
</i18n>
<AuthButton class="md:w-min w-full justify-center" icon="chevron-right" :text="$t('page_registration.button_create_account')" :loading="isLoading" :disabled="isLoading"/>
<AuthButton
class="w-full justify-center md:w-min"
icon="chevron-right"
:text="$t('page_registration.button_create_account')"
:loading="isLoading"
:disabled="isLoading"
/>
</div>
</ValidationObserver>
<SocialiteAuthenticationButtons/>
<SocialiteAuthenticationButtons />
<span class="block">{{ $t('page_registration.have_an_account') }}
<router-link :to="{name: 'SignIn'}" class="font-bold text-theme">
<span class="block"
>{{ $t('page_registration.have_an_account') }}
<router-link :to="{ name: 'SignIn' }" class="text-theme font-bold">
{{ $t('page_forgotten_password.password_remember_button') }}
</router-link>
</span>
@@ -75,129 +107,119 @@
</template>
<script>
import Headline from "./Headline";
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import SocialiteAuthenticationButtons from "../../components/Auth/SocialiteAuthenticationButtons";
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import {events} from '../../bus'
import axios from 'axios'
import Headline from './Headline'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import SocialiteAuthenticationButtons from '../../components/Auth/SocialiteAuthenticationButtons'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
import axios from 'axios'
export default {
name: 'SignUp',
components: {
SocialiteAuthenticationButtons,
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
Headline,
required,
export default {
name: 'SignUp',
components: {
SocialiteAuthenticationButtons,
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
AuthContent,
AuthButton,
Headline,
required,
},
computed: {
...mapGetters(['config']),
privacyPolicy() {
return this.config.legal.find((legal) => {
return legal.slug === 'privacy-policy'
})
},
computed: {
...mapGetters([
'config'
]),
privacyPolicy() {
return this.config.legal.find(legal => {
return legal.slug === 'privacy-policy'
})
},
termsOfService() {
return this.config.legal.find(legal => {
return legal.slug === 'terms-of-service'
})
},
termsOfService() {
return this.config.legal.find((legal) => {
return legal.slug === 'terms-of-service'
})
},
data() {
return {
isLoading: false,
register: {
name: '',
email: '',
password: '',
password_confirmation: '',
reCaptcha:null,
},
},
data() {
return {
isLoading: false,
register: {
name: '',
email: '',
password: '',
password_confirmation: '',
reCaptcha: null,
},
}
},
methods: {
async signUp() {
// Validate fields
const isValid = await this.$refs.sign_up.validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Get ReCaptcha token
if (config.allowedRecaptcha) {
this.register.reCaptcha = await this.$reCaptchaToken('register').then((response) => {
return response
})
}
},
methods: {
async signUp() {
// Validate fields
const isValid = await this.$refs.sign_up.validate();
// Send request to get user token
axios
.post('/api/register', this.register)
.then(() => {
// End loading
this.isLoading = false
if (!isValid) return;
if (!this.config.userVerification) {
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Start loading
this.isLoading = true
// Go to files page
this.$router.push({ name: 'Files' })
} else {
// Go to SuccessfullySend page
this.$router.push({ name: 'SuccessfullySend' })
}
})
.catch((error) => {
if (error.response.status == 500) {
events.$emit('alert:open', {
emoji: '🤔',
title: this.$t('popup_signup_error.title'),
message: this.$t('popup_signup_error.message'),
})
}
// Get ReCaptcha token
if(config.allowedRecaptcha) {
this.register.reCaptcha = await this.$reCaptchaToken('register').then((response) => {
return response
})
}
// Send request to get user token
axios
.post('/api/register', this.register)
.then(() => {
// End loading
this.isLoading = false
if(! this.config.userVerification) {
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Go to files page
this.$router.push({name: 'Files'})
} else {
// Go to SuccessfullySend page
this.$router.push({name: 'SuccessfullySend'})
}
})
.catch(error => {
if (error.response.status == 500) {
events.$emit('alert:open', {
emoji: '🤔',
title: this.$t('popup_signup_error.title'),
message: this.$t('popup_signup_error.message')
if (error.response.status == 422) {
if (error.response.data.errors['email']) {
this.$refs.sign_up.setErrors({
'E-Mail': error.response.data.errors['email'],
})
}
if (error.response.status == 422) {
if (error.response.data.errors['email']) {
this.$refs.sign_up.setErrors({
'E-Mail': error.response.data.errors['email']
});
}
if (error.response.data.errors['password']) {
this.$refs.sign_up.setErrors({
'Your New Password': error.response.data.errors['password']
});
}
if (error.response.data.errors['password']) {
this.$refs.sign_up.setErrors({
'Your New Password': error.response.data.errors['password'],
})
}
}
// End loading
this.isLoading = false
})
}
// End loading
this.isLoading = false
})
},
created() {
this.$scrollTop()
}
}
},
created() {
this.$scrollTop()
},
}
</script>

View File

@@ -1,38 +1,37 @@
<template>
<Spinner/>
<Spinner />
</template>
<script>
import Spinner from "../../components/FilesView/Spinner";
import {events} from "../../bus";
import Spinner from '../../components/FilesView/Spinner'
import { events } from '../../bus'
export default {
name: 'SocialiteCallback',
components: {
Spinner
},
created () {
Spinner,
},
created() {
axios
.get(`/api${this.$route.fullPath}`)
.then(() => {
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// Go to files page
this.$router.push({name: 'Files'})
this.$router.push({ name: 'Files' })
})
.catch(error => {
if (error.response.status === 401) {
events.$emit('alert:open', {
title: error.response.data.message,
})
} else {
this.$isSomethingWrong()
}
.catch((error) => {
if (error.response.status === 401) {
events.$emit('alert:open', {
title: error.response.data.message,
})
} else {
this.$isSomethingWrong()
}
this.$router.push({name: 'SignIn'})
})
}
this.$router.push({ name: 'SignIn' })
})
},
}
</script>
</script>

View File

@@ -1,31 +1,28 @@
<template>
<AuthContentWrapper class="h-screen">
<AuthContent :visible="true">
<Headline
:title="$t('page_email_successfully_verified.title')"
:description="$t('page_email_successfully_verified.subtitle')"
/>
<router-link :to="{name: 'SignIn'}">
<AuthButton icon="chevron-right" :text="$t('page_sign_in.button_log_in')"/>
<Headline :title="$t('page_email_successfully_verified.title')" :description="$t('page_email_successfully_verified.subtitle')" />
<router-link :to="{ name: 'SignIn' }">
<AuthButton icon="chevron-right" :text="$t('page_sign_in.button_log_in')" />
</router-link>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import Headline from "./Headline";
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import Headline from './Headline'
export default {
name: 'SuccessfullyEmailVerified',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
export default {
name: 'SuccessfullyEmailVerified',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
</script>

View File

@@ -1,13 +1,10 @@
<template>
<AuthContentWrapper class="h-screen">
<AuthContent :visible="true">
<Headline
:title="$t('page_email_successfully_send.title')"
:description="$t('page_email_successfully_send.subtitle')"
/>
<Headline :title="$t('page_email_successfully_send.title')" :description="$t('page_email_successfully_send.subtitle')" />
<span class="block">
<router-link :to="{name: 'Homepage'}" class="font-bold text-theme">
<span class="block">
<router-link :to="{ name: 'Homepage' }" class="text-theme font-bold">
{{ $t('go_home') }}
</router-link>
</span>
@@ -16,18 +13,18 @@
</template>
<script>
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import Headline from "./Headline"
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import Headline from './Headline'
export default {
name: 'SuccessfullySendEmail',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
export default {
name: 'SuccessfullySendEmail',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
</script>

View File

@@ -1,183 +1,172 @@
<template>
<ContentSidebar v-if="navigationTree && navigationTree.length >= 1">
<!--Locations-->
<ContentGroup :title="$t('sidebar.locations_title')">
<div class="menu-list-wrapper vertical">
<a class="menu-list-item link" @click="goHome">
<div class="icon">
<home-icon size="17"/>
</div>
<div class="label">
{{ $t('Home') }}
</div>
</a>
</div>
</ContentGroup>
<ContentSidebar v-if="navigationTree && navigationTree.length >= 1">
<!--Locations-->
<ContentGroup :title="$t('sidebar.locations_title')">
<div class="menu-list-wrapper vertical">
<a class="menu-list-item link" @click="goHome">
<div class="icon">
<home-icon size="17" />
</div>
<div class="label">
{{ $t('Home') }}
</div>
</a>
</div>
</ContentGroup>
<!--Navigator-->
<ContentGroup :title="$t('sidebar.navigator_title')" class="navigator">
<TreeMenuNavigator class="folder-tree" :depth="0" :nodes="folder" v-for="folder in navigationTree" :key="folder.id" />
</ContentGroup>
</ContentSidebar>
<!--Navigator-->
<ContentGroup :title="$t('sidebar.navigator_title')" class="navigator">
<TreeMenuNavigator class="folder-tree" :depth="0" :nodes="folder" v-for="folder in navigationTree" :key="folder.id" />
</ContentGroup>
</ContentSidebar>
</template>
<script>
import {FolderIcon, HomeIcon, LinkIcon, Trash2Icon, UploadCloudIcon, UserCheckIcon, UsersIcon, XIcon} from "vue-feather-icons";
import TreeMenuNavigator from "../../../components/Others/TreeMenuNavigator";
import ContentSidebar from "../../../components/Sidebar/ContentSidebar";
import ContentGroup from "../../../components/Sidebar/ContentGroup";
import {events} from "../../../bus";
import {mapGetters} from "vuex";
import { FolderIcon, HomeIcon, LinkIcon, Trash2Icon, UploadCloudIcon, UserCheckIcon, UsersIcon, XIcon } from 'vue-feather-icons'
import TreeMenuNavigator from '../../../components/Others/TreeMenuNavigator'
import ContentSidebar from '../../../components/Sidebar/ContentSidebar'
import ContentGroup from '../../../components/Sidebar/ContentGroup'
import { events } from '../../../bus'
import { mapGetters } from 'vuex'
export default {
name: "NavigationSharePanel",
components: {
TreeMenuNavigator,
ContentSidebar,
ContentGroup,
UploadCloudIcon,
UserCheckIcon,
FolderIcon,
Trash2Icon,
UsersIcon,
HomeIcon,
LinkIcon,
XIcon,
},
computed: {
...mapGetters([
'sharedDetail',
'navigation',
'clipboard',
'config',
'user',
]),
favourites() {
return this.user.data.relationships.favourites.data.attributes.folders
},
storage() {
return this.$store.getters.user.data.attributes.storage
},
tree() {
return this.user.data.attributes.folders
},
navigationTree() {
return this.navigation ? this.navigation[0].folders : undefined
},
},
data() {
return {
draggedItem: undefined,
area: false,
}
},
methods: {
goHome() {
this.$router.replace({
name: 'Public',
params: {
token: this.sharedDetail.data.attributes.token,
id: this.sharedDetail.data.attributes.item_id
}
})
},
dragLeave() {
this.area = false
},
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
name: 'NavigationSharePanel',
components: {
TreeMenuNavigator,
ContentSidebar,
ContentGroup,
UploadCloudIcon,
UserCheckIcon,
FolderIcon,
Trash2Icon,
UsersIcon,
HomeIcon,
LinkIcon,
XIcon,
},
computed: {
...mapGetters(['sharedDetail', 'navigation', 'clipboard', 'config', 'user']),
favourites() {
return this.user.data.relationships.favourites.data.attributes.folders
},
storage() {
return this.$store.getters.user.data.attributes.storage
},
tree() {
return this.user.data.attributes.folders
},
navigationTree() {
return this.navigation ? this.navigation[0].folders : undefined
},
},
data() {
return {
draggedItem: undefined,
area: false,
}
},
methods: {
goHome() {
this.$router.replace({
name: 'Public',
params: {
token: this.sharedDetail.data.attributes.token,
id: this.sharedDetail.data.attributes.item_id,
},
})
},
dragLeave() {
this.area = false
},
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
if (this.clipboard.length > 0 && this.clipboard.find(item => item.type !== 'folder')) return
if (this.clipboard.length > 0 && this.clipboard.find((item) => item.type !== 'folder')) return
this.area = true
},
dragFinish() {
this.area = false
this.area = true
},
dragFinish() {
this.area = false
events.$emit('drop')
events.$emit('drop')
// Check if dragged item is folder
if (this.draggedItem && this.draggedItem.type !== 'folder') return
// Check if dragged item is folder
if (this.draggedItem && this.draggedItem.type !== 'folder') return
// Check if folder exist in favourites
if (this.favourites.find(folder => folder.id === this.draggedItem.id)) return
// Check if folder exist in favourites
if (this.favourites.find((folder) => folder.id === this.draggedItem.id)) return
// Prevent to move folders to self
if (this.clipboard.length > 0 && this.clipboard.find(item => item.type !== 'folder')) return
// Prevent to move folders to self
if (this.clipboard.length > 0 && this.clipboard.find((item) => item.type !== 'folder')) return
//Add to favourites non selected folder
if (!this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', this.draggedItem)
}
//Add to favourites non selected folder
if (!this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', this.draggedItem)
}
//Add to favourites selected folders
if (this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', null)
}
},
},
created() {
// Listen for dragstart folder items
events.$on('dragstart', item => this.draggedItem = item)
//Add to favourites selected folders
if (this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', null)
}
},
},
created() {
// Listen for dragstart folder items
events.$on('dragstart', (item) => (this.draggedItem = item))
// Get folder tree
this.$store.dispatch('getFolderTree')
}
// Get folder tree
this.$store.dispatch('getFolderTree')
},
}
</script>
<style lang="scss" scoped>
.empty-note {
&.navigator {
padding: 5px 25px 10px;
}
.empty-note {
&.favourites {
padding: 5px 23px 10px;
}
}
&.navigator {
padding: 5px 25px 10px;
}
.navigator {
width: 100%;
overflow-x: auto;
}
&.favourites {
padding: 5px 23px 10px;
}
}
@media only screen and (max-width: 1024px) {
.empty-note {
&.navigator {
padding: 5px 20px 10px;
}
.navigator {
width: 100%;
overflow-x: auto;
}
&.favourites {
padding: 5px 18px 10px;
}
}
}
@media only screen and (max-width: 1024px) {
// Transition
.folder-item-move {
transition: all 300s ease;
}
.empty-note {
.folder-item-enter-active {
transition: all 300ms ease;
}
&.navigator {
padding: 5px 20px 10px;
}
.folder-item-leave-active {
transition: all 300ms;
}
&.favourites {
padding: 5px 18px 10px;
}
}
}
.folder-item-enter, .folder-item-leave-to /* .list-leave-active below version 2.1.8 */ {
opacity: 0;
transform: translateX(30px);
}
// Transition
.folder-item-move {
transition: all 300s ease;
}
.folder-item-enter-active {
transition: all 300ms ease;
}
.folder-item-leave-active {
transition: all 300ms;
}
.folder-item-enter, .folder-item-leave-to /* .list-leave-active below version 2.1.8 */
{
opacity: 0;
transform: translateX(30px);
}
.folder-item-leave-active {
position: absolute;
}
</style>
.folder-item-leave-active {
position: absolute;
}
</style>

View File

@@ -1,211 +1,219 @@
<template>
<ContentSidebar v-if="isVisibleNavigationBars" class="relative">
<ContentSidebar v-if="isVisibleNavigationBars" class="relative">
<!--Full screen button-->
<div @click="toggleNavigationBars" class="absolute top-2.5 right-0 inline-block cursor-pointer p-3 opacity-0 transition-all duration-200 hover:opacity-70">
<chevrons-left-icon size="18" />
</div>
<!--Full screen button-->
<div @click="toggleNavigationBars" class="inline-block absolute top-2.5 right-0 p-3 cursor-pointer transition-all duration-200 hover:opacity-70 opacity-0">
<chevrons-left-icon size="18"/>
</div>
<!--Locations-->
<ContentGroup v-for="(menu, i) in nav" :key="i" :title="menu.groupTitle" :slug="menu.groupTitle" :can-collapse="menu.groupCollapsable">
<router-link v-for="(item, i) in menu.groupLinks" :key="i" @click.native="resetData" :to="{ name: item.route }" class="flex items-center py-2.5">
<home-icon v-if="item.icon === 'home'" size="17" class="vue-feather icon-active mr-2.5" />
<upload-cloud-icon v-if="item.icon === 'upload-cloud'" size="17" class="vue-feather icon-active mr-2.5" />
<link-icon v-if="item.icon === 'link'" size="17" class="vue-feather icon-active mr-2.5" />
<trash2-icon v-if="item.icon === 'trash'" size="17" class="vue-feather icon-active mr-2.5" />
<users-icon size="17" v-if="item.icon === 'users'" class="vue-feather icon-active mr-2.5" />
<user-check-icon size="17" v-if="item.icon === 'user-check'" class="vue-feather icon-active mr-2.5" />
<!--Locations-->
<ContentGroup v-for="(menu, i) in nav" :key="i" :title="menu.groupTitle" :slug="menu.groupTitle" :can-collapse="menu.groupCollapsable">
<router-link v-for="(item, i) in menu.groupLinks" :key="i" @click.native="resetData" :to="{name: item.route}" class="flex items-center py-2.5">
<home-icon v-if="item.icon === 'home'" size="17" class="mr-2.5 vue-feather icon-active"/>
<upload-cloud-icon v-if="item.icon === 'upload-cloud'" size="17" class="mr-2.5 vue-feather icon-active" />
<link-icon v-if="item.icon === 'link'" size="17" class="mr-2.5 vue-feather icon-active" />
<trash2-icon v-if="item.icon === 'trash'" size="17" class="mr-2.5 vue-feather icon-active" />
<users-icon size="17" v-if="item.icon === 'users'" class="mr-2.5 vue-feather icon-active" />
<user-check-icon size="17" v-if="item.icon === 'user-check'" class="mr-2.5 vue-feather icon-active" />
<b class="text-active text-xs font-bold">
{{ item.title }}
</b>
</router-link>
</ContentGroup>
<b class="font-bold text-xs text-active">
{{ item.title }}
</b>
</router-link>
</ContentGroup>
<!--Navigator-->
<ContentGroup v-if="navigation" :title="$t('sidebar.navigator_title')" slug="navigator" :can-collapse="true">
<small v-if="tree.length === 0" class="text-xs font-bold text-gray-500">
{{ $t("There isn't any folder.") }}
</small>
<TreeMenuNavigator :depth="0" :nodes="folder" v-for="folder in tree" :key="folder.id" />
</ContentGroup>
<!--Navigator-->
<ContentGroup v-if="navigation" :title="$t('sidebar.navigator_title')" slug="navigator" :can-collapse="true">
<small v-if="tree.length === 0" class="text-xs font-bold text-gray-500">
{{ $t("There isn't any folder.") }}
</small>
<TreeMenuNavigator :depth="0" :nodes="folder" v-for="folder in tree" :key="folder.id"/>
</ContentGroup>
<!--Favourites-->
<ContentGroup v-if="user" :title="$t('sidebar.favourites')" slug="favourites" :can-collapse="true">
<div
@dragover.prevent="dragEnter"
@dragleave="dragLeave"
@drop="dragFinish($event)"
:class="{ 'border-theme': area }"
class="border-2 border-dashed border-transparent"
>
<!--Empty message-->
<small v-if="favourites.length === 0" class="favourites text-xs font-bold text-gray-500" :key="0">
{{ $t('sidebar.favourites_empty') }}
</small>
<!--Favourites-->
<ContentGroup v-if="user" :title="$t('sidebar.favourites')" slug="favourites" :can-collapse="true">
<div @dragover.prevent="dragEnter" @dragleave="dragLeave" @drop="dragFinish($event)" :class="{'border-theme': area }" class="border-2 border-transparent border-dashed">
<!--Empty message-->
<small v-if="favourites.length === 0" class="text-xs font-bold text-gray-500 favourites" :key="0">
{{ $t('sidebar.favourites_empty') }}
</small>
<!--Folder item-->
<div @click="goToFolder(folder)" v-for="folder in favourites" :key="folder.data.id" class="group flex items-center justify-between py-2.5 cursor-pointer">
<div class="flex items-center">
<folder-icon size="17" class="mr-2.5 vue-feather" :class="{'text-theme': $route.params.id === folder.data.id}" />
<span class="font-bold text-xs max-w-1 overflow-hidden text-ellipsis whitespace-nowrap" :class="{'text-theme': $route.params.id === folder.data.id}">
{{ folder.data.attributes.name }}
</span>
</div>
<x-icon @click.stop="$removeFavourite(folder)" size="12" class="mr-5 group-hover:opacity-100 opacity-0" />
</div>
</div>
</ContentGroup>
</ContentSidebar>
<!--Folder item-->
<div @click="goToFolder(folder)" v-for="folder in favourites" :key="folder.data.id" class="group flex cursor-pointer items-center justify-between py-2.5">
<div class="flex items-center">
<folder-icon
size="17"
class="vue-feather mr-2.5"
:class="{
'text-theme': $route.params.id === folder.data.id,
}"
/>
<span
class="max-w-1 overflow-hidden text-ellipsis whitespace-nowrap text-xs font-bold"
:class="{
'text-theme': $route.params.id === folder.data.id,
}"
>
{{ folder.data.attributes.name }}
</span>
</div>
<x-icon @click.stop="$removeFavourite(folder)" size="12" class="mr-5 opacity-0 group-hover:opacity-100" />
</div>
</div>
</ContentGroup>
</ContentSidebar>
</template>
<script>
import { ChevronsLeftIcon, FolderIcon, HomeIcon, LinkIcon, Trash2Icon, UploadCloudIcon, UserCheckIcon, UsersIcon, XIcon} from "vue-feather-icons";
import TreeMenuNavigator from "../../../components/Others/TreeMenuNavigator";
import ContentSidebar from "../../../components/Sidebar/ContentSidebar";
import ContentGroup from "../../../components/Sidebar/ContentGroup";
import {events} from "../../../bus";
import {mapGetters} from "vuex";
import { ChevronsLeftIcon, FolderIcon, HomeIcon, LinkIcon, Trash2Icon, UploadCloudIcon, UserCheckIcon, UsersIcon, XIcon } from 'vue-feather-icons'
import TreeMenuNavigator from '../../../components/Others/TreeMenuNavigator'
import ContentSidebar from '../../../components/Sidebar/ContentSidebar'
import ContentGroup from '../../../components/Sidebar/ContentGroup'
import { events } from '../../../bus'
import { mapGetters } from 'vuex'
export default {
name: "PanelNavigationFiles",
components: {
TreeMenuNavigator,
ContentSidebar,
ContentGroup,
ChevronsLeftIcon,
UploadCloudIcon,
UserCheckIcon,
FolderIcon,
Trash2Icon,
UsersIcon,
HomeIcon,
LinkIcon,
XIcon,
},
computed: {
...mapGetters([
'isVisibleNavigationBars',
'navigation',
'clipboard',
'config',
'user',
]),
favourites() {
return this.user.data.relationships.favourites.data
},
storage() {
return this.$store.getters.user.data.attributes.storage
},
tree() {
return {
'RecentUploads': this.navigation[0].folders,
'MySharedItems': this.navigation[0].folders,
'Trash': this.navigation[0].folders,
'Public': this.navigation[0].folders,
'Files': this.navigation[0].folders,
'TeamFolders': this.navigation[1].folders,
'SharedWithMe': this.navigation[2].folders,
}[this.$route.name]
},
},
data() {
return {
draggedItem: undefined,
area: false,
nav: [
{
groupCollapsable: false,
groupTitle: this.$t('sidebar.locations_title'),
groupLinks: [
{
icon: 'home',
route: 'Files',
title: this.$t('sidebar.home'),
},
{
icon: 'upload-cloud',
route: 'RecentUploads',
title: this.$t('sidebar.latest'),
},
{
icon: 'link',
route: 'MySharedItems',
title: this.$t('sidebar.my_shared'),
},
{
icon: 'trash',
route: 'Trash',
title: this.$t('locations.trash'),
},
],
},
{
groupCollapsable: true,
groupTitle: this.$t('Collaboration'),
groupLinks: [
{
icon: 'users',
route: 'TeamFolders',
title: this.$t('Team Folders'),
},
{
icon: 'user-check',
route: 'SharedWithMe',
title: this.$t('Shared with Me'),
},
],
},
],
}
},
methods: {
toggleNavigationBars() {
this.$store.dispatch('toggleNavigationBars')
},
resetData() {
this.$store.commit('SET_CURRENT_TEAM_FOLDER', null)
this.$store.commit('CLIPBOARD_CLEAR')
},
goToFolder(folder) {
this.$router.push({name: 'Files', params: {id: folder.data.id}})
},
dragLeave() {
this.area = false
},
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
name: 'PanelNavigationFiles',
components: {
TreeMenuNavigator,
ContentSidebar,
ContentGroup,
ChevronsLeftIcon,
UploadCloudIcon,
UserCheckIcon,
FolderIcon,
Trash2Icon,
UsersIcon,
HomeIcon,
LinkIcon,
XIcon,
},
computed: {
...mapGetters(['isVisibleNavigationBars', 'navigation', 'clipboard', 'config', 'user']),
favourites() {
return this.user.data.relationships.favourites.data
},
storage() {
return this.$store.getters.user.data.attributes.storage
},
tree() {
return {
RecentUploads: this.navigation[0].folders,
MySharedItems: this.navigation[0].folders,
Trash: this.navigation[0].folders,
Public: this.navigation[0].folders,
Files: this.navigation[0].folders,
TeamFolders: this.navigation[1].folders,
SharedWithMe: this.navigation[2].folders,
}[this.$route.name]
},
},
data() {
return {
draggedItem: undefined,
area: false,
nav: [
{
groupCollapsable: false,
groupTitle: this.$t('sidebar.locations_title'),
groupLinks: [
{
icon: 'home',
route: 'Files',
title: this.$t('sidebar.home'),
},
{
icon: 'upload-cloud',
route: 'RecentUploads',
title: this.$t('sidebar.latest'),
},
{
icon: 'link',
route: 'MySharedItems',
title: this.$t('sidebar.my_shared'),
},
{
icon: 'trash',
route: 'Trash',
title: this.$t('locations.trash'),
},
],
},
{
groupCollapsable: true,
groupTitle: this.$t('Collaboration'),
groupLinks: [
{
icon: 'users',
route: 'TeamFolders',
title: this.$t('Team Folders'),
},
{
icon: 'user-check',
route: 'SharedWithMe',
title: this.$t('Shared with Me'),
},
],
},
],
}
},
methods: {
toggleNavigationBars() {
this.$store.dispatch('toggleNavigationBars')
},
resetData() {
this.$store.commit('SET_CURRENT_TEAM_FOLDER', null)
this.$store.commit('CLIPBOARD_CLEAR')
},
goToFolder(folder) {
this.$router.push({ name: 'Files', params: { id: folder.data.id } })
},
dragLeave() {
this.area = false
},
dragEnter() {
if (this.draggedItem && this.draggedItem.type !== 'folder') return
if (this.clipboard.length > 0 && this.clipboard.find(item => item.type !== 'folder')) return
if (this.clipboard.length > 0 && this.clipboard.find((item) => item.type !== 'folder')) return
this.area = true
},
dragFinish() {
this.area = false
this.area = true
},
dragFinish() {
this.area = false
events.$emit('drop')
events.$emit('drop')
// Check if dragged item is folder
if (this.draggedItem && this.draggedItem.type !== 'folder') return
// Check if dragged item is folder
if (this.draggedItem && this.draggedItem.type !== 'folder') return
// Check if folder exist in favourites
if (this.favourites.find(folder => folder.id === this.draggedItem.id)) return
// Check if folder exist in favourites
if (this.favourites.find((folder) => folder.id === this.draggedItem.id)) return
// Prevent to move folders to self
if (this.clipboard.length > 0 && this.clipboard.find(item => item.type !== 'folder')) return
// Prevent to move folders to self
if (this.clipboard.length > 0 && this.clipboard.find((item) => item.type !== 'folder')) return
//Add to favourites non selected folder
if (!this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', this.draggedItem)
}
//Add to favourites non selected folder
if (!this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', this.draggedItem)
}
//Add to favourites selected folders
if (this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', null)
}
},
},
created() {
// Listen for dragstart folder items
events.$on('dragstart', item => this.draggedItem = item)
//Add to favourites selected folders
if (this.clipboard.includes(this.draggedItem)) {
this.$store.dispatch('addToFavourites', null)
}
},
},
created() {
// Listen for dragstart folder items
events.$on('dragstart', (item) => (this.draggedItem = item))
this.$store.dispatch('getFolderTree')
}
this.$store.dispatch('getFolderTree')
},
}
</script>
</script>

View File

@@ -1,215 +1,238 @@
<template>
<div>
<MobileContextMenu>
<OptionGroup v-if="item && isFolder">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
<div>
<MobileContextMenu>
<OptionGroup v-if="item && isFolder">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="$convertAsTeamFolder(item)" v-if="isFolder" :title="$t('Convert as Team Folder')" icon="users" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="$convertAsTeamFolder(item)" v-if="isFolder" :title="$t('Convert as Team Folder')" icon="users" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
</MobileContextMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :class="{'is-inactive': !hasCapacity }" :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="$createTeamFolder" :title="$t('Create Team Folder')" icon="users" :is-hover-disabled="true" />
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :class="{ 'is-inactive': !hasCapacity }" :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="$createTeamFolder" :title="$t('Create Team Folder')" icon="users" :is-hover-disabled="true" />
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileMultiSelectToolbar>
<ToolbarButton @click.native="$moveFileOrFolder(clipboard)" class="action-btn" source="move" :action="$t('actions.move')" :class="{'is-inactive' : clipboard.length < 1}" />
<ToolbarButton @click.native="$deleteFileOrFolder(clipboard)" class="action-btn" source="trash" :class="{'is-inactive' : clipboard.length < 1}" :action="$t('actions.delete')" />
<MobileMultiSelectToolbar>
<ToolbarButton
@click.native="$moveFileOrFolder(clipboard)"
class="action-btn"
source="move"
:action="$t('actions.move')"
:class="{ 'is-inactive': clipboard.length < 1 }"
/>
<ToolbarButton
@click.native="$deleteFileOrFolder(clipboard)"
class="action-btn"
source="trash"
:class="{ 'is-inactive': clipboard.length < 1 }"
:action="$t('actions.delete')"
/>
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
</MobileMultiSelectToolbar>
</MobileMultiSelectToolbar>
<ContextMenu>
<template v-slot:empty-select>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$createFolder" :title="$t('context_menu.create_folder')" icon="create-folder" />
</OptionGroup>
</template>
<ContextMenu>
<template v-slot:empty-select>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$createFolder" :title="$t('context_menu.create_folder')" icon="create-folder" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="isFolder">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="$convertAsTeamFolder(item)" v-if="isFolder" :title="$t('Convert as Team Folder')" icon="user-plus" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="isFolder">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option
@click.native="$shareFileOrFolder(item)"
:title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')"
icon="share"
/>
<Option @click.native="$convertAsTeamFolder(item)" v-if="isFolder" :title="$t('Convert as Team Folder')" icon="user-plus" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="!hasFile">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="!hasFile">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('create-list')" v-if="$checkPermission(['master', 'editor'])" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('create-list')" v-if="$checkPermission(['master', 'editor'])" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<EmptyFilePage>
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</EmptyFilePage>
<EmptyFilePage>
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</EmptyFilePage>
<FileBrowser />
</div>
<FileBrowser />
</div>
</template>
<script>
import EmptyFilePage from "../../components/FilesView/EmptyFilePage";
import FileActionsMobile from "../../components/FilesView/FileActionsMobile";
import MobileActionButtonUpload from "../../components/FilesView/MobileActionButtonUpload";
import MobileMultiSelectToolbar from "../../components/FilesView/MobileMultiSelectToolbar";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileContextMenu from "../../components/FilesView/MobileContextMenu";
import MobileCreateMenu from "../../components/FilesView/MobileCreateMenu";
import ButtonUpload from "../../components/FilesView/ButtonUpload";
import ToolbarButton from "../../components/FilesView/ToolbarButton";
import OptionUpload from "../../components/FilesView/OptionUpload";
import FileBrowser from "../../components/FilesView/FileBrowser";
import ContextMenu from "../../components/FilesView/ContextMenu";
import OptionGroup from "../../components/FilesView/OptionGroup";
import Option from "../../components/FilesView/Option";
import { mapGetters } from 'vuex'
import {events} from "../../bus";
import EmptyFilePage from '../../components/FilesView/EmptyFilePage'
import FileActionsMobile from '../../components/FilesView/FileActionsMobile'
import MobileActionButtonUpload from '../../components/FilesView/MobileActionButtonUpload'
import MobileMultiSelectToolbar from '../../components/FilesView/MobileMultiSelectToolbar'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileContextMenu from '../../components/FilesView/MobileContextMenu'
import MobileCreateMenu from '../../components/FilesView/MobileCreateMenu'
import ButtonUpload from '../../components/FilesView/ButtonUpload'
import ToolbarButton from '../../components/FilesView/ToolbarButton'
import OptionUpload from '../../components/FilesView/OptionUpload'
import FileBrowser from '../../components/FilesView/FileBrowser'
import ContextMenu from '../../components/FilesView/ContextMenu'
import OptionGroup from '../../components/FilesView/OptionGroup'
import Option from '../../components/FilesView/Option'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'Files',
components: {
EmptyFilePage,
FileActionsMobile,
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
ButtonUpload,
OptionUpload,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
},
computed: {
...mapGetters([
'fastPreview',
'clipboard',
'config',
'user',
]),
hasCapacity() {
// Check if storage limitation is set
if (!this.config.storageLimit) return true
export default {
name: 'Files',
components: {
EmptyFilePage,
FileActionsMobile,
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
ButtonUpload,
OptionUpload,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
},
computed: {
...mapGetters(['fastPreview', 'clipboard', 'config', 'user']),
hasCapacity() {
// Check if storage limitation is set
if (!this.config.storageLimit) return true
// Check if user has storage
return this.user && this.user.data.attributes.storage.used <= 100
},
isFolder() {
return this.item && this.item.data.type === 'folder'
},
isInFavourites() {
return this.favourites.find((el) => el.id === this.item.data.id)
},
hasFile() {
return this.clipboard.find(item => item.data.type !== 'folder')
},
favourites() {
return this.user.data.relationships.favourites.data
},
},
data() {
return {
item: undefined,
}
},
methods: {
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.favourites && !this.favourites.find(el => el.id === this.item.data.id)) {
// Add to favourite folder that is not selected
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', this.item)
}
// Check if user has storage
return this.user && this.user.data.attributes.storage.used <= 100
},
isFolder() {
return this.item && this.item.data.type === 'folder'
},
isInFavourites() {
return this.favourites.find((el) => el.id === this.item.data.id)
},
hasFile() {
return this.clipboard.find((item) => item.data.type !== 'folder')
},
favourites() {
return this.user.data.relationships.favourites.data
},
},
data() {
return {
item: undefined,
}
},
methods: {
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.favourites && !this.favourites.find((el) => el.id === this.item.data.id)) {
// Add to favourite folder that is not selected
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', this.item)
}
// Add to favourites all selected folders
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', null)
}
} else {
this.$store.dispatch('removeFromFavourites', this.item)
}
},
createFolder() {
events.$emit('popup:open', {name: 'create-folder'})
},
},
created() {
this.$store.dispatch('getFolder', this.$route.params.id)
// Add to favourites all selected folders
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', null)
}
} else {
this.$store.dispatch('removeFromFavourites', this.item)
}
},
createFolder() {
events.$emit('popup:open', { name: 'create-folder' })
},
},
created() {
this.$store.dispatch('getFolder', this.$route.params.id)
events.$on('context-menu:show', (event, item) => this.item = item)
events.$on('context-menu:current-folder', folder => this.item = folder)
events.$on('mobile-context-menu:show', item => this.item = item)
}
}
events.$on('context-menu:show', (event, item) => (this.item = item))
events.$on('context-menu:current-folder', (folder) => (this.item = folder))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
},
}
</script>

View File

@@ -1,157 +1,170 @@
<template>
<div>
<ContextMenu>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="isFolder">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<div>
<ContextMenu>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="isFolder">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option
@click.native="$shareFileOrFolder(item)"
:title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')"
icon="share"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="!hasFile">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareCancel" :title="$t('context_menu.share_cancel')" icon="share" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="!hasFile">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareCancel" :title="$t('context_menu.share_cancel')" icon="share" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<MobileContextMenu>
<OptionGroup v-if="isFolder">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareFileOrFolder(item)" :title="item && item.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
<MobileContextMenu>
<OptionGroup v-if="isFolder">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareFileOrFolder(item)" :title="item && item.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight')}}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<EmptyFilePage>
<h1 class="title">{{ $t('shared.empty_shared') }}</h1>
</EmptyFilePage>
<EmptyFilePage>
<h1 class="title">{{ $t('shared.empty_shared') }}</h1>
</EmptyFilePage>
<FileBrowser />
<FileBrowser />
<MobileMultiSelectToolbar>
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
<ToolbarButton @click.native="$shareCancel" class="action-btn" source="shared-off" />
</MobileMultiSelectToolbar>
</div>
<MobileMultiSelectToolbar>
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
<ToolbarButton @click.native="$shareCancel" class="action-btn" source="shared-off" />
</MobileMultiSelectToolbar>
</div>
</template>
<script>
import EmptyFilePage from "../../components/FilesView/EmptyFilePage";
import FileActionsMobile from "../../components/FilesView/FileActionsMobile";
import MobileActionButtonUpload from "../../components/FilesView/MobileActionButtonUpload";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileMultiSelectToolbar from "../../components/FilesView/MobileMultiSelectToolbar";
import MobileContextMenu from "../../components/FilesView/MobileContextMenu";
import ToolbarButton from "../../components/FilesView/ToolbarButton";
import FileBrowser from "../../components/FilesView/FileBrowser";
import ContextMenu from "../../components/FilesView/ContextMenu";
import OptionGroup from "../../components/FilesView/OptionGroup";
import Option from "../../components/FilesView/Option";
import { mapGetters } from 'vuex'
import {events} from "../../bus";
import EmptyFilePage from '../../components/FilesView/EmptyFilePage'
import FileActionsMobile from '../../components/FilesView/FileActionsMobile'
import MobileActionButtonUpload from '../../components/FilesView/MobileActionButtonUpload'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileMultiSelectToolbar from '../../components/FilesView/MobileMultiSelectToolbar'
import MobileContextMenu from '../../components/FilesView/MobileContextMenu'
import ToolbarButton from '../../components/FilesView/ToolbarButton'
import FileBrowser from '../../components/FilesView/FileBrowser'
import ContextMenu from '../../components/FilesView/ContextMenu'
import OptionGroup from '../../components/FilesView/OptionGroup'
import Option from '../../components/FilesView/Option'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'MySharedItems',
components: {
MobileActionButtonUpload,
MobileActionButton,
MobileMultiSelectToolbar,
MobileContextMenu,
ToolbarButton,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters([
'clipboard',
'user',
]),
isFolder() {
return this.item && this.item.type === 'folder'
},
isInFavourites() {
return this.favourites.find(el => el.id === this.item.id)
},
hasFile() {
return this.clipboard.find(item => item.type !== 'folder')
},
favourites() {
return this.user.data.relationships.favourites.data.attributes.folders
},
},
data() {
return {
item: undefined,
}
},
methods: {
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.favourites && !this.favourites.find(el => el.id === this.item.data.id)) {
// Add to favourite folder that is not selected
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', this.item)
}
export default {
name: 'MySharedItems',
components: {
MobileActionButtonUpload,
MobileActionButton,
MobileMultiSelectToolbar,
MobileContextMenu,
ToolbarButton,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters(['clipboard', 'user']),
isFolder() {
return this.item && this.item.type === 'folder'
},
isInFavourites() {
return this.favourites.find((el) => el.id === this.item.id)
},
hasFile() {
return this.clipboard.find((item) => item.type !== 'folder')
},
favourites() {
return this.user.data.relationships.favourites.data.attributes.folders
},
},
data() {
return {
item: undefined,
}
},
methods: {
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.favourites && !this.favourites.find((el) => el.id === this.item.data.id)) {
// Add to favourite folder that is not selected
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', this.item)
}
// Add to favourites all selected folders
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', null)
}
} else {
this.$store.dispatch('removeFromFavourites', this.item)
}
},
},
created() {
this.$store.dispatch('getMySharedItems')
// Add to favourites all selected folders
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', null)
}
} else {
this.$store.dispatch('removeFromFavourites', this.item)
}
},
},
created() {
this.$store.dispatch('getMySharedItems')
events.$on('context-menu:show', (event, item) => this.item = item)
events.$on('mobile-context-menu:show', item => this.item = item)
}
}
events.$on('context-menu:show', (event, item) => (this.item = item))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
},
}
</script>

View File

@@ -1,183 +1,193 @@
<template>
<div>
<MobileContextMenu>
<template v-slot:editor>
<OptionGroup>
<Option v-if="item" @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option v-if="item" @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:visitor>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</MobileContextMenu>
<div>
<MobileContextMenu>
<template v-slot:editor>
<OptionGroup>
<Option v-if="item" @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option v-if="item" @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:visitor>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</MobileContextMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" :is-hover-disabled="true" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" :is-hover-disabled="true" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileMultiSelectToolbar>
<template v-slot:visitor>
<ToolbarButton @click.native="downloadItem" class="action-btn" source="download" :action="$t('actions.download')" />
</template>
<template v-slot:editor>
<ToolbarButton @click.native="$moveFileOrFolder(clipboard)" class="action-btn" source="move" :action="$t('actions.move')" :class="{'is-inactive': clipboard.length < 1}" />
<ToolbarButton @click.native="$deleteFileOrFolder(clipboard)" class="action-btn" source="trash" :class="{'is-inactive': clipboard.length < 1}" :action="$t('actions.delete')" />
<ToolbarButton @click.native="downloadItem" class="action-btn" source="download" :action="$t('actions.download')" />
</template>
</MobileMultiSelectToolbar>
<MobileMultiSelectToolbar>
<template v-slot:visitor>
<ToolbarButton @click.native="downloadItem" class="action-btn" source="download" :action="$t('actions.download')" />
</template>
<template v-slot:editor>
<ToolbarButton
@click.native="$moveFileOrFolder(clipboard)"
class="action-btn"
source="move"
:action="$t('actions.move')"
:class="{ 'is-inactive': clipboard.length < 1 }"
/>
<ToolbarButton
@click.native="$deleteFileOrFolder(clipboard)"
class="action-btn"
source="trash"
:class="{ 'is-inactive': clipboard.length < 1 }"
:action="$t('actions.delete')"
/>
<ToolbarButton @click.native="downloadItem" class="action-btn" source="download" :action="$t('actions.download')" />
</template>
</MobileMultiSelectToolbar>
<ContextMenu>
<template v-slot:empty-select v-if="$checkPermission('editor')">
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$createFolder" :title="$t('context_menu.create_folder')" icon="create-folder" />
</OptionGroup>
</template>
<ContextMenu>
<template v-slot:empty-select v-if="$checkPermission('editor')">
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$createFolder" :title="$t('context_menu.create_folder')" icon="create-folder" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="$checkPermission('editor')">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="$checkPermission('editor')">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="$checkPermission('editor')">
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="$checkPermission('editor')">
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<FileActionsMobile>
<template v-if="$checkPermission('editor')">
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('create-list')" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</template>
<template v-if="$checkPermission('visitor')">
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight')}}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</template>
</FileActionsMobile>
<FileActionsMobile>
<template v-if="$checkPermission('editor')">
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('create-list')" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</template>
<template v-if="$checkPermission('visitor')">
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</template>
</FileActionsMobile>
<EmptyFilePage>
<template v-if="$checkPermission('editor')">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</template>
<template v-if="$checkPermission('visitor')">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
</template>
</EmptyFilePage>
<EmptyFilePage>
<template v-if="$checkPermission('editor')">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</template>
<template v-if="$checkPermission('visitor')">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
</template>
</EmptyFilePage>
<FileBrowser />
</div>
<FileBrowser />
</div>
</template>
<script>
import EmptyFilePage from "../../components/FilesView/EmptyFilePage";
import FileActionsMobile from "../../components/FilesView/FileActionsMobile";
import MobileMultiSelectToolbar from "../../components/FilesView/MobileMultiSelectToolbar";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileContextMenu from "../../components/FilesView/MobileContextMenu";
import MobileCreateMenu from "../../components/FilesView/MobileCreateMenu";
import ToolbarButton from "../../components/FilesView/ToolbarButton";
import OptionUpload from "../../components/FilesView/OptionUpload";
import ButtonUpload from "../../components/FilesView/ButtonUpload";
import FileBrowser from "../../components/FilesView/FileBrowser";
import ContextMenu from "../../components/FilesView/ContextMenu";
import OptionGroup from "../../components/FilesView/OptionGroup";
import Option from "../../components/FilesView/Option";
import {events} from "../../bus"
import { mapGetters } from 'vuex'
import EmptyFilePage from '../../components/FilesView/EmptyFilePage'
import FileActionsMobile from '../../components/FilesView/FileActionsMobile'
import MobileMultiSelectToolbar from '../../components/FilesView/MobileMultiSelectToolbar'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileContextMenu from '../../components/FilesView/MobileContextMenu'
import MobileCreateMenu from '../../components/FilesView/MobileCreateMenu'
import ToolbarButton from '../../components/FilesView/ToolbarButton'
import OptionUpload from '../../components/FilesView/OptionUpload'
import ButtonUpload from '../../components/FilesView/ButtonUpload'
import FileBrowser from '../../components/FilesView/FileBrowser'
import ContextMenu from '../../components/FilesView/ContextMenu'
import OptionGroup from '../../components/FilesView/OptionGroup'
import Option from '../../components/FilesView/Option'
import { events } from '../../bus'
import { mapGetters } from 'vuex'
export default {
name: 'Files',
components: {
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
OptionUpload,
ButtonUpload,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters([
'clipboard',
])
},
data() {
return {
item: undefined,
}
},
methods: {
createFolder() {
events.$emit('popup:open', {name: 'create-folder'})
},
},
created() {
this.$store.dispatch('getSharedFolder', this.$route.params.id)
export default {
name: 'Files',
components: {
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
OptionUpload,
ButtonUpload,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters(['clipboard']),
},
data() {
return {
item: undefined,
}
},
methods: {
createFolder() {
events.$emit('popup:open', { name: 'create-folder' })
},
},
created() {
this.$store.dispatch('getSharedFolder', this.$route.params.id)
events.$on('context-menu:show', (event, item) => this.item = item)
events.$on('mobile-context-menu:show', item => this.item = item)
}
}
events.$on('context-menu:show', (event, item) => (this.item = item))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
},
}
</script>

View File

@@ -1,131 +1,138 @@
<template>
<div>
<ContextMenu>
<template v-slot:single-select v-if="item">
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<div>
<ContextMenu>
<template v-slot:single-select v-if="item">
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option
@click.native="$shareFileOrFolder(item)"
:title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')"
icon="share"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:multiple-select v-if="item">
<OptionGroup>
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<template v-slot:multiple-select v-if="item">
<OptionGroup>
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<MobileContextMenu>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
<Option @click.native="$shareFileOrFolder(item)" :title="item && item.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
<MobileContextMenu>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
<Option @click.native="$shareFileOrFolder(item)" :title="item && item.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButtonUpload>
{{ $t('context_menu.upload') }}
</MobileActionButtonUpload>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButtonUpload>
{{ $t('context_menu.upload') }}
</MobileActionButtonUpload>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<EmptyFilePage>
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</EmptyFilePage>
<EmptyFilePage>
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</EmptyFilePage>
<FileBrowser />
<FileBrowser />
<MobileMultiSelectToolbar>
<ToolbarButton @click.native="$deleteFileOrFolder(clipboard)" class="action-btn" source="trash" :class="{'is-inactive' : clipboard.length < 1}" :action="$t('actions.delete')" />
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
</MobileMultiSelectToolbar>
</div>
<MobileMultiSelectToolbar>
<ToolbarButton
@click.native="$deleteFileOrFolder(clipboard)"
class="action-btn"
source="trash"
:class="{ 'is-inactive': clipboard.length < 1 }"
:action="$t('actions.delete')"
/>
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
</MobileMultiSelectToolbar>
</div>
</template>
<script>
import EmptyFilePage from "../../components/FilesView/EmptyFilePage";
import FileActionsMobile from "../../components/FilesView/FileActionsMobile";
import MobileActionButtonUpload from "../../components/FilesView/MobileActionButtonUpload";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileMultiSelectToolbar from "../../components/FilesView/MobileMultiSelectToolbar";
import MobileContextMenu from "../../components/FilesView/MobileContextMenu";
import ToolbarButton from "../../components/FilesView/ToolbarButton";
import ButtonUpload from "../../components/FilesView/ButtonUpload";
import FileBrowser from "../../components/FilesView/FileBrowser";
import ContextMenu from "../../components/FilesView/ContextMenu";
import OptionGroup from "../../components/FilesView/OptionGroup";
import Option from "../../components/FilesView/Option";
import { mapGetters } from 'vuex'
import {events} from "../../bus";
import EmptyFilePage from '../../components/FilesView/EmptyFilePage'
import FileActionsMobile from '../../components/FilesView/FileActionsMobile'
import MobileActionButtonUpload from '../../components/FilesView/MobileActionButtonUpload'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileMultiSelectToolbar from '../../components/FilesView/MobileMultiSelectToolbar'
import MobileContextMenu from '../../components/FilesView/MobileContextMenu'
import ToolbarButton from '../../components/FilesView/ToolbarButton'
import ButtonUpload from '../../components/FilesView/ButtonUpload'
import FileBrowser from '../../components/FilesView/FileBrowser'
import ContextMenu from '../../components/FilesView/ContextMenu'
import OptionGroup from '../../components/FilesView/OptionGroup'
import Option from '../../components/FilesView/Option'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'RecentUploads',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
ToolbarButton,
ButtonUpload,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters([
'clipboard',
'user',
]),
},
data() {
return {
item: undefined,
}
},
created() {
this.$store.dispatch('getRecentUploads')
export default {
name: 'RecentUploads',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
ToolbarButton,
ButtonUpload,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters(['clipboard', 'user']),
},
data() {
return {
item: undefined,
}
},
created() {
this.$store.dispatch('getRecentUploads')
events.$on('context-menu:show', (event, item) => this.item = item)
events.$on('mobile-context-menu:show', item => this.item = item)
}
}
events.$on('context-menu:show', (event, item) => (this.item = item))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
},
}
</script>

View File

@@ -1,240 +1,244 @@
<template>
<div>
<MobileContextMenu>
<OptionGroup v-if="canEdit && item">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<div>
<MobileContextMenu>
<OptionGroup v-if="canEdit && item">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
</MobileContextMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" :is-hover-disabled="true" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" :is-hover-disabled="true" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileTeamContextMenu>
<OptionGroup>
<Option @click.native="$detachMeFromTeamFolder(teamFolder)" :title="$t('Leave the Team Folder')" icon="user-minus" />
</OptionGroup>
</MobileTeamContextMenu>
<MobileTeamContextMenu>
<OptionGroup>
<Option @click.native="$detachMeFromTeamFolder(teamFolder)" :title="$t('Leave the Team Folder')" icon="user-minus" />
</OptionGroup>
</MobileTeamContextMenu>
<MobileMultiSelectToolbar>
<ToolbarButton v-if="canEdit" @click.native="$moveFileOrFolder(clipboard)" class="action-btn" source="move" :action="$t('actions.move')" :class="{'is-inactive' : clipboard.length < 1}" />
<ToolbarButton v-if="canEdit" @click.native="$deleteFileOrFolder(clipboard)" class="action-btn" source="trash" :class="{'is-inactive' : clipboard.length < 1}" :action="$t('actions.delete')" />
<MobileMultiSelectToolbar>
<ToolbarButton
v-if="canEdit"
@click.native="$moveFileOrFolder(clipboard)"
class="action-btn"
source="move"
:action="$t('actions.move')"
:class="{ 'is-inactive': clipboard.length < 1 }"
/>
<ToolbarButton
v-if="canEdit"
@click.native="$deleteFileOrFolder(clipboard)"
class="action-btn"
source="trash"
:class="{ 'is-inactive': clipboard.length < 1 }"
:action="$t('actions.delete')"
/>
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
</MobileMultiSelectToolbar>
</MobileMultiSelectToolbar>
<ContextMenu>
<template v-slot:empty-select v-if="canEdit">
<OptionGroup v-if="! isTeamFolderHomepage">
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup v-if="! isTeamFolderHomepage">
<Option @click.stop.native="$createFolder" :title="$t('actions.create_folder')" icon="folder-plus" />
</OptionGroup>
</template>
<ContextMenu>
<template v-slot:empty-select v-if="canEdit">
<OptionGroup v-if="!isTeamFolderHomepage">
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
</OptionGroup>
<OptionGroup v-if="!isTeamFolderHomepage">
<Option @click.stop.native="$createFolder" :title="$t('actions.create_folder')" icon="folder-plus" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="canEdit">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="canEdit">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="canEdit">
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="canEdit">
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton v-if="canEdit" @click.native="$showMobileMenu('create-list')" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton v-if="canEdit" @click.native="$showMobileMenu('create-list')" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<EmptyFilePage>
<EmptyFilePage>
<!--Homepage-->
<template v-if="isTeamFolderHomepage">
<h1 class="title">
{{ $t('Nothing Shared With You') }}
</h1>
<p class="description">
{{ $t('All items that are shared with you will be visible here.') }}
</p>
</template>
<!--Homepage-->
<template v-if="isTeamFolderHomepage">
<h1 class="title">
{{ $t('Nothing Shared With You') }}
</h1>
<p class="description">
{{ $t('All items that are shared with you will be visible here.') }}
</p>
</template>
<!--Empty folder wit can-edit privileges -->
<template v-if="canEdit && !isTeamFolderHomepage">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</template>
<!--Empty folder wit can-edit privileges -->
<template v-if="canEdit && ! isTeamFolderHomepage">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</template>
<!--Empty folder wit can-view privileges -->
<template v-if="!canEdit && !isTeamFolderHomepage">
<h1 class="title">
{{ $t('There is Nothing Yet') }}
</h1>
</template>
</EmptyFilePage>
<!--Empty folder wit can-view privileges -->
<template v-if="! canEdit && ! isTeamFolderHomepage">
<h1 class="title">
{{ $t('There is Nothing Yet') }}
</h1>
</template>
</EmptyFilePage>
<FileBrowser />
</div>
<FileBrowser />
</div>
</template>
<script>
import MobileTeamContextMenu from "../../components/FilesView/MobileTeamContextMenu";
import EmptyFilePage from "../../components/FilesView/EmptyFilePage";
import FileActionsMobile from "../../components/FilesView/FileActionsMobile";
import MobileActionButtonUpload from "../../components/FilesView/MobileActionButtonUpload";
import MobileMultiSelectToolbar from "../../components/FilesView/MobileMultiSelectToolbar";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileContextMenu from "../../components/FilesView/MobileContextMenu";
import MobileCreateMenu from "../../components/FilesView/MobileCreateMenu";
import ButtonUpload from "../../components/FilesView/ButtonUpload";
import ToolbarButton from "../../components/FilesView/ToolbarButton";
import OptionUpload from "../../components/FilesView/OptionUpload";
import FileBrowser from "../../components/FilesView/FileBrowser";
import ContextMenu from "../../components/FilesView/ContextMenu";
import OptionGroup from "../../components/FilesView/OptionGroup";
import ButtonBase from "../../components/FilesView/ButtonBase";
import Option from "../../components/FilesView/Option";
import { mapGetters } from 'vuex'
import {events} from "../../bus";
import MobileTeamContextMenu from '../../components/FilesView/MobileTeamContextMenu'
import EmptyFilePage from '../../components/FilesView/EmptyFilePage'
import FileActionsMobile from '../../components/FilesView/FileActionsMobile'
import MobileActionButtonUpload from '../../components/FilesView/MobileActionButtonUpload'
import MobileMultiSelectToolbar from '../../components/FilesView/MobileMultiSelectToolbar'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileContextMenu from '../../components/FilesView/MobileContextMenu'
import MobileCreateMenu from '../../components/FilesView/MobileCreateMenu'
import ButtonUpload from '../../components/FilesView/ButtonUpload'
import ToolbarButton from '../../components/FilesView/ToolbarButton'
import OptionUpload from '../../components/FilesView/OptionUpload'
import FileBrowser from '../../components/FilesView/FileBrowser'
import ContextMenu from '../../components/FilesView/ContextMenu'
import OptionGroup from '../../components/FilesView/OptionGroup'
import ButtonBase from '../../components/FilesView/ButtonBase'
import Option from '../../components/FilesView/Option'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'SharedWithMe',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileTeamContextMenu,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
ButtonUpload,
OptionUpload,
OptionGroup,
FileBrowser,
ContextMenu,
ButtonBase,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters([
'currentTeamFolder',
'clipboard',
'config',
'user',
]),
teamFolder() {
return this.currentTeamFolder
? this.currentTeamFolder
: this.clipboard[0]
},
canEdit() {
if (this.currentTeamFolder && this.user) {
let member = this.currentTeamFolder.data.relationships.members.data.find(member => member.data.id === this.user.data.id)
export default {
name: 'SharedWithMe',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileTeamContextMenu,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
ButtonUpload,
OptionUpload,
OptionGroup,
FileBrowser,
ContextMenu,
ButtonBase,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters(['currentTeamFolder', 'clipboard', 'config', 'user']),
teamFolder() {
return this.currentTeamFolder ? this.currentTeamFolder : this.clipboard[0]
},
canEdit() {
if (this.currentTeamFolder && this.user) {
let member = this.currentTeamFolder.data.relationships.members.data.find((member) => member.data.id === this.user.data.id)
return member.data.attributes.permission === 'can-edit'
}
return member.data.attributes.permission === 'can-edit'
}
return false
},
isTeamFolderHomepage() {
return this.$isThisRoute(this.$route, ['SharedWithMe'])
&& ! this.$route.params.id
},
isFolder() {
return this.item && this.item.data.type === 'folder'
},
hasFile() {
return this.clipboard.find(item => item.type !== 'folder')
},
},
data() {
return {
item: undefined,
}
},
methods: {
createFolder() {
events.$emit('popup:open', {name: 'create-folder'})
},
},
mounted() {
this.$store.commit('SET_CURRENT_TEAM_FOLDER', undefined)
this.$store.dispatch('getSharedWithMeFolder', this.$route.params.id)
return false
},
isTeamFolderHomepage() {
return this.$isThisRoute(this.$route, ['SharedWithMe']) && !this.$route.params.id
},
isFolder() {
return this.item && this.item.data.type === 'folder'
},
hasFile() {
return this.clipboard.find((item) => item.type !== 'folder')
},
},
data() {
return {
item: undefined,
}
},
methods: {
createFolder() {
events.$emit('popup:open', { name: 'create-folder' })
},
},
mounted() {
this.$store.commit('SET_CURRENT_TEAM_FOLDER', undefined)
this.$store.dispatch('getSharedWithMeFolder', this.$route.params.id)
events.$on('context-menu:show', (event, item) => this.item = item)
events.$on('mobile-context-menu:show', item => this.item = item)
events.$on('context-menu:current-folder', folder => this.item = folder)
events.$on('context-menu:show', (event, item) => (this.item = item))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
events.$on('context-menu:current-folder', (folder) => (this.item = folder))
events.$on('action:confirmed', data => {
events.$on('action:confirmed', (data) => {
// Leave team folder after popup confirmation
if (data.operation === 'leave-team-folder')
axios
.delete(`/api/teams/folders/${data.id}/leave`)
.then(() => {
if (this.$route.params.id) {
this.$router.push({ name: 'SharedWithMe' })
} else {
this.$store.dispatch('getSharedWithMeFolder', undefined)
}
// Leave team folder after popup confirmation
if (data.operation === 'leave-team-folder')
axios.delete(`/api/teams/folders/${data.id}/leave`)
.then(() => {
if (this.$route.params.id) {
this.$router.push({name: 'SharedWithMe'})
} else {
this.$store.dispatch('getSharedWithMeFolder', undefined)
}
events.$emit('toaster', {
type: 'success',
message: this.$t('You have successfully left the team folder'),
})
})
.catch(() => this.$isSomethingWrong())
})
},
destroyed() {
events.$off('action:confirmed')
},
}
events.$emit('toaster', {
type: 'success',
message: this.$t('You have successfully left the team folder'),
})
})
.catch(() => this.$isSomethingWrong())
})
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -1,265 +1,285 @@
<template>
<div>
<MobileContextMenu>
<OptionGroup v-if="item && isFolder">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
<div>
<MobileContextMenu>
<OptionGroup v-if="item && isFolder">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="$updateTeamFolder(item)" v-if="isFolder" :title="$t('Edit Team Members')" icon="users" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="$updateTeamFolder(item)" v-if="isFolder" :title="$t('Edit Team Members')" icon="users" />
</OptionGroup>
<OptionGroup v-if="item">
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
</MobileContextMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" :is-hover-disabled="true" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="$createTeamFolder" :title="$t('Create Team Folder')" icon="users" :is-hover-disabled="true" />
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileCreateMenu>
<OptionGroup>
<OptionUpload :title="$t('actions.upload')" type="file" :is-hover-disabled="true" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" :is-hover-disabled="true" />
</OptionGroup>
<OptionGroup>
<Option @click.stop.native="$createTeamFolder" :title="$t('Create Team Folder')" icon="users" :is-hover-disabled="true" />
<Option @click.stop.native="createFolder" :title="$t('actions.create_folder')" icon="folder-plus" :is-hover-disabled="true" />
</OptionGroup>
</MobileCreateMenu>
<MobileTeamContextMenu>
<OptionGroup>
<Option @click.native="$updateTeamFolder(teamFolder)" :title="$t('Edit Members')" icon="rename" />
<Option @click.native="$dissolveTeamFolder(teamFolder)" :title="$t('Dissolve Team')" icon="trash" />
</OptionGroup>
</MobileTeamContextMenu>
<MobileTeamContextMenu>
<OptionGroup>
<Option @click.native="$updateTeamFolder(teamFolder)" :title="$t('Edit Members')" icon="rename" />
<Option @click.native="$dissolveTeamFolder(teamFolder)" :title="$t('Dissolve Team')" icon="trash" />
</OptionGroup>
</MobileTeamContextMenu>
<MobileMultiSelectToolbar>
<ToolbarButton @click.native="$moveFileOrFolder(clipboard)" class="action-btn" source="move" :action="$t('actions.move')" :class="{'is-inactive' : clipboard.length < 1}" />
<ToolbarButton @click.native="$deleteFileOrFolder(clipboard)" class="action-btn" source="trash" :class="{'is-inactive' : clipboard.length < 1}" :action="$t('actions.delete')" />
<MobileMultiSelectToolbar>
<ToolbarButton
@click.native="$moveFileOrFolder(clipboard)"
class="action-btn"
source="move"
:action="$t('actions.move')"
:class="{ 'is-inactive': clipboard.length < 1 }"
/>
<ToolbarButton
@click.native="$deleteFileOrFolder(clipboard)"
class="action-btn"
source="trash"
:class="{ 'is-inactive': clipboard.length < 1 }"
:action="$t('actions.delete')"
/>
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
</MobileMultiSelectToolbar>
</MobileMultiSelectToolbar>
<ContextMenu>
<template v-slot:empty-select>
<OptionGroup v-if="! isTeamFolderHomepage">
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
<Option @click.stop.native="$createFolder" :title="$t('actions.create_folder')" icon="folder-plus" />
</OptionGroup>
<OptionGroup v-if="isTeamFolderHomepage">
<Option @click.native="$createTeamFolder" :title="$t('Create Team Folder')" icon="users" />
</OptionGroup>
</template>
<ContextMenu>
<template v-slot:empty-select>
<OptionGroup v-if="!isTeamFolderHomepage">
<OptionUpload :title="$t('actions.upload')" type="file" />
<OptionUpload :title="$t('actions.upload_folder')" type="folder" />
<Option @click.stop.native="$createFolder" :title="$t('actions.create_folder')" icon="folder-plus" />
</OptionGroup>
<OptionGroup v-if="isTeamFolderHomepage">
<Option @click.native="$createTeamFolder" :title="$t('Create Team Folder')" icon="users" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="isFolder">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$shareFileOrFolder(item)" :title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')" icon="share" />
<Option @click.native="$updateTeamFolder(item)" v-if="isFolder" :title="$t('Edit Team Members')" icon="users" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup v-if="isFolder">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$renameFileOrFolder(item)" :title="$t('context_menu.rename')" icon="rename" />
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option
@click.native="$shareFileOrFolder(item)"
:title="item.data.relationships.shared ? $t('context_menu.share_edit') : $t('context_menu.share')"
icon="share"
/>
<Option @click.native="$updateTeamFolder(item)" v-if="isFolder" :title="$t('Edit Team Members')" icon="users" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="!hasFile">
<Option @click.native="addToFavourites" :title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')" icon="favourites" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<template v-slot:multiple-select v-if="item">
<OptionGroup v-if="!hasFile">
<Option
@click.native="addToFavourites"
:title="isInFavourites ? $t('context_menu.remove_from_favourites') : $t('context_menu.add_to_favourites')"
icon="favourites"
/>
</OptionGroup>
<OptionGroup>
<Option @click.native="$moveFileOrFolder(item)" :title="$t('context_menu.move')" icon="move-item" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('create-list')" v-if="$checkPermission(['master', 'editor'])" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('create-list')" v-if="$checkPermission(['master', 'editor'])" icon="cloud-plus">
{{ $t('mobile.create') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<EmptyFilePage>
<template v-if="isTeamFolderHomepage">
<h1 class="title">
{{ $t('Create your Team Folder') }}
</h1>
<p class="description">
{{ $t('Collaborate on your files with your team easily by creating new team folder.') }}
</p>
<ButtonBase @click.native="$createTeamFolder" button-style="theme" class="m-center">
{{ $t('Create Team Folder') }}
</ButtonBase>
</template>
<EmptyFilePage>
<template v-if="isTeamFolderHomepage">
<h1 class="title">
{{ $t('Create your Team Folder') }}
</h1>
<p class="description">
{{ $t('Collaborate on your files with your team easily by creating new team folder.') }}
</p>
<ButtonBase @click.native="$createTeamFolder" button-style="theme" class="m-center">
{{ $t('Create Team Folder') }}
</ButtonBase>
</template>
<template v-if="! isTeamFolderHomepage">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</template>
</EmptyFilePage>
<template v-if="!isTeamFolderHomepage">
<h1 class="title">
{{ $t('empty_page.title') }}
</h1>
<p class="description">
{{ $t('empty_page.description') }}
</p>
<ButtonUpload button-style="theme">
{{ $t('empty_page.call_to_action') }}
</ButtonUpload>
</template>
</EmptyFilePage>
<FileBrowser />
</div>
<FileBrowser />
</div>
</template>
<script>
import MobileTeamContextMenu from "../../components/FilesView/MobileTeamContextMenu";
import EmptyFilePage from "../../components/FilesView/EmptyFilePage";
import FileActionsMobile from "../../components/FilesView/FileActionsMobile";
import MobileActionButtonUpload from "../../components/FilesView/MobileActionButtonUpload";
import MobileMultiSelectToolbar from "../../components/FilesView/MobileMultiSelectToolbar";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileContextMenu from "../../components/FilesView/MobileContextMenu";
import MobileCreateMenu from "../../components/FilesView/MobileCreateMenu";
import ButtonUpload from "../../components/FilesView/ButtonUpload";
import ToolbarButton from "../../components/FilesView/ToolbarButton";
import OptionUpload from "../../components/FilesView/OptionUpload";
import FileBrowser from "../../components/FilesView/FileBrowser";
import ContextMenu from "../../components/FilesView/ContextMenu";
import OptionGroup from "../../components/FilesView/OptionGroup";
import ButtonBase from "../../components/FilesView/ButtonBase";
import Option from "../../components/FilesView/Option";
import { mapGetters } from 'vuex'
import {events} from "../../bus";
import MobileTeamContextMenu from '../../components/FilesView/MobileTeamContextMenu'
import EmptyFilePage from '../../components/FilesView/EmptyFilePage'
import FileActionsMobile from '../../components/FilesView/FileActionsMobile'
import MobileActionButtonUpload from '../../components/FilesView/MobileActionButtonUpload'
import MobileMultiSelectToolbar from '../../components/FilesView/MobileMultiSelectToolbar'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileContextMenu from '../../components/FilesView/MobileContextMenu'
import MobileCreateMenu from '../../components/FilesView/MobileCreateMenu'
import ButtonUpload from '../../components/FilesView/ButtonUpload'
import ToolbarButton from '../../components/FilesView/ToolbarButton'
import OptionUpload from '../../components/FilesView/OptionUpload'
import FileBrowser from '../../components/FilesView/FileBrowser'
import ContextMenu from '../../components/FilesView/ContextMenu'
import OptionGroup from '../../components/FilesView/OptionGroup'
import ButtonBase from '../../components/FilesView/ButtonBase'
import Option from '../../components/FilesView/Option'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'TeamFolders',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileTeamContextMenu,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
ButtonUpload,
OptionUpload,
OptionGroup,
FileBrowser,
ContextMenu,
ButtonBase,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters([
'currentTeamFolder',
'clipboard',
'config',
'user',
]),
teamFolder() {
return this.currentTeamFolder
? this.currentTeamFolder
: this.clipboard[0]
},
isTeamFolderHomepage() {
return this.$isThisRoute(this.$route, ['TeamFolders'])
&& ! this.$route.params.id
},
isFolder() {
return this.item && this.item.data.type === 'folder'
},
isInFavourites() {
return this.favourites.find((el) => el.id === this.item.id)
},
hasFile() {
return this.clipboard.find(item => item.type !== 'folder')
},
favourites() {
return this.user.data.relationships.favourites.data
},
},
data() {
return {
item: undefined,
}
},
methods: {
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.favourites && !this.favourites.find(el => el.id === this.item.data.id)) {
// Add to favourite folder that is not selected
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', this.item)
}
export default {
name: 'TeamFolders',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileTeamContextMenu,
MobileActionButton,
MobileContextMenu,
MobileCreateMenu,
ToolbarButton,
ButtonUpload,
OptionUpload,
OptionGroup,
FileBrowser,
ContextMenu,
ButtonBase,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters(['currentTeamFolder', 'clipboard', 'config', 'user']),
teamFolder() {
return this.currentTeamFolder ? this.currentTeamFolder : this.clipboard[0]
},
isTeamFolderHomepage() {
return this.$isThisRoute(this.$route, ['TeamFolders']) && !this.$route.params.id
},
isFolder() {
return this.item && this.item.data.type === 'folder'
},
isInFavourites() {
return this.favourites.find((el) => el.id === this.item.id)
},
hasFile() {
return this.clipboard.find((item) => item.type !== 'folder')
},
favourites() {
return this.user.data.relationships.favourites.data
},
},
data() {
return {
item: undefined,
}
},
methods: {
addToFavourites() {
// Check if folder is in favourites and then add/remove from favourites
if (this.favourites && !this.favourites.find((el) => el.id === this.item.data.id)) {
// Add to favourite folder that is not selected
if (!this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', this.item)
}
// Add to favourites all selected folders
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', null)
}
} else {
this.$store.dispatch('removeFromFavourites', this.item)
}
},
createFolder() {
events.$emit('popup:open', {name: 'create-folder'})
},
},
mounted() {
this.$store.dispatch('getTeamFolder', this.$route.params.id)
// Add to favourites all selected folders
if (this.clipboard.includes(this.item)) {
this.$store.dispatch('addToFavourites', null)
}
} else {
this.$store.dispatch('removeFromFavourites', this.item)
}
},
createFolder() {
events.$emit('popup:open', { name: 'create-folder' })
},
},
mounted() {
this.$store.dispatch('getTeamFolder', this.$route.params.id)
events.$on('context-menu:show', (event, item) => this.item = item)
events.$on('mobile-context-menu:show', item => this.item = item)
events.$on('context-menu:current-folder', folder => this.item = folder)
events.$on('context-menu:show', (event, item) => (this.item = item))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
events.$on('context-menu:current-folder', (folder) => (this.item = folder))
events.$on('action:confirmed', data => {
events.$on('action:confirmed', (data) => {
if (data.operation === 'dissolve-team-folder')
axios
.delete(`/api/teams/folders/${data.id}`)
.then(() => {
if (this.$route.params.id) {
this.$router.push({ name: 'TeamFolders' })
} else {
this.$store.commit('REMOVE_ITEM', data.id)
}
if (data.operation === 'dissolve-team-folder')
axios.delete(`/api/teams/folders/${data.id}`)
.then(() => {
if (this.$route.params.id) {
this.$router.push({name: 'TeamFolders'})
} else {
this.$store.commit('REMOVE_ITEM', data.id)
}
events.$emit('toaster', {
type: 'success',
message: this.$t('Your Team Folder was moved into your files.'),
})
})
.catch(() => this.$isSomethingWrong())
})
},
destroyed() {
events.$off('action:confirmed')
},
}
events.$emit('toaster', {
type: 'success',
message: this.$t('Your Team Folder was moved into your files.'),
})
})
.catch(() => this.$isSomethingWrong())
})
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -1,123 +1,127 @@
<template>
<div>
<ContextMenu>
<template v-slot:empty-select>
<OptionGroup>
<Option @click.native="$emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash" />
</OptionGroup>
</template>
<div>
<ContextMenu>
<template v-slot:empty-select>
<OptionGroup>
<Option @click.native="$emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup>
<Option @click.native="$restoreFileOrFolder(item)" v-if="item" :title="$t('context_menu.restore')" icon="restore" />
<Option @click.native="$deleteFileOrFolder(item)" v-if="item" :title="$t('context_menu.delete')" icon="trash" />
<Option @click.native="$emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:single-select v-if="item">
<OptionGroup>
<Option @click.native="$restoreFileOrFolder(item)" v-if="item" :title="$t('context_menu.restore')" icon="restore" />
<Option @click.native="$deleteFileOrFolder(item)" v-if="item" :title="$t('context_menu.delete')" icon="trash" />
<Option @click.native="$emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$openInDetailPanel(item)" :title="$t('context_menu.detail')" icon="detail" />
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
<template v-slot:multiple-select v-if="item">
<OptionGroup>
<Option @click.native="$restoreFileOrFolder(item)" v-if="item" :title="$t('context_menu.restore')" icon="restore" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
<Option @click.native="$emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<template v-slot:multiple-select v-if="item">
<OptionGroup>
<Option @click.native="$restoreFileOrFolder(item)" v-if="item" :title="$t('context_menu.restore')" icon="restore" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
<Option @click.native="$emptyTrash" :title="$t('context_menu.empty_trash')" icon="empty-trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection()" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</template>
</ContextMenu>
<MobileContextMenu>
<OptionGroup v-if="item">
<Option @click.native="$restoreFileOrFolder(item)" v-if="item" :title="$t('context_menu.restore')" icon="restore" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
<MobileContextMenu>
<OptionGroup v-if="item">
<Option @click.native="$restoreFileOrFolder(item)" v-if="item" :title="$t('context_menu.restore')" icon="restore" />
<Option @click.native="$deleteFileOrFolder(item)" :title="$t('context_menu.delete')" icon="trash" />
</OptionGroup>
<OptionGroup>
<Option @click.native="$downloadSelection(item)" :title="$t('context_menu.download')" icon="download" />
</OptionGroup>
</MobileContextMenu>
</MobileContextMenu>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight')}}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$emptyTrash" icon="trash">
{{ $t('context_menu.empty_trash') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<FileActionsMobile>
<MobileActionButton @click.native="$openSpotlight()" icon="search">
{{ $t('Spotlight') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-filter')" :icon="$getCurrentSectionIcon()">
{{ $getCurrentSectionName() }}
</MobileActionButton>
<MobileActionButton @click.native="$emptyTrash" icon="trash">
{{ $t('context_menu.empty_trash') }}
</MobileActionButton>
<MobileActionButton @click.native="$enableMultiSelectMode" icon="check-square">
{{ $t('context_menu.select') }}
</MobileActionButton>
<MobileActionButton @click.native="$showMobileMenu('file-sorting')" icon="preview-sorting">
{{ $t('preview_sorting.preview_sorting_button') }}
</MobileActionButton>
</FileActionsMobile>
<EmptyFilePage>
<h1 class="title">{{ $t('Your Trash is Empty') }}</h1>
</EmptyFilePage>
<EmptyFilePage>
<h1 class="title">{{ $t('Your Trash is Empty') }}</h1>
</EmptyFilePage>
<FileBrowser />
<FileBrowser />
<MobileMultiSelectToolbar>
<ToolbarButton @click.native="$deleteFileOrFolder(clipboard)" class="action-btn" source="trash" :class="{'is-inactive' : clipboard.length < 1}" :action="$t('actions.delete')" />
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
</MobileMultiSelectToolbar>
</div>
<MobileMultiSelectToolbar>
<ToolbarButton
@click.native="$deleteFileOrFolder(clipboard)"
class="action-btn"
source="trash"
:class="{ 'is-inactive': clipboard.length < 1 }"
:action="$t('actions.delete')"
/>
<ToolbarButton @click.native="$downloadSelection(item)" class="action-btn" source="download" :action="$t('actions.download')" />
</MobileMultiSelectToolbar>
</div>
</template>
<script>
import EmptyFilePage from "../../components/FilesView/EmptyFilePage";
import FileActionsMobile from "../../components/FilesView/FileActionsMobile";
import MobileActionButtonUpload from "../../components/FilesView/MobileActionButtonUpload";
import MobileActionButton from "../../components/FilesView/MobileActionButton";
import MobileMultiSelectToolbar from "../../components/FilesView/MobileMultiSelectToolbar";
import MobileContextMenu from "../../components/FilesView/MobileContextMenu";
import ToolbarButton from "../../components/FilesView/ToolbarButton";
import FileBrowser from "../../components/FilesView/FileBrowser";
import ContextMenu from "../../components/FilesView/ContextMenu";
import OptionGroup from "../../components/FilesView/OptionGroup";
import Option from "../../components/FilesView/Option";
import { mapGetters } from 'vuex'
import {events} from "../../bus";
import EmptyFilePage from '../../components/FilesView/EmptyFilePage'
import FileActionsMobile from '../../components/FilesView/FileActionsMobile'
import MobileActionButtonUpload from '../../components/FilesView/MobileActionButtonUpload'
import MobileActionButton from '../../components/FilesView/MobileActionButton'
import MobileMultiSelectToolbar from '../../components/FilesView/MobileMultiSelectToolbar'
import MobileContextMenu from '../../components/FilesView/MobileContextMenu'
import ToolbarButton from '../../components/FilesView/ToolbarButton'
import FileBrowser from '../../components/FilesView/FileBrowser'
import ContextMenu from '../../components/FilesView/ContextMenu'
import OptionGroup from '../../components/FilesView/OptionGroup'
import Option from '../../components/FilesView/Option'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
export default {
name: 'Trash',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
ToolbarButton,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters([
'clipboard',
]),
},
data() {
return {
item: undefined,
}
},
created() {
this.$store.dispatch('getTrash', this.$route.params.id)
export default {
name: 'Trash',
components: {
MobileActionButtonUpload,
MobileMultiSelectToolbar,
MobileActionButton,
MobileContextMenu,
ToolbarButton,
OptionGroup,
FileBrowser,
ContextMenu,
Option,
FileActionsMobile,
EmptyFilePage,
},
computed: {
...mapGetters(['clipboard']),
},
data() {
return {
item: undefined,
}
},
created() {
this.$store.dispatch('getTrash', this.$route.params.id)
events.$on('context-menu:show', (event, item) => this.item = item)
events.$on('mobile-context-menu:show', item => this.item = item)
}
}
events.$on('context-menu:show', (event, item) => (this.item = item))
events.$on('mobile-context-menu:show', (item) => (this.item = item))
},
}
</script>

View File

@@ -1,40 +1,37 @@
<template>
<div class="landing-page">
<!--Navigation-->
<Navigation class="page-wrapper small"/>
<Navigation class="page-wrapper small" />
<!--Page content-->
<div class="page-wrapper small">
<!--Headline-->
<PageTitle
class="headline"
:title="$t('page_contact_us.title')"
:description="$t('page_contact_us.description')"
></PageTitle>
<ValidationObserver v-if="! isSuccess" @submit.prevent="contactForm" ref="contactForm" v-slot="{ invalid }" tag="form"
class="form block-form">
<PageTitle class="headline" :title="$t('page_contact_us.title')" :description="$t('page_contact_us.description')"></PageTitle>
<ValidationObserver v-if="!isSuccess" @submit.prevent="contactForm" ref="contactForm" v-slot="{ invalid }" tag="form" class="form block-form">
<div class="block-wrapper">
<label>{{ $t('page_contact_us.form.email') }}:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="E-Mail" rules="required"
v-slot="{ errors }">
<input v-model="contact.email" :placeholder="$t('page_contact_us.form.email_plac')" type="email"
class="focus-border-theme"
:class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="E-Mail" rules="required" v-slot="{ errors }">
<input
v-model="contact.email"
:placeholder="$t('page_contact_us.form.email_plac')"
type="email"
class="focus-border-theme"
:class="{ 'border-red': errors[0] }"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>{{ $t('page_contact_us.form.message') }}:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Message" rules="required"
v-slot="{ errors }">
<textarea v-model="contact.message" :placeholder="$t('page_contact_us.form.message_plac')" rows="6"
class="focus-border-theme"
:class="{'border-red': errors[0]}"
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Message" rules="required" v-slot="{ errors }">
<textarea
v-model="contact.message"
:placeholder="$t('page_contact_us.form.message_plac')"
rows="6"
class="focus-border-theme"
:class="{ 'border-red': errors[0] }"
></textarea>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
@@ -45,7 +42,7 @@
</InfoBox>
<div>
<AuthButton class="submit-button" icon="chevron-right" :text="$t('page_contact_us.form.submit_button')" :loading="isLoading" :disabled="isLoading"/>
<AuthButton class="submit-button" icon="chevron-right" :text="$t('page_contact_us.form.submit_button')" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
<InfoBox v-if="isSuccess">
@@ -54,119 +51,116 @@
</div>
<!--Footer-->
<PageFooter/>
<PageFooter />
</div>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import PageTitle from "../../components/Index/Components/PageTitle";
import PageFooter from '../../components/Index/IndexPageFooter'
import Navigation from '../../components/Index/IndexNavigation'
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthButton from "../../components/Auth/AuthButton";
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTitle from '../../components/Index/Components/PageTitle'
import PageFooter from '../../components/Index/IndexPageFooter'
import Navigation from '../../components/Index/IndexNavigation'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthButton from '../../components/Auth/AuthButton'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'ContactUs',
components: {
ValidationProvider,
ValidationObserver,
AuthButton,
PageFooter,
Navigation,
PageTitle,
required,
InfoBox,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
isSuccess: false,
isError: false,
contact: {
email: '',
message: '',
reCaptcha: null,
},
}
},
methods: {
async contactForm() {
// Validate fields
const isValid = await this.$refs.contactForm.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Get ReCaptcha token
if(config.allowedRecaptcha) {
this.register.reCaptcha = await this.$reCaptchaToken('register').then((response) => {
return response
})
}
// Send request to get user token
axios
.post('/api/contact', this.contact)
.then(() => {
this.isSuccess = true
})
.catch(() => {
this.isError = true
})
.finally(() => {
// End loading
this.isLoading = false
})
}
},
created() {
this.$scrollTop()
export default {
name: 'ContactUs',
components: {
ValidationProvider,
ValidationObserver,
AuthButton,
PageFooter,
Navigation,
PageTitle,
required,
InfoBox,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
isSuccess: false,
isError: false,
contact: {
email: '',
message: '',
reCaptcha: null,
},
}
}
},
methods: {
async contactForm() {
// Validate fields
const isValid = await this.$refs.contactForm.validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Get ReCaptcha token
if (config.allowedRecaptcha) {
this.register.reCaptcha = await this.$reCaptchaToken('register').then((response) => {
return response
})
}
// Send request to get user token
axios
.post('/api/contact', this.contact)
.then(() => {
this.isSuccess = true
})
.catch(() => {
this.isError = true
})
.finally(() => {
// End loading
this.isLoading = false
})
},
},
created() {
this.$scrollTop()
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/forms';
.form {
max-width: 100%;
.form {
max-width: 100%;
}
.headline {
padding-top: 70px;
padding-bottom: 50px;
}
.form.block-form {
.submit-button {
margin-top: 20px;
margin-left: 0;
margin-right: 0;
}
}
.dark {
}
@media only screen and (max-width: 960px) {
.headline {
padding-top: 70px;
padding-bottom: 50px;
}
.form.block-form {
.submit-button {
margin-top: 20px;
margin-left: 0;
margin-right: 0;
}
}
.dark {
}
@media only screen and (max-width: 960px) {
.headline {
padding-top: 50px;
padding-bottom: 30px;
}
padding-top: 50px;
padding-bottom: 30px;
}
}
</style>

View File

@@ -1,99 +1,91 @@
<template>
<div class="landing-page">
<!--Navigation-->
<Navigation class="page-wrapper small"/>
<Navigation class="page-wrapper small" />
<!--Page content-->
<div class="page-wrapper small">
<!--Headline-->
<PageTitle
class="headline"
:title="page.data.attributes.title"
></PageTitle>
<PageTitle class="headline" :title="page.data.attributes.title"></PageTitle>
<!--Content-->
<div class="page-content" v-html="page.data.attributes.content_formatted"></div>
</div>
<!--Footer-->
<PageFooter/>
<PageFooter />
</div>
</template>
<script>
import PageTitle from "../../components/Index/Components/PageTitle";
import PageFooter from '../../components/Index/IndexPageFooter'
import Navigation from '../../components/Index/IndexNavigation'
import {mapGetters} from 'vuex'
import axios from 'axios'
import PageTitle from '../../components/Index/Components/PageTitle'
import PageFooter from '../../components/Index/IndexPageFooter'
import Navigation from '../../components/Index/IndexNavigation'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'DynamicPage',
components: {
PageFooter,
Navigation,
PageTitle,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
page: undefined,
}
},
watch: {
$route(to, from) {
this.getPage()
}
},
methods: {
getPage() {
axios.get('/api/page/' + this.$route.params.slug)
.then(response => {
this.page = response.data
this.$scrollTop()
})
}
},
created() {
this.getPage()
export default {
name: 'DynamicPage',
components: {
PageFooter,
Navigation,
PageTitle,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
page: undefined,
}
}
},
watch: {
$route(to, from) {
this.getPage()
},
},
methods: {
getPage() {
axios.get('/api/page/' + this.$route.params.slug).then((response) => {
this.page = response.data
this.$scrollTop()
})
},
},
created() {
this.getPage()
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.headline {
padding-top: 70px;
padding-bottom: 50px;
}
.page-content {
/deep/ p {
@include font-size(20);
font-weight: 500;
line-height: 1.65;
padding-bottom: 30px;
}
}
.dark {
}
@media only screen and (max-width: 960px) {
.headline {
padding-top: 70px;
padding-bottom: 50px;
}
.page-content {
/deep/ p {
@include font-size(20);
font-weight: 500;
line-height: 1.65;
padding-bottom: 30px;
}
}
.dark {
}
@media only screen and (max-width: 960px) {
.headline {
padding-top: 50px;
padding-bottom: 30px;
}
padding-top: 50px;
padding-bottom: 30px;
}
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="landing-page">
<div v-if="! isLoading">
<div v-if="!isLoading">
<!--Navigation-->
<Navigation class="page-wrapper medium" />
@@ -14,7 +14,7 @@
<MainFeatures />
<!--Pricing Tables-->
<!-- <PricingTables v-if="config.isSaaS" />-->
<!-- <PricingTables v-if="config.isSaaS" />-->
<!--Get Started Call To Action-->
<GetStarted />
@@ -29,63 +29,62 @@
</template>
<script>
import HeroScreenshot from '../../components/Index/IndexHeroScreenshot'
import PricingTables from '../../components/Index/IndexPricingTables'
import MainFeatures from '../../components/Index/IndexMainFeatures'
import Navigation from '../../components/Index/IndexNavigation'
import PageHeader from '../../components/Index/IndexPageHeader'
import GetStarted from '../../components/Index/IndexGetStarted'
import PageFooter from '../../components/Index/IndexPageFooter'
import Spinner from "../../components/FilesView/Spinner";
import { mapGetters } from 'vuex'
import axios from 'axios'
import HeroScreenshot from '../../components/Index/IndexHeroScreenshot'
import PricingTables from '../../components/Index/IndexPricingTables'
import MainFeatures from '../../components/Index/IndexMainFeatures'
import Navigation from '../../components/Index/IndexNavigation'
import PageHeader from '../../components/Index/IndexPageHeader'
import GetStarted from '../../components/Index/IndexGetStarted'
import PageFooter from '../../components/Index/IndexPageFooter'
import Spinner from '../../components/FilesView/Spinner'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'Homepage',
components: {
HeroScreenshot,
PricingTables,
MainFeatures,
GetStarted,
Navigation,
PageHeader,
PageFooter,
Spinner,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: true,
}
},
mounted() {
if (! this.config.allowHomepage)
this.$router.push({name: 'SignIn'})
// Get page content
axios.get('/api/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.$store.commit('SET_INDEX_CONTENT', response.data)
})
.finally(() => {
this.isLoading = false
})
},
created() {
this.$scrollTop()
export default {
name: 'Homepage',
components: {
HeroScreenshot,
PricingTables,
MainFeatures,
GetStarted,
Navigation,
PageHeader,
PageFooter,
Spinner,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: true,
}
}
},
mounted() {
if (!this.config.allowHomepage) this.$router.push({ name: 'SignIn' })
// Get page content
axios
.get('/api/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.$store.commit('SET_INDEX_CONTENT', response.data)
})
.finally(() => {
this.isLoading = false
})
},
created() {
this.$scrollTop()
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
</style>

View File

@@ -1,36 +1,38 @@
<template>
<div class="flex text-center py-5 px-5 w-full justify-between items-center z-20 lg:hidden block">
<div class="z-20 block flex w-full items-center justify-between py-5 px-5 text-center lg:hidden">
<!--Location Title-->
<div
class="inline-block overflow-hidden text-ellipsis whitespace-nowrap align-middle text-sm font-bold transition-all duration-200 dark:text-gray-100"
style="max-width: 200px"
>
{{ locationName }}
</div>
<!--Location Title-->
<div class="text-sm dark:text-gray-100 align-middle font-bold overflow-hidden text-ellipsis inline-block whitespace-nowrap transition-all duration-200" style="max-width: 200px;">
{{ locationName }}
</div>
<!--More Actions-->
<div class="flex items-center">
<div @click="$openSpotlight()" class="px-2 mr-4 cursor-pointer">
<search-icon size="17" class="vue-feather dark:text-gray-100" />
</div>
<div @click="$showMobileMenu('user-navigation')" class="pr-1.5 cursor-pointer">
<menu-icon size="17" class="vue-feather dark:text-gray-100" />
</div>
</div>
</div>
<!--More Actions-->
<div class="flex items-center">
<div @click="$openSpotlight()" class="mr-4 cursor-pointer px-2">
<search-icon size="17" class="vue-feather dark:text-gray-100" />
</div>
<div @click="$showMobileMenu('user-navigation')" class="cursor-pointer pr-1.5">
<menu-icon size="17" class="vue-feather dark:text-gray-100" />
</div>
</div>
</div>
</template>
<script>
import {MenuIcon, ChevronLeftIcon, SearchIcon } from 'vue-feather-icons'
import { MenuIcon, ChevronLeftIcon, SearchIcon } from 'vue-feather-icons'
export default {
name: 'MobileNavigationToolbar',
components: {
ChevronLeftIcon,
SearchIcon,
MenuIcon,
},
computed: {
locationName() {
return this.$t(this.$route.meta.title)
}
}
}
export default {
name: 'MobileNavigationToolbar',
components: {
ChevronLeftIcon,
SearchIcon,
MenuIcon,
},
computed: {
locationName() {
return this.$t(this.$route.meta.title)
},
},
}
</script>

View File

@@ -1,12 +1,10 @@
<template>
<AuthContentWrapper ref="auth">
<AuthContent :visible="true">
<Headline
:title="$t('page_shared_404.title')"
:description="$t('page_shared_404.subtitle')"
/>
<span class="additional-link">{{ $t('page_registration.have_an_account') }}
<router-link :to="{name: 'SignIn'}">
<Headline :title="$t('page_shared_404.title')" :description="$t('page_shared_404.subtitle')" />
<span class="additional-link"
>{{ $t('page_registration.have_an_account') }}
<router-link :to="{ name: 'SignIn' }">
{{ $t('page_forgotten_password.password_remember_button') }}
</router-link>
</span>
@@ -15,22 +13,22 @@
</template>
<script>
import AuthContentWrapper from "../components/Auth/AuthContentWrapper";
import AuthContent from "../components/Auth/AuthContent";
import AuthButton from "../components/Auth/AuthButton";
import Headline from "./Auth/Headline"
import AuthContentWrapper from '../components/Auth/AuthContentWrapper'
import AuthContent from '../components/Auth/AuthContent'
import AuthButton from '../components/Auth/AuthButton'
import Headline from './Auth/Headline'
export default {
name: 'NotFound',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
export default {
name: 'NotFound',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
</script>
<style scoped lang="scss">
@import '../../sass/vuefilemanager/auth';
@import '../../sass/vuefilemanager/auth';
</style>

View File

@@ -1,16 +1,15 @@
<template>
<div class="lg:flex lg:h-screen lg:overflow-hidden">
<!--On Top of App Components-->
<FilePreview />
<Spotlight />
<Spotlight />
<!--Popups-->
<!--Popups-->
<ProcessingPopup />
<ConfirmPopup />
<CreateTeamFolderPopup />
<EditTeamFolderPopup />
<CreateTeamFolderPopup />
<EditTeamFolderPopup />
<ShareCreatePopup />
<ShareEditPopup />
@@ -19,111 +18,100 @@
<RenameItemPopup />
<MoveItemPopup />
<!--Mobile components-->
<!--Mobile components-->
<FileSortingMobile />
<FileFilterMobile />
<!--Navigations-->
<!--Navigations-->
<MobileNavigation />
<!--Others-->
<!--Others-->
<DragUI />
<!--2 col Sidebars-->
<!--2 col Sidebars-->
<SidebarNavigation />
<PanelNavigationFiles />
<PanelNavigationFiles />
<div
@contextmenu.prevent.capture="contextMenu($event, undefined)"
class="lg:grid lg:content-start lg:flex-grow lg:px-3.5 transition-transform duration-200"
>
<DesktopToolbar />
<div @contextmenu.prevent.capture="contextMenu($event, undefined)" class="transition-transform duration-200 lg:grid lg:flex-grow lg:content-start lg:px-3.5">
<DesktopToolbar />
<MobileToolbar />
<MobileToolbar />
<!--File list & info sidebar-->
<div class="flex space-x-3 lg:overflow-hidden lg:h-screen">
<!--File list & info sidebar-->
<div class="flex space-x-3 lg:h-screen lg:overflow-hidden">
<router-view id="file-view" class="relative w-full" :key="$route.fullPath" />
<router-view
id="file-view"
class="relative w-full"
:key="$route.fullPath"
/>
<InfoSidebar v-if="isVisibleSidebar" />
</div>
</div>
<InfoSidebar v-if="isVisibleSidebar" />
</div>
</div>
</div>
</template>
<script>
import FileSortingMobile from "../components/FilesView/FileSortingMobile";
import SidebarNavigation from "../components/Sidebar/SidebarNavigation";
import FileFilterMobile from '../components/FilesView/FileFilterMobile'
import CreateFolderPopup from '../components/Others/CreateFolderPopup'
import ProcessingPopup from '../components/FilesView/ProcessingPopup'
import MobileNavigation from '../components/Others/MobileNavigation'
import ShareCreatePopup from '../components/Others/ShareCreatePopup'
import DesktopToolbar from '../components/FilesView/DesktopToolbar'
import CreateTeamFolderPopup from "../components/Teams/CreateTeamFolderPopup"
import ConfirmPopup from '../components/Others/Popup/ConfirmPopup'
import RenameItemPopup from '../components/Others/RenameItemPopup'
import PanelNavigationFiles from "./FileView/Components/PanelNavigationFiles"
import MobileToolbar from '../components/FilesView/MobileToolbar'
import ShareEditPopup from '../components/Others/ShareEditPopup'
import FilePreview from '../components/FilePreview/FilePreview'
import MoveItemPopup from '../components/Others/MoveItemPopup'
import EditTeamFolderPopup from "../components/Teams/EditTeamFolderPopup"
import Spotlight from '../components/Spotlight/Spotlight'
import DragUI from '../components/FilesView/DragUI'
import InfoSidebar from "../components/FilesView/InfoSidebar"
import {events} from '../bus'
import {mapGetters} from "vuex"
import FileSortingMobile from '../components/FilesView/FileSortingMobile'
import SidebarNavigation from '../components/Sidebar/SidebarNavigation'
import FileFilterMobile from '../components/FilesView/FileFilterMobile'
import CreateFolderPopup from '../components/Others/CreateFolderPopup'
import ProcessingPopup from '../components/FilesView/ProcessingPopup'
import MobileNavigation from '../components/Others/MobileNavigation'
import ShareCreatePopup from '../components/Others/ShareCreatePopup'
import DesktopToolbar from '../components/FilesView/DesktopToolbar'
import CreateTeamFolderPopup from '../components/Teams/CreateTeamFolderPopup'
import ConfirmPopup from '../components/Others/Popup/ConfirmPopup'
import RenameItemPopup from '../components/Others/RenameItemPopup'
import PanelNavigationFiles from './FileView/Components/PanelNavigationFiles'
import MobileToolbar from '../components/FilesView/MobileToolbar'
import ShareEditPopup from '../components/Others/ShareEditPopup'
import FilePreview from '../components/FilePreview/FilePreview'
import MoveItemPopup from '../components/Others/MoveItemPopup'
import EditTeamFolderPopup from '../components/Teams/EditTeamFolderPopup'
import Spotlight from '../components/Spotlight/Spotlight'
import DragUI from '../components/FilesView/DragUI'
import InfoSidebar from '../components/FilesView/InfoSidebar'
import { events } from '../bus'
import { mapGetters } from 'vuex'
export default {
name: 'Platform',
components: {
CreateTeamFolderPopup,
PanelNavigationFiles,
EditTeamFolderPopup,
CreateFolderPopup,
FileSortingMobile,
SidebarNavigation,
FileFilterMobile,
MobileNavigation,
ShareCreatePopup,
ProcessingPopup,
RenameItemPopup,
ShareEditPopup,
DesktopToolbar,
MoveItemPopup,
MobileToolbar,
ConfirmPopup,
InfoSidebar,
FilePreview,
Spotlight,
DragUI,
},
computed: {
...mapGetters([
'isVisibleSidebar',
'isLimitedUser',
])
},
data() {
return {
isScaledDown: false
}
},
methods: {
contextMenu(event, item) {
events.$emit('context-menu:show', event, item)
},
},
mounted() {
// TODO: new scaledown effect
events.$on('mobile-menu:show', () => this.isScaledDown = true)
events.$on('mobile-menu:hide', () => this.isScaledDown = false)
}
}
export default {
name: 'Platform',
components: {
CreateTeamFolderPopup,
PanelNavigationFiles,
EditTeamFolderPopup,
CreateFolderPopup,
FileSortingMobile,
SidebarNavigation,
FileFilterMobile,
MobileNavigation,
ShareCreatePopup,
ProcessingPopup,
RenameItemPopup,
ShareEditPopup,
DesktopToolbar,
MoveItemPopup,
MobileToolbar,
ConfirmPopup,
InfoSidebar,
FilePreview,
Spotlight,
DragUI,
},
computed: {
...mapGetters(['isVisibleSidebar', 'isLimitedUser']),
},
data() {
return {
isScaledDown: false,
}
},
methods: {
contextMenu(event, item) {
events.$emit('context-menu:show', event, item)
},
},
mounted() {
// TODO: new scaledown effect
events.$on('mobile-menu:show', () => (this.isScaledDown = true))
events.$on('mobile-menu:hide', () => (this.isScaledDown = false))
},
}
</script>

View File

@@ -1,62 +1,60 @@
<template>
<div class="lg:flex md:h-screen md:overflow-hidden dark:bg-dark-background bg-light-background">
<!--On Top of App Components-->
<div class="bg-light-background dark:bg-dark-background md:h-screen md:overflow-hidden lg:flex">
<!--On Top of App Components-->
<FilePreview />
<Spotlight />
<Spotlight />
<ConfirmPopup />
<ConfirmPopup />
<ConfirmPassword />
<ConfirmPassword />
<!--2FA popups-->
<TwoFactorRecoveryCodesPopup />
<TwoFactorQrSetupPopup />
<!--2FA popups-->
<TwoFactorRecoveryCodesPopup />
<TwoFactorQrSetupPopup />
<!--Access Token Popup-->
<CreatePersonalTokenPopup />
<!--Access Token Popup-->
<CreatePersonalTokenPopup />
<!--Payments Popup-->
<SelectPlanSubscriptionPopup />
<SelectSingleChargeMethodPopup />
<!--Payments Popup-->
<SelectPlanSubscriptionPopup />
<SelectSingleChargeMethodPopup />
<SidebarNavigation />
<SidebarNavigation />
<!--Navigations-->
<!--Navigations-->
<MobileNavigation />
<MobileNavigationToolbar />
<MobileNavigationToolbar />
<div v-if="user" class="lg:pt-6 md:px-6 px-2.5 lg:pb-0 pb-12 w-full overflow-x-hidden relative xl:max-w-screen-lg md:max-w-4xl mx-auto">
<div v-if="! isLoading" id="page-content">
<div v-if="user" class="relative mx-auto w-full overflow-x-hidden px-2.5 pb-12 md:max-w-4xl md:px-6 lg:pt-6 lg:pb-0 xl:max-w-screen-lg">
<div v-if="!isLoading" id="page-content">
<div class="card sticky top-0 z-10 shadow-card" style="padding-bottom: 0">
<!--User thumbnail-->
<div class="mb-3 flex items-center">
<!--Image input for replace avatar-->
<AvatarInput v-model="avatar" :avatar="user.data.relationships.settings.data.attributes.avatar.md" />
<div class="card shadow-card sticky top-0 z-10" style="padding-bottom: 0">
<!--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 v-if="config.subscriptionType === 'fixed'" :color="subscriptionColor">
{{ subscriptionStatus }}
</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-->
<AvatarInput v-model="avatar" :avatar="user.data.relationships.settings.data.attributes.avatar.md" />
<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 v-if="config.subscriptionType === 'fixed'" :color="subscriptionColor">
{{ subscriptionStatus }}
</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" />
<!--Router Content-->
<router-view :user="user" />
</div>
<div id="loader" v-if="isLoading">
<Spinner />
@@ -66,98 +64,90 @@
</template>
<script>
import MobileNavigation from "../components/Others/MobileNavigation";
import SelectSingleChargeMethodPopup from "../components/Others/SelectSingleChargeMethodPopup";
import SelectPlanSubscriptionPopup from "../components/Subscription/SelectPlanSubscriptionPopup";
import ConfirmPopup from "../components/Others/Popup/ConfirmPopup";
import FilePreview from "../components/FilePreview/FilePreview";
import Spotlight from "../components/Spotlight/Spotlight";
import TwoFactorRecoveryCodesPopup from "../components/Others/TwoFactorRecoveryCodesPopup";
import CreatePersonalTokenPopup from "../components/Others/CreatePersonalTokenPopup";
import TwoFactorQrSetupPopup from "../components/Others/TwoFactorQrSetupPopup";
import AvatarInput from "../components/Others/Forms/AvatarInput";
import SidebarNavigation from "../components/Sidebar/SidebarNavigation"
import ColorLabel from "../components/Others/ColorLabel";
import Spinner from "../components/FilesView/Spinner";
import {mapGetters} from 'vuex'
import CardNavigation from "../components/Admin/CardNavigation";
import ConfirmPassword from "../components/Others/ConfirmPassword";
import MobileNavigationToolbar from "./MobileNavigationToolbar";
export default {
name: 'Settings',
components: {
MobileNavigationToolbar,
MobileNavigation,
ConfirmPassword,
SelectSingleChargeMethodPopup,
SelectPlanSubscriptionPopup,
ConfirmPopup,
CardNavigation,
FilePreview,
Spotlight,
TwoFactorRecoveryCodesPopup,
CreatePersonalTokenPopup,
TwoFactorQrSetupPopup,
SidebarNavigation,
AvatarInput,
ColorLabel,
Spinner,
import MobileNavigation from '../components/Others/MobileNavigation'
import SelectSingleChargeMethodPopup from '../components/Others/SelectSingleChargeMethodPopup'
import SelectPlanSubscriptionPopup from '../components/Subscription/SelectPlanSubscriptionPopup'
import ConfirmPopup from '../components/Others/Popup/ConfirmPopup'
import FilePreview from '../components/FilePreview/FilePreview'
import Spotlight from '../components/Spotlight/Spotlight'
import TwoFactorRecoveryCodesPopup from '../components/Others/TwoFactorRecoveryCodesPopup'
import CreatePersonalTokenPopup from '../components/Others/CreatePersonalTokenPopup'
import TwoFactorQrSetupPopup from '../components/Others/TwoFactorQrSetupPopup'
import AvatarInput from '../components/Others/Forms/AvatarInput'
import SidebarNavigation from '../components/Sidebar/SidebarNavigation'
import ColorLabel from '../components/Others/ColorLabel'
import Spinner from '../components/FilesView/Spinner'
import { mapGetters } from 'vuex'
import CardNavigation from '../components/Admin/CardNavigation'
import ConfirmPassword from '../components/Others/ConfirmPassword'
import MobileNavigationToolbar from './MobileNavigationToolbar'
export default {
name: 'Settings',
components: {
MobileNavigationToolbar,
MobileNavigation,
ConfirmPassword,
SelectSingleChargeMethodPopup,
SelectPlanSubscriptionPopup,
ConfirmPopup,
CardNavigation,
FilePreview,
Spotlight,
TwoFactorRecoveryCodesPopup,
CreatePersonalTokenPopup,
TwoFactorQrSetupPopup,
SidebarNavigation,
AvatarInput,
ColorLabel,
Spinner,
},
computed: {
...mapGetters(['config', 'user']),
subscriptionStatus() {
return this.user.data.relationships.subscription ? this.$t('global.premium') : this.$t('global.free')
},
computed: {
...mapGetters([
'config',
'user',
]),
subscriptionStatus() {
return this.user.data.relationships.subscription
? this.$t('global.premium')
: this.$t('global.free')
},
subscriptionColor() {
return this.user.data.relationships.subscription
? 'green'
: 'purple'
},
canShowUpgradeWarning() {
return this.config.storageLimit && this.user.data.attributes.storage.used > 95
},
canShowIncompletePayment() {
return this.user.data.attributes.incomplete_payment
},
pages() {
let list = [
{
title: this.$t('menu.profile'),
route: 'Profile',
},
{
title: this.$t('menu.password'),
route: 'Password',
},
{
title: this.$t('menu.storage'),
route: 'Storage',
},
]
// Push billing item if subscription is set
if (['fixed', 'metered'].includes(this.config.subscriptionType)) {
list.push({
title: this.$t('Billing'),
route: 'Billing',
})
}
return list
}
subscriptionColor() {
return this.user.data.relationships.subscription ? 'green' : 'purple'
},
data() {
return {
avatar: undefined,
isLoading: false,
canShowUpgradeWarning() {
return this.config.storageLimit && this.user.data.attributes.storage.used > 95
},
canShowIncompletePayment() {
return this.user.data.attributes.incomplete_payment
},
pages() {
let list = [
{
title: this.$t('menu.profile'),
route: 'Profile',
},
{
title: this.$t('menu.password'),
route: 'Password',
},
{
title: this.$t('menu.storage'),
route: 'Storage',
},
]
// Push billing item if subscription is set
if (['fixed', 'metered'].includes(this.config.subscriptionType)) {
list.push({
title: this.$t('Billing'),
route: 'Billing',
})
}
return list
},
},
data() {
return {
avatar: undefined,
isLoading: false,
}
}
},
}
</script>

View File

@@ -3,18 +3,17 @@
</template>
<script>
import { mapGetters } from 'vuex'
import { mapGetters } from 'vuex'
export default {
name: 'SetupWizard',
computed: {
...mapGetters(['config']),
},
mounted() {
let status = this.$root.$data.config.installation
export default {
name: 'SetupWizard',
computed: {
...mapGetters(['config']),
},
mounted() {
let status = this.$root.$data.config.installation
if (status && status === 'setup-done')
this.$router.push({name: 'SignIn'})
}
}
if (status && status === 'setup-done') this.$router.push({ name: 'SignIn' })
},
}
</script>

View File

@@ -1,10 +1,9 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<div class="content-headline">
<settings-icon size="40" class="title-icon text-theme"/>
<settings-icon size="40" class="title-icon text-theme" />
<h1>Setup Wizard</h1>
<h2>Create your admin account.</h2>
</div>
@@ -22,7 +21,7 @@
<div class="block-wrapper">
<label>Full Name:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Full Name" rules="required" v-slot="{ errors }">
<input v-model="admin.name" placeholder="Type your full name" type="text" :class="{'border-red': errors[0]}" />
<input v-model="admin.name" placeholder="Type your full name" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -30,7 +29,7 @@
<div class="block-wrapper">
<label>Email:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Email" rules="required" v-slot="{ errors }">
<input v-model="admin.email" placeholder="Type your email" type="email" :class="{'border-red': errors[0]}" />
<input v-model="admin.email" placeholder="Type your email" type="email" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -38,7 +37,7 @@
<div class="block-wrapper">
<label>Password:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Password" rules="required|confirmed:confirmation" v-slot="{ errors }">
<input v-model="admin.password" placeholder="Type your password" type="password" :class="{'border-red': errors[0]}" />
<input v-model="admin.password" placeholder="Type your password" type="password" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -46,166 +45,156 @@
<div class="block-wrapper">
<label>Password Confirmation:</label>
<ValidationProvider tag="div" class="input-wrapper" name="confirmation" rules="required" vid="confirmation" v-slot="{ errors }">
<input v-model="admin.password_confirmation" placeholder="Confirm your password" type="password" :class="{'border-red': errors[0]}" />
<input v-model="admin.password_confirmation" placeholder="Confirm your password" type="password" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="submit-wrapper">
<AuthButton icon="chevron-right" text="Create Admin and Login" :loading="isLoading" :disabled="isLoading"/>
<AuthButton icon="chevron-right" text="Create Admin and Login" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
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 InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import { SettingsIcon } from 'vue-feather-icons'
import {required} from 'vee-validate/dist/rules'
import {events} from '../../bus'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
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 InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import { events } from '../../bus'
import axios from 'axios'
export default {
name: 'EnvironmentSetup',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
SwitchInput,
AuthContent,
ImageInput,
AuthButton,
FormLabel,
required,
InfoBox,
},
data() {
return {
isLoading: false,
admin: {
name: '',
email: '',
avatar: undefined,
password: '',
password_confirmation: '',
},
}
},
methods: {
async adminAccountSubmit() {
export default {
name: 'EnvironmentSetup',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
SwitchInput,
AuthContent,
ImageInput,
AuthButton,
FormLabel,
required,
InfoBox,
},
data() {
return {
isLoading: false,
admin: {
name: '',
email: '',
avatar: undefined,
password: '',
password_confirmation: '',
},
}
},
methods: {
async adminAccountSubmit() {
// Validate fields
const isValid = await this.$refs.adminAccount.validate()
// Validate fields
const isValid = await this.$refs.adminAccount.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.admin.name)
formData.append('email', this.admin.email)
formData.append('password', this.admin.password)
formData.append('password_confirmation', this.admin.password_confirmation)
// Add image to form
formData.append('name', this.admin.name)
formData.append('email', this.admin.email)
formData.append('password', this.admin.password)
formData.append('password_confirmation', this.admin.password_confirmation)
formData.append('license', localStorage.getItem('license'))
formData.append('purchase_code', localStorage.getItem('purchase_code'))
formData.append('license', localStorage.getItem('license'))
formData.append('purchase_code', localStorage.getItem('purchase_code'))
if (this.admin.avatar) formData.append('avatar', this.admin.avatar)
if (this.admin.avatar)
formData.append('avatar', this.admin.avatar)
axios
.post('/api/setup/admin-setup', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => {
// End loading
this.isLoading = false
axios
.post('/api/setup/admin-setup', formData, {
headers: {
'Content-Type': 'multipart/form-data',
}
})
.then(response => {
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
// End loading
this.isLoading = false
if (localStorage.getItem('license') === 'Extended') {
this.$store.commit('SET_SAAS', true)
}
// Set login state
this.$store.commit('SET_AUTHORIZED', true)
if (localStorage.getItem('license') === 'Extended') {
this.$store.commit('SET_SAAS', true)
}
// Go to files page
this.$router.push({name: 'Dashboard'})
// Remove license from localStorage
localStorage.removeItem('purchase_code')
localStorage.removeItem('license')
})
.catch(error => {
if (error.response.status == 401) {
if (error.response.data.error === 'invalid_client') {
events.$emit('alert:open', {
emoji: '🤔',
title: this.$t('popup_passport_error.title'),
message: this.$t('popup_passport_error.message')
})
}
}
if (error.response.status == 500) {
// Go to files page
this.$router.push({ name: 'Dashboard' })
// Remove license from localStorage
localStorage.removeItem('purchase_code')
localStorage.removeItem('license')
})
.catch((error) => {
if (error.response.status == 401) {
if (error.response.data.error === 'invalid_client') {
events.$emit('alert:open', {
emoji: '🤔',
title: this.$t('popup_signup_error.title'),
message: this.$t('popup_signup_error.message')
title: this.$t('popup_passport_error.title'),
message: this.$t('popup_passport_error.message'),
})
}
}
if (error.response.status == 500) {
events.$emit('alert:open', {
emoji: '🤔',
title: this.$t('popup_signup_error.title'),
message: this.$t('popup_signup_error.message'),
})
}
if (error.response.status == 422) {
if (error.response.data.errors['email']) {
this.$refs.adminAccount.setErrors({
Email: error.response.data.errors['email'],
})
}
if (error.response.status == 422) {
if (error.response.data.errors['email']) {
this.$refs.adminAccount.setErrors({
'Email': error.response.data.errors['email']
});
}
if (error.response.data.errors['password']) {
this.$refs.adminAccount.setErrors({
'Password': error.response.data.errors['password']
});
}
if (error.response.data.errors['password']) {
this.$refs.adminAccount.setErrors({
Password: error.response.data.errors['password'],
})
}
}
// End loading
this.isLoading = false
})
},
// End loading
this.isLoading = false
})
},
created() {
this.$scrollTop()
}
}
},
created() {
this.$scrollTop()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
</style>

View File

@@ -1,24 +1,18 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Set up your application appearance, analytics, etc."
>
<Headline class="container mx-auto max-w-screen-sm" title="Setup Wizard" description="Set up your application appearance, analytics, etc.">
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<ValidationObserver @submit.prevent="appSetupSubmit" ref="appSetup" v-slot="{ invalid }" tag="form"
class="form block-form">
<ValidationObserver @submit.prevent="appSetupSubmit" ref="appSetup" v-slot="{ invalid }" tag="form" class="form block-form">
<FormLabel>General Settings</FormLabel>
<div class="block-wrapper">
<label>App Title:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="App Title" rules="required" v-slot="{ errors }">
<input v-model="app.title" placeholder="Type your app title" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="app.title" placeholder="Type your app title" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -26,7 +20,7 @@
<div class="block-wrapper">
<label>App Description:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="App Description" rules="required" v-slot="{ errors }">
<input v-model="app.description" placeholder="Type your app description" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="app.description" placeholder="Type your app description" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -34,28 +28,28 @@
<div class="block-wrapper">
<label>App Logo (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="App Logo" v-slot="{ errors }">
<ImageInput v-model="app.logo" :error="errors[0]"/>
<ImageInput v-model="app.logo" :error="errors[0]" />
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>App Logo Horizontal (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="App Logo" v-slot="{ errors }">
<ImageInput v-model="app.logo_horizontal" :error="errors[0]"/>
<ImageInput v-model="app.logo_horizontal" :error="errors[0]" />
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>App Favicon (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="App Favicon" v-slot="{ errors }">
<ImageInput v-model="app.favicon" :error="errors[0]"/>
<ImageInput v-model="app.favicon" :error="errors[0]" />
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>OG Image (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="App Favicon" v-slot="{ errors }">
<ImageInput v-model="app.og_image" :error="errors[0]"/>
<ImageInput v-model="app.og_image" :error="errors[0]" />
<small class="input-help">Image that appear when someone shares the content to Facebook or any other social medium. Preferred size is 1200x627</small>
</ValidationProvider>
</div>
@@ -63,7 +57,7 @@
<div class="block-wrapper">
<label>App Touch Icon (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="App Favicon" v-slot="{ errors }">
<ImageInput v-model="app.touch_icon" :error="errors[0]"/>
<ImageInput v-model="app.touch_icon" :error="errors[0]" />
<small class="input-help">If user store bookmark on his phone screen, this icon appear in app thumbnail. Preferred size is 156x156</small>
</ValidationProvider>
</div>
@@ -72,19 +66,16 @@
<div class="block-wrapper">
<label>Contact Email:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Contact Email"
rules="required" v-slot="{ errors }">
<input v-model="app.contactMail" placeholder="Type your contact email" type="email" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Contact Email" rules="required" v-slot="{ errors }">
<input v-model="app.contactMail" placeholder="Type your contact email" type="email" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Google Analytics Code (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Google Analytics Code"
v-slot="{ errors }">
<input v-model="app.googleAnalytics" placeholder="Paste your Google Analytics Code"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Google Analytics Code" v-slot="{ errors }">
<input v-model="app.googleAnalytics" placeholder="Paste your Google Analytics Code" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -94,9 +85,11 @@
<div class="inline-wrapper">
<div class="switch-label">
<label class="input-label">Storage Limitation:</label>
<small class="input-help">If this value is off, all users will have infinity storage capacity and you won't be able to charge your users for storage plan.</small>
<small class="input-help"
>If this value is off, all users will have infinity storage capacity and you won't be able to charge your users for storage plan.</small
>
</div>
<SwitchInput v-model="app.storageLimitation" class="switch" :state="app.storageLimitation"/>
<SwitchInput v-model="app.storageLimitation" class="switch" :state="app.storageLimitation" />
</div>
</div>
</div>
@@ -104,12 +97,13 @@
<div class="block-wrapper" v-if="app.storageLimitation">
<label>Default Storage Space for Accounts:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Default Storage Space" rules="required" v-slot="{ errors }">
<input v-model="app.defaultStorage"
min="1"
max="999999999"
placeholder="Set default storage space in GB"
type="number"
:class="{'border-red': errors[0]}"
<input
v-model="app.defaultStorage"
min="1"
max="999999999"
placeholder="Set default storage space in GB"
type="number"
:class="{ 'border-red': errors[0] }"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
@@ -120,146 +114,137 @@
<div class="inline-wrapper">
<div class="switch-label">
<label class="input-label">Allow User Registration:</label>
<small class="input-help">You can disable public registration for new users. You will still able to create new users in administration panel.</small>
<small class="input-help"
>You can disable public registration for new users. You will still able to create new users in administration panel.</small
>
</div>
<SwitchInput v-model="app.userRegistration" class="switch" :state="app.userRegistration"/>
<SwitchInput v-model="app.userRegistration" class="switch" :state="app.userRegistration" />
</div>
</div>
</div>
<div class="submit-wrapper">
<AuthButton icon="chevron-right" text="Save and Create Admin" :loading="isLoading" :disabled="isLoading"/>
<AuthButton icon="chevron-right" text="Save and Create Admin" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
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 InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {SettingsIcon} from 'vue-feather-icons'
import Headline from "../Auth/Headline"
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
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 InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import Headline from '../Auth/Headline'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'EnvironmentSetup',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
SwitchInput,
AuthContent,
ImageInput,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
},
data() {
return {
isLoading: false,
app: {
title: '',
description: '',
logo: undefined,
logo_horizontal: undefined,
favicon: undefined,
og_image: undefined,
touch_icon: undefined,
contactMail: '',
googleAnalytics: '',
defaultStorage: '5',
userRegistration: 1,
storageLimitation: 1,
},
}
},
methods: {
async appSetupSubmit() {
// Validate fields
const isValid = await this.$refs.appSetup.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Create form
let formData = new FormData()
// Add image to form
formData.append('title', this.app.title)
formData.append('description', this.app.description)
formData.append('contactMail', this.app.contactMail)
formData.append('userRegistration', Boolean(this.app.userRegistration) ? 1 : 0)
formData.append('storageLimitation', Boolean(this.app.storageLimitation) ? 1 : 0)
if (this.app.googleAnalytics)
formData.append('googleAnalytics', this.app.googleAnalytics)
if (this.app.defaultStorage)
formData.append('defaultStorage', this.app.defaultStorage)
if (this.app.logo)
formData.append('logo', this.app.logo)
if (this.app.logo_horizontal)
formData.append('logo_horizontal', this.app.logo_horizontal)
if (this.app.og_image)
formData.append('og_image', this.app.og_image)
if (this.app.touch_icon)
formData.append('touch_icon', this.app.touch_icon)
if (this.app.favicon)
formData.append('favicon', this.app.favicon)
// Send request to get verify account
axios
.post('/api/setup/app-setup', formData, {
headers: {
'Content-Type': 'multipart/form-data',
}
})
.then(response => {
// End loading
this.isLoading = false
// Redirect to next step
this.$router.push({name: 'AdminAccount'})
})
.catch(error => {
// End loading
this.isLoading = false
})
export default {
name: 'EnvironmentSetup',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
SwitchInput,
AuthContent,
ImageInput,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
},
data() {
return {
isLoading: false,
app: {
title: '',
description: '',
logo: undefined,
logo_horizontal: undefined,
favicon: undefined,
og_image: undefined,
touch_icon: undefined,
contactMail: '',
googleAnalytics: '',
defaultStorage: '5',
userRegistration: 1,
storageLimitation: 1,
},
},
created() {
this.$scrollTop()
}
}
},
methods: {
async appSetupSubmit() {
// Validate fields
const isValid = await this.$refs.appSetup.validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Create form
let formData = new FormData()
// Add image to form
formData.append('title', this.app.title)
formData.append('description', this.app.description)
formData.append('contactMail', this.app.contactMail)
formData.append('userRegistration', Boolean(this.app.userRegistration) ? 1 : 0)
formData.append('storageLimitation', Boolean(this.app.storageLimitation) ? 1 : 0)
if (this.app.googleAnalytics) formData.append('googleAnalytics', this.app.googleAnalytics)
if (this.app.defaultStorage) formData.append('defaultStorage', this.app.defaultStorage)
if (this.app.logo) formData.append('logo', this.app.logo)
if (this.app.logo_horizontal) formData.append('logo_horizontal', this.app.logo_horizontal)
if (this.app.og_image) formData.append('og_image', this.app.og_image)
if (this.app.touch_icon) formData.append('touch_icon', this.app.touch_icon)
if (this.app.favicon) formData.append('favicon', this.app.favicon)
// Send request to get verify account
axios
.post('/api/setup/app-setup', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((response) => {
// End loading
this.isLoading = false
// Redirect to next step
this.$router.push({ name: 'AdminAccount' })
})
.catch((error) => {
// End loading
this.isLoading = false
})
},
},
created() {
this.$scrollTop()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
</style>

View File

@@ -1,35 +1,25 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Set up your billing information."
>
<Headline class="container mx-auto max-w-screen-sm" title="Setup Wizard" description="Set up your billing information.">
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
<ValidationObserver @submit.prevent="billingInformationSubmit" ref="billingInformation" v-slot="{ invalid }"
tag="form" class="form block-form">
</Headline>
<ValidationObserver @submit.prevent="billingInformationSubmit" ref="billingInformation" v-slot="{ invalid }" tag="form" class="form block-form">
<FormLabel>Company Information</FormLabel>
<div class="block-wrapper">
<label>Company Name:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Name"
rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_name" placeholder="Type your company name"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Name" rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_name" placeholder="Type your company name" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>VAT Number:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Vat Number"
rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_vat_number" placeholder="Type your VAT number"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Vat Number" rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_vat_number" placeholder="Type your VAT number" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -38,19 +28,16 @@
<div class="block-wrapper">
<label>Billing Country:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Country"
rules="required" v-slot="{ errors }">
<SelectInput v-model="billingInformation.billing_country" :options="countries" placeholder="Select your billing country" :isError="errors[0]"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Country" rules="required" v-slot="{ errors }">
<SelectInput v-model="billingInformation.billing_country" :options="countries" placeholder="Select your billing country" :isError="errors[0]" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Billing Address:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Address"
rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_address" placeholder="Type your billing address"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Address" rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_address" placeholder="Type your billing address" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -58,19 +45,15 @@
<div class="wrapper-inline">
<div class="block-wrapper">
<label>Billing City:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing City"
rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_city" placeholder="Type your billing city"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing City" rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_city" placeholder="Type your billing city" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Billing Postal Code:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Postal Code"
rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_postal_code"
placeholder="Type your billing postal code" type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Postal Code" rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_postal_code" placeholder="Type your billing postal code" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -78,116 +61,106 @@
<div class="block-wrapper">
<label>Billing State:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing State"
rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_state" placeholder="Type your billing state"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing State" rules="required" v-slot="{ errors }">
<input v-model="billingInformation.billing_state" placeholder="Type your billing state" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Billing Phone Number (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Phone Number"
v-slot="{ errors }">
<input v-model="billingInformation.billing_phone_number" placeholder="Type your billing phone number"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Billing Phone Number" v-slot="{ errors }">
<input v-model="billingInformation.billing_phone_number" placeholder="Type your billing phone number" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="submit-wrapper">
<AuthButton icon="chevron-right" text="Save and Create Plans" :loading="isLoading"
:disabled="isLoading"/>
<AuthButton icon="chevron-right" text="Save and Create Plans" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {SettingsIcon} from 'vue-feather-icons'
import Headline from "../Auth/Headline"
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import Headline from '../Auth/Headline'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'BillingsDetail',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
},
computed: {
...mapGetters(['countries']),
},
data() {
return {
isLoading: false,
billingInformation: {
billing_phone_number: '',
billing_postal_code: '',
billing_vat_number: '',
billing_address: '',
billing_country: '',
billing_state: '',
billing_city: '',
billing_name: '',
}
}
},
methods: {
async billingInformationSubmit() {
// Validate fields
const isValid = await this.$refs.billingInformation.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/setup/stripe-billings', this.billingInformation)
.then(() => {
// Redirect to next step
this.$router.push({name: 'SubscriptionPlans'})
})
.catch(error => {
})
.finally(() => {
this.isLoading = false
})
export default {
name: 'BillingsDetail',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
},
computed: {
...mapGetters(['countries']),
},
data() {
return {
isLoading: false,
billingInformation: {
billing_phone_number: '',
billing_postal_code: '',
billing_vat_number: '',
billing_address: '',
billing_country: '',
billing_state: '',
billing_city: '',
billing_name: '',
},
},
created() {
this.$scrollTop()
}
}
},
methods: {
async billingInformationSubmit() {
// Validate fields
const isValid = await this.$refs.billingInformation.validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/setup/stripe-billings', this.billingInformation)
.then(() => {
// Redirect to next step
this.$router.push({ name: 'SubscriptionPlans' })
})
.catch((error) => {})
.finally(() => {
this.isLoading = false
})
},
},
created() {
this.$scrollTop()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
</style>

View File

@@ -1,20 +1,18 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Set up your database connection to install application database."
>
<Headline class="container mx-auto max-w-screen-sm" title="Setup Wizard" description="Set up your database connection to install application database.">
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<ValidationObserver @submit.prevent="databaseCredentialsSubmit" ref="verifyPurchaseCode" v-slot="{ invalid }" tag="form" class="form block-form">
<FormLabel>Database Credentials</FormLabel>
<InfoBox>
<p>We strongly recommend use MySQL or MariaDB database. Create new database, set all privileges and get credentials. For those who use cPanel or Plesk, here is useful resources:</p>
<p>
We strongly recommend use MySQL or MariaDB database. Create new database, set all privileges and get credentials. For those who use cPanel or Plesk, here is
useful resources:
</p>
<ul>
<li>
<a href="https://www.inmotionhosting.com/support/edu/cpanel/create-database-2/" target="_blank">1. cPanel - MySQL Database Wizard</a>
@@ -26,7 +24,13 @@
<div class="block-wrapper">
<label>Connection:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Connection" rules="required" v-slot="{ errors }">
<SelectInput v-model="databaseCredentials.connection" :options="connectionList" default="mysql" placeholder="Select your database connection" :isError="errors[0]"/>
<SelectInput
v-model="databaseCredentials.connection"
:options="connectionList"
default="mysql"
placeholder="Select your database connection"
:isError="errors[0]"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -34,7 +38,7 @@
<div class="block-wrapper">
<label>Host:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Host" rules="required" v-slot="{ errors }">
<input v-model="databaseCredentials.host" placeholder="Type your database host" type="text" :class="{'border-red': errors[0]}" />
<input v-model="databaseCredentials.host" placeholder="Type your database host" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -42,7 +46,7 @@
<div class="block-wrapper">
<label>Port:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Port" rules="required" v-slot="{ errors }">
<input v-model="databaseCredentials.port" placeholder="Type your database port" type="text" :class="{'border-red': errors[0]}" />
<input v-model="databaseCredentials.port" placeholder="Type your database port" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -50,7 +54,7 @@
<div class="block-wrapper">
<label>Database Name:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Database Name" rules="required" v-slot="{ errors }">
<input v-model="databaseCredentials.name" placeholder="Select your database name" type="text" :class="{'border-red': errors[0]}" />
<input v-model="databaseCredentials.name" placeholder="Select your database name" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -58,7 +62,7 @@
<div class="block-wrapper">
<label>Database Username:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Database Username" rules="required" v-slot="{ errors }">
<input v-model="databaseCredentials.username" placeholder="Select your database name" type="text" :class="{'border-red': errors[0]}" />
<input v-model="databaseCredentials.username" placeholder="Select your database name" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -66,124 +70,120 @@
<div class="block-wrapper">
<label>Database Password:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Database Password" rules="required" v-slot="{ errors }">
<input v-model="databaseCredentials.password" placeholder="Select your database password" type="text" :class="{'border-red': errors[0]}" />
<input v-model="databaseCredentials.password" placeholder="Select your database password" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<InfoBox v-if="isError" type="error" style="margin-bottom: 10px">
<p>We couldn't establish database connection. Please double check your database credentials.</p>
<br>
<br />
<p>Detailed error: {{ errorMessage }}</p>
</InfoBox>
<div class="submit-wrapper">
<AuthButton icon="chevron-right" :text="submitButtonText" :loading="isLoading" :disabled="isLoading"/>
<AuthButton icon="chevron-right" :text="submitButtonText" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import { SettingsIcon } from 'vue-feather-icons'
import {required} from 'vee-validate/dist/rules'
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'Database',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
export default {
name: 'Database',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
},
computed: {
submitButtonText() {
return this.isLoading ? 'Testing and Installing Database' : 'Test Connection and Install Database'
},
computed: {
submitButtonText() {
return this.isLoading ? 'Testing and Installing Database' : 'Test Connection and Install Database'
}
},
data() {
return {
isLoading: false,
isError: false,
errorMessage: '',
connectionList: [
{
label: 'MySQL',
value: 'mysql',
},
],
databaseCredentials: {
connection: 'mysql',
host: '',
port: '',
name: '',
username: '',
password: '',
}
}
},
methods: {
async databaseCredentialsSubmit() {
// Validate fields
const isValid = await this.$refs.verifyPurchaseCode.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
this.isError = false
// Send request to get verify account
axios
.post('/api/setup/database', this.databaseCredentials)
.then(response => {
// End loading
this.isLoading = false
// Redirect to next step
this.$router.push({name: 'InstallationDisclaimer'})
})
.catch(error => {
if (error.response.status = 500) {
this.isError = true
this.errorMessage = error.response.data.message
}
// End loading
this.isLoading = false
})
},
data() {
return {
isLoading: false,
isError: false,
errorMessage: '',
connectionList: [
{
label: 'MySQL',
value: 'mysql',
},
],
databaseCredentials: {
connection: 'mysql',
host: '',
port: '',
name: '',
username: '',
password: '',
},
},
created() {
this.$scrollTop()
}
}
},
methods: {
async databaseCredentialsSubmit() {
// Validate fields
const isValid = await this.$refs.verifyPurchaseCode.validate()
if (!isValid) return
// Start loading
this.isLoading = true
this.isError = false
// Send request to get verify account
axios
.post('/api/setup/database', this.databaseCredentials)
.then((response) => {
// End loading
this.isLoading = false
// Redirect to next step
this.$router.push({ name: 'InstallationDisclaimer' })
})
.catch((error) => {
if ((error.response.status = 500)) {
this.isError = true
this.errorMessage = error.response.data.message
}
// End loading
this.isLoading = false
})
},
},
created() {
this.$scrollTop()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
</style>

View File

@@ -1,19 +1,16 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Set up your storage driver and email client."
>
<Headline class="container mx-auto max-w-screen-sm" title="Setup Wizard" description="Set up your storage driver and email client.">
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<ValidationObserver @submit.prevent="EnvironmentSetupSubmit" ref="environmentSetup" v-slot="{ invalid }" tag="form" class="form block-form">
<InfoBox>
<p>If you dont know which storage driver set, keep selected <b>'Local Driver'</b>. For more info, where
you can host your files <a href="https://vuefilemanager.com/docs/guide/storage.html#introduction" target="_blank">visit our guide</a>.</p>
<p>
If you dont know which storage driver set, keep selected <b>'Local Driver'</b>. For more info, where you can host your files
<a href="https://vuefilemanager.com/docs/guide/storage.html#introduction" target="_blank">visit our guide</a>.
</p>
</InfoBox>
<FormLabel>Storage Setup</FormLabel>
@@ -21,7 +18,7 @@
<div class="block-wrapper">
<label>Storage Service:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Storage Service" rules="required" v-slot="{ errors }">
<SelectInput v-model="storage.driver" :options="storageServiceList" default="local" placeholder="Select your storage service" :isError="errors[0]"/>
<SelectInput v-model="storage.driver" :options="storageServiceList" default="local" placeholder="Select your storage service" :isError="errors[0]" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -30,47 +27,46 @@
<div class="block-wrapper">
<label>Key:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Key" rules="required" v-slot="{ errors }">
<input v-model="storage.key" placeholder="Paste your key" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="storage.key" placeholder="Paste your key" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Secret:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Secret" rules="required" v-slot="{ errors }">
<input v-model="storage.secret" placeholder="Paste your secret" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="storage.secret" placeholder="Paste your secret" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Region:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Region" rules="required" v-slot="{ errors }">
<SelectInput v-model="storage.region" :options="regionList" :key="storage.driver" placeholder="Select your region" :isError="errors[0]"/>
<SelectInput v-model="storage.region" :options="regionList" :key="storage.driver" placeholder="Select your region" :isError="errors[0]" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
<small class="input-help">
Select your region where is your bucket/space created.
</small>
<small class="input-help"> Select your region where is your bucket/space created. </small>
</ValidationProvider>
</div>
<div class="block-wrapper" v-if="storage.driver !== 's3'">
<label>Endpoint URL:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Endpoint" rules="required" v-slot="{ errors }">
<input v-model="storage.endpoint" placeholder="Type your endpoint" type="text" :class="{'border-red': errors[0]}" readonly/>
<input v-model="storage.endpoint" placeholder="Type your endpoint" type="text" :class="{ 'border-red': errors[0] }" readonly />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Bucket:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Bucket" rules="required" v-slot="{ errors }">
<input v-model="storage.bucket" placeholder="Type your bucket name" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="storage.bucket" placeholder="Type your bucket name" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
<small class="input-help">
Provide your created unique bucket name
</small>
<small class="input-help"> Provide your created unique bucket name </small>
</ValidationProvider>
</div>
<InfoBox>
<p>Later, you can edit these data in your <b>.env</b> file which is located in app root folder.</p>
<p>
Later, you can edit these data in your
<b>.env</b> file which is located in app root folder.
</p>
</InfoBox>
</div>
@@ -79,7 +75,7 @@
<div class="block-wrapper">
<label>Mail Driver:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Mail Driver" rules="required" v-slot="{ errors }">
<SelectInput v-model="mail.driver" :options="mailDriverList" default="smtp" placeholder="Select your mail driver" :isError="errors[0]"/>
<SelectInput v-model="mail.driver" :options="mailDriverList" default="smtp" placeholder="Select your mail driver" :isError="errors[0]" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -87,7 +83,7 @@
<div class="block-wrapper">
<label>Mail Host:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Mail Host" rules="required" v-slot="{ errors }">
<input v-model="mail.host" placeholder="Type your mail host" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="mail.host" placeholder="Type your mail host" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -95,7 +91,7 @@
<div class="block-wrapper">
<label>Mail Port:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Mail Port" rules="required" v-slot="{ errors }">
<input v-model="mail.port" placeholder="Type your mail port" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="mail.port" placeholder="Type your mail port" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -103,7 +99,7 @@
<div class="block-wrapper">
<label>Mail Username:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Mail Username" rules="required" v-slot="{ errors }">
<input v-model="mail.username" placeholder="Type your mail username" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="mail.username" placeholder="Type your mail username" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -111,7 +107,7 @@
<div class="block-wrapper">
<label>Mail Password:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Mail Password" rules="required" v-slot="{ errors }">
<input v-model="mail.password" placeholder="Type your mail password" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="mail.password" placeholder="Type your mail password" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -119,32 +115,31 @@
<div class="block-wrapper">
<label>Mail Encryption:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Mail Encryption" rules="required" v-slot="{ errors }">
<SelectInput v-model="mail.encryption" :options="encryptionList" placeholder="Select your mail encryption" :isError="errors[0]"/>
<SelectInput v-model="mail.encryption" :options="encryptionList" placeholder="Select your mail encryption" :isError="errors[0]" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="submit-wrapper">
<AuthButton icon="chevron-right" text="Save and Set General Settings" :loading="isLoading" :disabled="isLoading"/>
<AuthButton icon="chevron-right" text="Save and Set General Settings" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {SettingsIcon} from 'vue-feather-icons'
import Headline from "../Auth/Headline"
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import Headline from '../Auth/Headline'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
@@ -160,24 +155,20 @@ export default {
FormLabel,
required,
InfoBox,
Headline,
Headline,
},
watch: {
'storage.driver': function () {
this.storage.region = ''
},
'storage.region': function (val) {
if (this.storage.driver === 'spaces')
this.storage.endpoint = 'https://' + val + '.digitaloceanspaces.com'
if (this.storage.driver === 'spaces') this.storage.endpoint = 'https://' + val + '.digitaloceanspaces.com'
if (this.storage.driver === 'wasabi')
this.storage.endpoint = 'https://s3.' + val + '.wasabisys.com'
if (this.storage.driver === 'wasabi') this.storage.endpoint = 'https://s3.' + val + '.wasabisys.com'
if (this.storage.driver === 'backblaze')
this.storage.endpoint = 'https://s3.' + val + '.backblazeb2.com'
if (this.storage.driver === 'backblaze') this.storage.endpoint = 'https://s3.' + val + '.backblazeb2.com'
if (this.storage.driver === 'oss')
this.storage.endpoint = 'https://' + val + '.aliyuncs.com'
if (this.storage.driver === 'oss') this.storage.endpoint = 'https://' + val + '.aliyuncs.com'
},
},
computed: {
@@ -195,65 +186,65 @@ export default {
case 'backblaze':
return this.backblazeRegions
break
case 'oss':
return this.ossRegions
break
case 'oss':
return this.ossRegions
break
}
},
},
data() {
return {
isLoading: false,
ossRegions: [
{
label: 'China (Hangzhou)',
value: 'oss-cn-hangzhou',
},
{
label: 'China (Shanghai)',
value: 'oss-cn-shanghai',
},
{
label: 'China (Qingdao)',
value: 'oss-cn-qingdao',
},
{
label: 'China (Beijing)',
value: 'oss-cn-beijing',
},
{
label: 'China (Zhangjiakou)',
value: 'oss-cn-zhangjiakou',
},
{
label: 'China (Hohhot)',
value: 'oss-cn-huhehaote',
},
{
label: 'China (Ulanqab)',
value: 'oss-cn-wulanchabu',
},
{
label: 'China (Shenzhen)',
value: 'oss-cn-shenzhen',
},
{
label: 'China (Heyuan)',
value: 'oss-cn-heyuan',
},
{
label: 'China (Guangzhou)',
value: 'oss-cn-guangzhou',
},
{
label: 'China (Chengdu)',
value: 'oss-cn-chengdu',
},
{
label: 'China (Hong Kong)',
value: 'oss-cn-hongkong',
},
],
ossRegions: [
{
label: 'China (Hangzhou)',
value: 'oss-cn-hangzhou',
},
{
label: 'China (Shanghai)',
value: 'oss-cn-shanghai',
},
{
label: 'China (Qingdao)',
value: 'oss-cn-qingdao',
},
{
label: 'China (Beijing)',
value: 'oss-cn-beijing',
},
{
label: 'China (Zhangjiakou)',
value: 'oss-cn-zhangjiakou',
},
{
label: 'China (Hohhot)',
value: 'oss-cn-huhehaote',
},
{
label: 'China (Ulanqab)',
value: 'oss-cn-wulanchabu',
},
{
label: 'China (Shenzhen)',
value: 'oss-cn-shenzhen',
},
{
label: 'China (Heyuan)',
value: 'oss-cn-heyuan',
},
{
label: 'China (Guangzhou)',
value: 'oss-cn-guangzhou',
},
{
label: 'China (Chengdu)',
value: 'oss-cn-chengdu',
},
{
label: 'China (Hong Kong)',
value: 'oss-cn-hongkong',
},
],
wasabiRegions: [
{
label: 'US East 1 (N. Virginia)',
@@ -411,10 +402,10 @@ export default {
label: 'Backblaze B2 Cloud Storage',
value: 'backblaze',
},
{
label: 'Alibaba Cloud OSS',
value: 'oss',
},
{
label: 'Alibaba Cloud OSS',
value: 'oss',
},
],
encryptionList: [
{
@@ -471,16 +462,15 @@ export default {
username: '',
password: '',
encryption: '',
}
},
}
},
methods: {
async EnvironmentSetupSubmit() {
// Validate fields
const isValid = await this.$refs.environmentSetup.validate();
const isValid = await this.$refs.environmentSetup.validate()
if (!isValid) return;
if (!isValid) return
// Start loading
this.isLoading = true
@@ -491,16 +481,14 @@ export default {
storage: this.storage,
mail: this.mail,
})
.then(response => {
.then((response) => {
// End loading
this.isLoading = false
// Redirect to next step
this.$router.push({name: 'AppSetup'})
this.$router.push({ name: 'AppSetup' })
})
.catch(error => {
.catch((error) => {
// End loading
this.isLoading = false
})
@@ -508,7 +496,7 @@ export default {
},
created() {
this.$scrollTop()
}
},
}
</script>

View File

@@ -1,66 +1,43 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Database was installed successfully. Let's set up application, Make sure you have these informations before continue:"
>
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Database was installed successfully. Let's set up application, Make sure you have these informations before continue:"
>
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<div id="loader" v-if="isLoading">
<Spinner></Spinner>
</div>
<div class="form block-form" v-if="! isLoading">
<div class="form block-form" v-if="!isLoading">
<InfoBox>
<ul v-if="isExtended" style="margin-top: 0" class="information-list">
<li>
1. Stripe API Credentials
</li>
<li>
2. Billing details for Stripe Subscription Service
</li>
<li>
3. You will create your subscription plans
</li>
<li>
4. Email Account Credentials for sending emails to your users
</li>
<li>
5. If you use external storage service, then you will need set your API credentials
</li>
<li>
6. Some general settings for VueFileManager like Google Analytics, logo, favicon and application name
</li>
<li>
7. You will create admin account
</li>
<li>1. Stripe API Credentials</li>
<li>2. Billing details for Stripe Subscription Service</li>
<li>3. You will create your subscription plans</li>
<li>4. Email Account Credentials for sending emails to your users</li>
<li>5. If you use external storage service, then you will need set your API credentials</li>
<li>6. Some general settings for VueFileManager like Google Analytics, logo, favicon and application name</li>
<li>7. You will create admin account</li>
</ul>
<ul v-else style="margin-top: 0" class="information-list">
<li>
1. Email Account Credentials for sending emails to your users
</li>
<li>
2. If you use external storage service, then you will need set your API credentials
</li>
<li>
3. Some general settings for VueFileManager like Google Analytics, logo, favicon and application name
</li>
<li>
4. You will create admin account
</li>
<li>1. Email Account Credentials for sending emails to your users</li>
<li>2. If you use external storage service, then you will need set your API credentials</li>
<li>3. Some general settings for VueFileManager like Google Analytics, logo, favicon and application name</li>
<li>4. You will create admin account</li>
</ul>
</InfoBox>
<router-link v-if="isExtended" :to="{name: 'SubscriptionService'}" tag="div" class="submit-wrapper">
<AuthButton icon="chevron-right" text="I Get It! Let's Set Up Application" :loading="isLoading" :disabled="isLoading"/>
<router-link v-if="isExtended" :to="{ name: 'SubscriptionService' }" tag="div" class="submit-wrapper">
<AuthButton icon="chevron-right" text="I Get It! Let's Set Up Application" :loading="isLoading" :disabled="isLoading" />
</router-link>
<router-link v-if="! isExtended" :to="{name: 'EnvironmentSetup'}" tag="div" class="submit-wrapper">
<AuthButton icon="chevron-right" text="I Get It! Let's Set Up Application" :loading="isLoading" :disabled="isLoading"/>
<router-link v-if="!isExtended" :to="{ name: 'EnvironmentSetup' }" tag="div" class="submit-wrapper">
<AuthButton icon="chevron-right" text="I Get It! Let's Set Up Application" :loading="isLoading" :disabled="isLoading" />
</router-link>
</div>
</AuthContent>
@@ -68,101 +45,98 @@
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import Spinner from "../../components/FilesView/Spinner";
import { SettingsIcon } from 'vue-feather-icons'
import {required} from 'vee-validate/dist/rules'
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import Spinner from '../../components/FilesView/Spinner'
import { SettingsIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'InstallationDisclaimer',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
Spinner,
InfoBox,
Headline,
},
data() {
return {
isLoading: true,
isError: false,
isExtended: undefined
}
},
created() {
// Send request to get verify account
axios
.post('/api/setup/purchase-code', {
purchaseCode: localStorage.getItem('purchase_code'),
})
.then(response => {
this.$scrollTop()
// End loading
this.isLoading = false
if (response.data === 'b6896a44017217c36f4a6fdc56699728') {
this.isExtended = true
localStorage.setItem('license', 'Extended')
} else {
this.isExtended = false
localStorage.setItem('license', 'Regular')
}
})
.catch(error => {
// End loading
this.isLoading = false
if (error.response.status == 400) {
this.$router.push({name: 'PurchaseCode'})
}
})
export default {
name: 'InstallationDisclaimer',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
Spinner,
InfoBox,
Headline,
},
data() {
return {
isLoading: true,
isError: false,
isExtended: undefined,
}
}
},
created() {
// Send request to get verify account
axios
.post('/api/setup/purchase-code', {
purchaseCode: localStorage.getItem('purchase_code'),
})
.then((response) => {
this.$scrollTop()
// End loading
this.isLoading = false
if (response.data === 'b6896a44017217c36f4a6fdc56699728') {
this.isExtended = true
localStorage.setItem('license', 'Extended')
} else {
this.isExtended = false
localStorage.setItem('license', 'Regular')
}
})
.catch((error) => {
// End loading
this.isLoading = false
if (error.response.status == 400) {
this.$router.push({ name: 'PurchaseCode' })
}
})
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
#loader {
position: relative;
margin-top: 80px;
}
#loader {
position: relative;
margin-top: 80px;
}
.information-list {
li {
padding: 8px 0;
@include font-size(17);
font-weight: 600;
.information-list {
li {
padding: 8px 0;
@include font-size(17);
font-weight: 600;
&:first-child {
padding-top: 0;
}
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
}
&:last-child {
padding-bottom: 0;
}
}
}
</style>

View File

@@ -1,139 +1,124 @@
<template>
<AuthContentWrapper ref="auth">
<!--Licence Verify-->
<AuthContent name="licence-verify" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Please set your purchase code before continue to set up your application."
>
<Headline class="container mx-auto max-w-screen-sm" title="Setup Wizard" description="Please set your purchase code before continue to set up your application.">
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<ValidationObserver @submit.prevent="verifyPurchaseCode" ref="verifyPurchaseCode" v-slot="{ invalid }" tag="form" class="form inline-form">
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Purchase Code" rules="required" v-slot="{ errors }">
<input v-model="purchaseCode" placeholder="Paste your purchase code" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="purchaseCode" placeholder="Paste your purchase code" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<AuthButton icon="chevron-right" text="Verify" :loading="isLoading" :disabled="isLoading"/>
<AuthButton icon="chevron-right" text="Verify" :loading="isLoading" :disabled="isLoading" />
</ValidationObserver>
<p class="additional-link">
<a href="https://help.market.envato.com/hc/en-us/articles/202822600-Where-Is-My-Purchase-Code-" target="_blank">
Where I can find purchase code?
</a>
<a class="black-link" href="https://codecanyon.net/item/vue-file-manager-with-laravel-backend/25815986" target="_blank">
Dont have purchase code?
</a>
<a href="https://help.market.envato.com/hc/en-us/articles/202822600-Where-Is-My-Purchase-Code-" target="_blank"> Where I can find purchase code? </a>
<a class="black-link" href="https://codecanyon.net/item/vue-file-manager-with-laravel-backend/25815986" target="_blank"> Dont have purchase code? </a>
</p>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import { SettingsIcon } from 'vue-feather-icons'
import {required} from 'vee-validate/dist/rules'
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'PurchaseCode',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
AuthContent,
AuthButton,
required,
InfoBox,
Headline,
export default {
name: 'PurchaseCode',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
AuthContent,
AuthButton,
required,
InfoBox,
Headline,
},
data() {
return {
isLoading: false,
purchaseCode: '',
}
},
methods: {
async verifyPurchaseCode() {
// Validate fields
const isValid = await this.$refs.verifyPurchaseCode.validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/setup/purchase-code', {
purchaseCode: this.purchaseCode,
})
.then((response) => {
// End loading
this.isLoading = false
localStorage.setItem('purchase_code', this.purchaseCode)
// Redirect to next step
this.$router.push({ name: 'Database' })
})
.catch((error) => {
// End loading
this.isLoading = false
if (error.response.status == 400) {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['Purchase code is invalid.'],
})
} else if (error.response.status == 404) {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['You may have misconfigured the app, please read the readme file and try it again.'],
})
} else {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['Something is wrong. Please try again.'],
})
}
})
},
data() {
return {
isLoading: false,
purchaseCode: '',
}
},
methods: {
async verifyPurchaseCode() {
// Validate fields
const isValid = await this.$refs.verifyPurchaseCode.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/setup/purchase-code', {
purchaseCode: this.purchaseCode,
})
.then(response => {
// End loading
this.isLoading = false
localStorage.setItem('purchase_code', this.purchaseCode)
// Redirect to next step
this.$router.push({name: 'Database'})
})
.catch(error => {
// End loading
this.isLoading = false
if (error.response.status == 400) {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['Purchase code is invalid.']
});
} else if (error.response.status == 404) {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['You may have misconfigured the app, please read the readme file and try it again.']
});
} else {
this.$refs.verifyPurchaseCode.setErrors({
'Purchase Code': ['Something is wrong. Please try again.']
});
}
})
},
},
}
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
.additional-link {
.black-link {
color: $text;
}
}
.auth-form input {
min-width: 380px;
}
.dark {
.additional-link {
.black-link {
color: $text;
}
}
.auth-form input {
min-width: 380px;
}
.dark {
.additional-link {
.black-link {
color: $dark_mode_text_primary;
}
color: $dark_mode_text_primary;
}
}
}
</style>

View File

@@ -1,95 +1,92 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Server Check"
description="At first, we have to check if all modules and setup is ready for running VueFileManager"
>
<Headline
class="container mx-auto max-w-screen-sm"
title="Server Check"
description="At first, we have to check if all modules and setup is ready for running VueFileManager"
>
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<div class="form block-form">
<!--PHP Extension info-->
<!--PHP Extension info-->
<FormLabel>Required PHP Extensions</FormLabel>
<InfoBox>
<p>Those PHP modules are needed for accurate running VueFileManager on your server, please check and install if some is missing.</p>
</InfoBox>
<ul v-if="modules" class="check-list">
<li v-for="(value, module, i) in modules" :key="i" class="check-item">
<div class="content">
<b class="parameter capitalize">{{ module }}</b>
</div>
<div class="status" :class="value ? 'success' : 'danger'">
<check-icon v-if="value" size="16" />
<x-icon v-if="! value" size="16" />
<span class="note">{{ value ? 'Module Installed' : 'Missing Module' }}</span>
</div>
</li>
</ul>
<ul v-if="modules" class="check-list">
<li v-for="(value, module, i) in modules" :key="i" class="check-item">
<div class="content">
<b class="parameter capitalize">{{ module }}</b>
</div>
<div class="status" :class="value ? 'success' : 'danger'">
<check-icon v-if="value" size="16" />
<x-icon v-if="!value" size="16" />
<span class="note">{{ value ? 'Module Installed' : 'Missing Module' }}</span>
</div>
</li>
</ul>
<!--PHP version and ini check-->
<!--PHP version and ini check-->
<FormLabel>PHP Version and php.ini</FormLabel>
<InfoBox>
<p>Those PHP settings are needed for accurate running VueFileManager on your server, please check and tweak in your php.ini if needed.</p>
</InfoBox>
<ul class="check-list">
<li class="check-item">
<div class="content">
<b class="parameter">PHP Version</b>
<small v-if="! phpVersion.acceptable" class="help">You need PHP version at least {{ phpVersion.minimal }}.</small>
</div>
<div class="status" :class="phpVersion.acceptable ? 'success' : 'danger'">
<check-icon v-if="phpVersion.acceptable" size="16" />
<x-icon v-if="! phpVersion.acceptable" size="16" />
<span class="note">{{ phpVersion.current }}</span>
</div>
</li>
<ul class="check-list">
<li class="check-item">
<div class="content">
<b class="parameter">PHP Version</b>
<small v-if="!phpVersion.acceptable" class="help">You need PHP version at least {{ phpVersion.minimal }}.</small>
</div>
<div class="status" :class="phpVersion.acceptable ? 'success' : 'danger'">
<check-icon v-if="phpVersion.acceptable" size="16" />
<x-icon v-if="!phpVersion.acceptable" size="16" />
<span class="note">{{ phpVersion.current }}</span>
</div>
</li>
<li v-for="(values, setting, i) in ini" :key="i" class="check-item">
<div class="content">
<b class="parameter">{{ setting }}</b>
<small v-if="! values.status" class="help">We recommend set this value at least {{ values.minimal }}.</small>
</div>
<div class="status" :class="values.status ? 'success' : 'danger'">
<check-icon v-if="values.status" size="16" />
<x-icon v-if="! values.status" size="16" />
<span class="note">{{ values.current }}{{ setting !== 'max_execution_time' ? 'M' : '' }}</span>
</div>
</li>
</ul>
<li v-for="(values, setting, i) in ini" :key="i" class="check-item">
<div class="content">
<b class="parameter">{{ setting }}</b>
<small v-if="!values.status" class="help">We recommend set this value at least {{ values.minimal }}.</small>
</div>
<div class="status" :class="values.status ? 'success' : 'danger'">
<check-icon v-if="values.status" size="16" />
<x-icon v-if="!values.status" size="16" />
<span class="note">{{ values.current }}{{ setting !== 'max_execution_time' ? 'M' : '' }}</span>
</div>
</li>
</ul>
<!--API check-->
<FormLabel>API</FormLabel>
<!--API check-->
<FormLabel>API</FormLabel>
<InfoBox>
<p>The check, if your domain is set correctly.</p>
</InfoBox>
<ul class="check-list">
<li class="check-item">
<div class="content">
<b class="parameter">API</b>
<small v-if="isCheckedAPI && ! apiRunning" class="help">We detect, your domain root is not set correctly, please check it.</small>
</div>
<div v-if="isCheckedAPI" class="status" :class="apiRunning ? 'success' : 'danger'">
<check-icon v-if="apiRunning" size="16" />
<x-icon v-if="! apiRunning" size="16" />
<span class="note">{{ apiRunning ? 'Working correctly' : "Doesn't work" }}</span>
</div>
<div v-if="! isCheckedAPI" class="status">
<span class="note">Checking your API...</span>
</div>
</li>
</ul>
<ul class="check-list">
<li class="check-item">
<div class="content">
<b class="parameter">API</b>
<small v-if="isCheckedAPI && !apiRunning" class="help">We detect, your domain root is not set correctly, please check it.</small>
</div>
<div v-if="isCheckedAPI" class="status" :class="apiRunning ? 'success' : 'danger'">
<check-icon v-if="apiRunning" size="16" />
<x-icon v-if="!apiRunning" size="16" />
<span class="note">{{ apiRunning ? 'Working correctly' : "Doesn't work" }}</span>
</div>
<div v-if="!isCheckedAPI" class="status">
<span class="note">Checking your API...</span>
</div>
</li>
</ul>
<InfoBox v-if="isError" type="error" style="margin-bottom: 10px">
<p>We can't proceed to the next step because there are unresolved issues. Please solve it at first and next continue.</p>
<p>We can't proceed to the next step because there are unresolved issues. Please solve it at first and next continue.</p>
</InfoBox>
<div class="submit-wrapper">
@@ -101,162 +98,156 @@
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {SettingsIcon} from 'vue-feather-icons'
import {required} from 'vee-validate/dist/rules'
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import {
CheckIcon,
XIcon,
} from 'vue-feather-icons'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
import { CheckIcon, XIcon } from 'vue-feather-icons'
export default {
name: 'StatusCheck',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
CheckIcon,
Headline,
XIcon,
},
computed: {
modules() {
return this.$root.$data.config.statusCheck.modules
},
ini() {
return this.$root.$data.config.statusCheck.ini
},
phpVersion() {
return this.$root.$data.config.statusCheck.php_version
},
isCheckedAPI() {
return typeof this.apiRunning !== 'undefined'
},
},
data() {
return {
isLoading: false,
isError: false,
apiRunning: undefined,
}
},
methods: {
lastCheckBeforeNextPage() {
let modulesCheck = Object
.values(this.$root.$data.config.statusCheck.modules)
.every(module => module === true)
export default {
name: 'StatusCheck',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
CheckIcon,
Headline,
XIcon,
},
computed: {
modules() {
return this.$root.$data.config.statusCheck.modules
},
ini() {
return this.$root.$data.config.statusCheck.ini
},
phpVersion() {
return this.$root.$data.config.statusCheck.php_version
},
isCheckedAPI() {
return typeof this.apiRunning !== 'undefined'
},
},
data() {
return {
isLoading: false,
isError: false,
apiRunning: undefined,
}
},
methods: {
lastCheckBeforeNextPage() {
let modulesCheck = Object.values(this.$root.$data.config.statusCheck.modules).every((module) => module === true)
let iniCheck = Object
.values(this.$root.$data.config.statusCheck.ini)
.every(setting => setting.status === true)
let iniCheck = Object.values(this.$root.$data.config.statusCheck.ini).every((setting) => setting.status === true)
if (modulesCheck && iniCheck && this.apiRunning && this.phpVersion.acceptable) {
this.$router.push({name: 'PurchaseCode'})
} else {
this.isError = true
}
},
pingAPI() {
axios.get('/api/setup/ping')
.then(response => {
if (response.data === 'pong') {
this.apiRunning = true
} else {
this.apiRunning = false
}
})
.catch(() => {
this.apiRunning = false
})
}
},
created() {
this.$scrollTop()
this.pingAPI()
}
}
if (modulesCheck && iniCheck && this.apiRunning && this.phpVersion.acceptable) {
this.$router.push({ name: 'PurchaseCode' })
} else {
this.isError = true
}
},
pingAPI() {
axios
.get('/api/setup/ping')
.then((response) => {
if (response.data === 'pong') {
this.apiRunning = true
} else {
this.apiRunning = false
}
})
.catch(() => {
this.apiRunning = false
})
},
},
created() {
this.$scrollTop()
this.pingAPI()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
.check-list {
display: block;
border-radius: 8px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
padding: 5px 20px;
margin-bottom: 50px;
.check-list {
display: block;
border-radius: 8px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.12);
padding: 5px 20px;
margin-bottom: 50px;
.check-item {
padding: 12px 0;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid $light_mode_border;
.check-item {
padding: 12px 0;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid $light_mode_border;
&:last-child {
border-bottom: none;
}
}
&:last-child {
border-bottom: none;
}
}
.status {
display: flex;
align-items: center;
.status {
display: flex;
align-items: center;
.note {
margin-left: 10px;
@include font-size(12);
font-weight: 600;
color: $text-muted;
}
.note {
margin-left: 10px;
@include font-size(12);
font-weight: 600;
color: $text-muted;
}
&.success {
.note {
color: #00BC7E;
}
&.success {
.note {
color: #00bc7e;
}
polyline {
color: #00BC7E;
}
}
polyline {
color: #00bc7e;
}
}
&.danger {
.note {
color: #fd397a;
}
&.danger {
.note {
color: #fd397a;
}
line {
color: #fd397a;
}
}
}
line {
color: #fd397a;
}
}
}
.parameter {
@include font-size(14);
}
.parameter {
@include font-size(14);
}
.help {
@include font-size(12);
color: $text-muted;
display: block;
}
}
.help {
@include font-size(12);
color: $text-muted;
display: block;
}
}
</style>

View File

@@ -1,20 +1,18 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Set up your database credentials."
>
<Headline class="container mx-auto max-w-screen-sm" title="Setup Wizard" description="Set up your database credentials.">
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<ValidationObserver @submit.prevent="stripeCredentialsSubmit" ref="stripeCredentials" v-slot="{ invalid }" tag="form" class="form block-form">
<InfoBox>
<p>If you dont have stripe account, please <a href="https://dashboard.stripe.com/register" target="_blank">register here</a> and get your Publishable Key, Secret Key and create your webhook.</p>
<p>
If you dont have stripe account, please
<a href="https://dashboard.stripe.com/register" target="_blank">register here</a>
and get your Publishable Key, Secret Key and create your webhook.
</p>
</InfoBox>
<FormLabel>Stripe Setup</FormLabel>
@@ -22,7 +20,7 @@
<div class="block-wrapper">
<label>Stripe Currency:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Currency" rules="required" v-slot="{ errors }">
<SelectInput v-model="stripeCredentials.currency" :options="currencyList" placeholder="Select your Stripe currency" :isError="errors[0]"/>
<SelectInput v-model="stripeCredentials.currency" :options="currencyList" placeholder="Select your Stripe currency" :isError="errors[0]" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -32,7 +30,7 @@
<div class="block-wrapper">
<label>Publishable Key:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Publishable Key" rules="required" v-slot="{ errors }">
<input v-model="stripeCredentials.key" placeholder="Paste your publishable key" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="stripeCredentials.key" placeholder="Paste your publishable key" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -40,22 +38,24 @@
<div class="block-wrapper">
<label>Secret Key:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Secret Key" rules="required" v-slot="{ errors }">
<input v-model="stripeCredentials.secret" placeholder="Paste your secret key" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="stripeCredentials.secret" placeholder="Paste your secret key" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<FormLabel class="mt-70">Stripe Webhook</FormLabel>
<InfoBox>
<p>You have to create webhook endpoint in your Stripe Dashboard. You can find it in <b>Dashboard -> Developers -> Webhooks -> Add Endpoint</b>. In Endpoint URL
please copy and paste url bellow. Make sure, this url is your public domain, not localhost. In events section, please click on <b>receive all events</b>.
That's all.</p>
<p>
You have to create webhook endpoint in your Stripe Dashboard. You can find it in
<b>Dashboard -> Developers -> Webhooks -> Add Endpoint</b>. In Endpoint URL please copy and paste url bellow. Make sure, this url is your public domain, not
localhost. In events section, please click on <b>receive all events</b>. That's all.
</p>
</InfoBox>
<div class="block-wrapper">
<label>Endpoint URL:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Webhook URL" rules="required" v-slot="{ errors }">
<input :value="stripeWebhookEndpoint" type="text" disabled/>
<input :value="stripeWebhookEndpoint" type="text" disabled />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -63,7 +63,7 @@
<div class="block-wrapper">
<label>Webhook Secret:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Webhook Secret" rules="required" v-slot="{ errors }">
<input v-model="stripeCredentials.webhookSecret" placeholder="Type your stripe webhook secret" type="text" :class="{'border-red': errors[0]}"/>
<input v-model="stripeCredentials.webhookSecret" placeholder="Type your stripe webhook secret" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
@@ -73,110 +73,106 @@
</InfoBox>
<div class="submit-wrapper">
<AuthButton icon="chevron-right" :text="submitButtonText" :loading="isLoading" :disabled="isLoading"/>
<AuthButton icon="chevron-right" :text="submitButtonText" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {required} from 'vee-validate/dist/rules'
import {SettingsIcon} from 'vue-feather-icons'
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { required } from 'vee-validate/dist/rules'
import { SettingsIcon } from 'vue-feather-icons'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'StripeCredentials',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
export default {
name: 'StripeCredentials',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
AuthButton,
FormLabel,
required,
InfoBox,
Headline,
},
computed: {
...mapGetters(['config', 'currencyList']),
stripeWebhookEndpoint() {
return this.config.host + '/stripe/webhook'
},
computed: {
...mapGetters(['config', 'currencyList']),
stripeWebhookEndpoint() {
return this.config.host + '/stripe/webhook'
submitButtonText() {
return this.isLoading ? 'Testing Stripe Connection' : 'Save and Set Billings'
},
},
data() {
return {
isLoading: false,
isError: false,
errorMessage: '',
stripeCredentials: {
key: '',
secret: '',
webhookSecret: '',
currency: '',
},
submitButtonText() {
return this.isLoading ? 'Testing Stripe Connection' : 'Save and Set Billings'
}
},
data() {
return {
isLoading: false,
isError: false,
errorMessage: '',
stripeCredentials: {
key: '',
secret: '',
webhookSecret: '',
currency: '',
}
}
},
methods: {
async stripeCredentialsSubmit() {
// Validate fields
const isValid = await this.$refs.stripeCredentials.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/setup/stripe-credentials', this.stripeCredentials)
.then(response => {
// End loading
this.isLoading = false
// Store Stripe Public
this.$store.commit('SET_STRIPE_PUBLIC_KEY', this.stripeCredentials.key)
// Redirect to next step
this.$router.push({name: 'BillingsDetail'})
})
.catch(error => {
if (error.response.status = 401) {
this.isError = true
this.errorMessage = error.response.data.message
}
// End loading
this.isLoading = false
})
},
},
created() {
this.$scrollTop()
}
}
},
methods: {
async stripeCredentialsSubmit() {
// Validate fields
const isValid = await this.$refs.stripeCredentials.validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/setup/stripe-credentials', this.stripeCredentials)
.then((response) => {
// End loading
this.isLoading = false
// Store Stripe Public
this.$store.commit('SET_STRIPE_PUBLIC_KEY', this.stripeCredentials.key)
// Redirect to next step
this.$router.push({ name: 'BillingsDetail' })
})
.catch((error) => {
if ((error.response.status = 401)) {
this.isError = true
this.errorMessage = error.response.data.message
}
// End loading
this.isLoading = false
})
},
},
created() {
this.$scrollTop()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
</style>

View File

@@ -1,19 +1,12 @@
<template>
<AuthContentWrapper ref="auth">
<!--Database Credentials-->
<AuthContent name="database-credentials" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="Set up plans for your customers."
>
<Headline class="container mx-auto max-w-screen-sm" title="Setup Wizard" description="Set up plans for your customers.">
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<ValidationObserver @submit.prevent="subscriptionPlansSubmit" ref="subscriptionPlans" v-slot="{ invalid }"
tag="form" class="form block-form">
<ValidationObserver @submit.prevent="subscriptionPlansSubmit" ref="subscriptionPlans" v-slot="{ invalid }" tag="form" class="form block-form">
<FormLabel>Create your plans</FormLabel>
<InfoBox>
<p>Your plans will be <b>sorted automatically</b> in ascent order by plan price. All plans is automatically created as monthly plans.</p>
@@ -25,56 +18,53 @@
<b class="duplicator-title">{{ index }}. Plan</b>
<div class="block-wrapper">
<label>Name:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Name"
rules="required" v-slot="{ errors }">
<input v-model="plan.attributes.name" placeholder="Type your plan name"
type="text" :class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Name" rules="required" v-slot="{ errors }">
<input v-model="plan.attributes.name" placeholder="Type your plan name" type="text" :class="{ 'border-red': errors[0] }" />
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Description (optional):</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Description"
v-slot="{ errors }">
<textarea v-model="plan.attributes.description"
placeholder="Type your plan description" :class="{'border-red': errors[0]}"></textarea>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Description" v-slot="{ errors }">
<textarea v-model="plan.attributes.description" placeholder="Type your plan description" :class="{ 'border-red': errors[0] }"></textarea>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Price:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Price"
rules="required" v-slot="{ errors }">
<input v-model="plan.attributes.price" placeholder="Type your plan price" type="number"
step="0.01" min="1" max="999999999999"
:class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Price" rules="required" v-slot="{ errors }">
<input
v-model="plan.attributes.price"
placeholder="Type your plan price"
type="number"
step="0.01"
min="1"
max="999999999999"
:class="{ 'border-red': errors[0] }"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
<div class="block-wrapper">
<label>Storage Capacity:</label>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Storage Capacity"
rules="required" v-slot="{ errors }">
<input v-model="plan.attributes.capacity"
min="1"
max="999999999"
placeholder="Type storage capacity in GB"
type="number"
:class="{'border-red': errors[0]}"/>
<ValidationProvider tag="div" mode="passive" class="input-wrapper" name="Storage Capacity" rules="required" v-slot="{ errors }">
<input
v-model="plan.attributes.capacity"
min="1"
max="999999999"
placeholder="Type storage capacity in GB"
type="number"
:class="{ 'border-red': errors[0] }"
/>
<span class="error-message" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
</div>
</div>
<ButtonBase
@click.native="addRow"
class="duplicator-add-button"
button-style="theme-solid"
>Add New Plan
</ButtonBase>
<ButtonBase @click.native="addRow" class="duplicator-add-button" button-style="theme-solid">Add New Plan </ButtonBase>
</div>
<InfoBox v-if="isError" type="error" style="margin-top: 40px">
@@ -82,8 +72,7 @@
</InfoBox>
<div class="submit-wrapper">
<AuthButton icon="chevron-right" :text="submitButtonText" :loading="isLoading"
:disabled="isLoading"/>
<AuthButton icon="chevron-right" :text="submitButtonText" :loading="isLoading" :disabled="isLoading" />
</div>
</ValidationObserver>
</AuthContent>
@@ -91,120 +80,116 @@
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import ButtonBase from "../../components/FilesView/ButtonBase";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import {SettingsIcon} from 'vue-feather-icons'
import {required} from 'vee-validate/dist/rules'
import {XIcon} from 'vue-feather-icons'
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import ButtonBase from '../../components/FilesView/ButtonBase'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import { XIcon } from 'vue-feather-icons'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'subscriptionPlans',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
ButtonBase,
AuthButton,
FormLabel,
required,
InfoBox,
XIcon,
Headline,
export default {
name: 'subscriptionPlans',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
SettingsIcon,
SelectInput,
AuthContent,
ButtonBase,
AuthButton,
FormLabel,
required,
InfoBox,
XIcon,
Headline,
},
computed: {
submitButtonText() {
return this.isLoading ? 'Creating Subscription Stripe Plans' : 'Save and Go Next'
},
computed: {
submitButtonText() {
return this.isLoading ? 'Creating Subscription Stripe Plans' : 'Save and Go Next'
}
},
data() {
return {
isLoading: false,
isError: false,
errorMessage: '',
subscriptionPlans: [
{
id: 1,
type: 'plan',
attributes: {
name: '',
description: '',
price: '',
capacity: '',
}
}
]
}
},
methods: {
async subscriptionPlansSubmit() {
// Validate fields
const isValid = await this.$refs.subscriptionPlans.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
this.isError = false
// Send request to get verify account
axios
.post('/api/setup/stripe-plans', {
plans: this.subscriptionPlans
})
.then(() => {
// Redirect to next step
this.$router.push({name: 'EnvironmentSetup'})
})
.catch(error => {
if (error.response.status = 500) {
this.isError = true
this.errorMessage = error.response.data.message
}
})
.finally(() => {
this.isLoading = false
})
},
addRow() {
this.subscriptionPlans.push({
id: Math.floor(Math.random() * 10000000),
type: 'plans',
},
data() {
return {
isLoading: false,
isError: false,
errorMessage: '',
subscriptionPlans: [
{
id: 1,
type: 'plan',
attributes: {
name: '',
description: '',
price: '',
capacity: '',
},
},
],
}
},
methods: {
async subscriptionPlansSubmit() {
// Validate fields
const isValid = await this.$refs.subscriptionPlans.validate()
if (!isValid) return
// Start loading
this.isLoading = true
this.isError = false
// Send request to get verify account
axios
.post('/api/setup/stripe-plans', {
plans: this.subscriptionPlans,
})
.then(() => {
// Redirect to next step
this.$router.push({ name: 'EnvironmentSetup' })
})
.catch((error) => {
if ((error.response.status = 500)) {
this.isError = true
this.errorMessage = error.response.data.message
}
})
},
removeRow(plan) {
this.subscriptionPlans = this.subscriptionPlans.filter(item => item.id !== plan.id)
},
.finally(() => {
this.isLoading = false
})
},
created() {
this.$scrollTop()
}
}
addRow() {
this.subscriptionPlans.push({
id: Math.floor(Math.random() * 10000000),
type: 'plans',
attributes: {
name: '',
description: '',
price: '',
capacity: '',
},
})
},
removeRow(plan) {
this.subscriptionPlans = this.subscriptionPlans.filter((item) => item.id !== plan.id)
},
},
created() {
this.$scrollTop()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/forms';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
</style>

View File

@@ -1,27 +1,25 @@
<template>
<AuthContentWrapper ref="auth">
<!--Licence Verify-->
<AuthContent name="subscription-service" :visible="true">
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="You can charge users for storage space by monthly billing plans. Please, select your charging service or skip this step if you don't want charge users:"
>
<Headline
class="container mx-auto max-w-screen-sm"
title="Setup Wizard"
description="You can charge users for storage space by monthly billing plans. Please, select your charging service or skip this step if you don't want charge users:"
>
<settings-icon size="40" class="title-icon text-theme mx-auto" />
</Headline>
</Headline>
<div class="services">
<router-link :to="{name: 'StripeCredentials'}" tag="div" class="service-card">
<img src="/assets/icons/stripe-service.svg" alt="Stripe" class="service-logo">
<router-link :to="{ name: 'StripeCredentials' }" tag="div" class="service-card">
<img src="/assets/icons/stripe-service.svg" alt="Stripe" class="service-logo" />
<div class="service-content">
<b class="service-title">Charging with Stripe</b>
<p class="service-description">You can create custom storage plans and charge your users with monthly subscription.</p>
</div>
<router-link :to="{name: 'StripeCredentials'}" class="service-link">
<router-link :to="{ name: 'StripeCredentials' }" class="service-link">
<span>Set Up Billing and Plans With Stripe</span>
<chevron-right-icon size="22" class="icon"></chevron-right-icon>
</router-link>
@@ -29,8 +27,8 @@
</div>
<p class="additional-link">
<router-link :to="{name: 'EnvironmentSetup'}">
<AuthButton class="skip-subscription-setup" icon="chevron-right" text="I will set up Stripe later" />
<router-link :to="{ name: 'EnvironmentSetup' }">
<AuthButton class="skip-subscription-setup" icon="chevron-right" text="I will set up Stripe later" />
</router-link>
</p>
</AuthContent>
@@ -38,114 +36,114 @@
</template>
<script>
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import { SettingsIcon, ChevronRightIcon } from 'vue-feather-icons'
import {required} from 'vee-validate/dist/rules'
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import { SettingsIcon, ChevronRightIcon } from 'vue-feather-icons'
import { required } from 'vee-validate/dist/rules'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'SubscriptionService',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
ChevronRightIcon,
SettingsIcon,
AuthContent,
AuthButton,
required,
Headline,
},
data() {
return {
isLoading: false,
}
},
created() {
this.$scrollTop()
export default {
name: 'SubscriptionService',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
ChevronRightIcon,
SettingsIcon,
AuthContent,
AuthButton,
required,
Headline,
},
data() {
return {
isLoading: false,
}
}
},
created() {
this.$scrollTop()
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
@import '../../../sass/vuefilemanager/auth';
@import '../../../sass/vuefilemanager/setup_wizard';
.services {
margin: 0 auto;
.services {
margin: 0 auto;
}
.service-card {
text-align: left;
box-shadow: 0 5px 30px 5px rgba(#3d4efd, 0.25);
border-radius: 20px;
max-width: 415px;
display: inline-block;
padding: 30px;
background: rgb(58, 75, 255);
background: linear-gradient(135deg, rgba(58, 75, 255, 1) 0%, rgba(103, 114, 229, 1) 100%);
@include transition(200ms);
&:hover {
cursor: pointer;
box-shadow: 0 8px 35px 5px rgba(#3d4efd, 0.4);
@include transform(scale(1.02));
}
.service-card {
text-align: left;
box-shadow: 0 5px 30px 5px rgba(#3D4EFD, 0.25);
border-radius: 20px;
max-width: 415px;
display: inline-block;
padding: 30px;
background: rgb(58,75,255);
background: linear-gradient(135deg, rgba(58,75,255,1) 0%, rgba(103,114,229,1) 100%);
@include transition(200ms);
.service-logo {
margin-bottom: 30px;
display: block;
}
&:hover {
cursor: pointer;
box-shadow: 0 8px 35px 5px rgba(#3D4EFD, 0.4);
@include transform(scale(1.02));
}
.service-content {
margin-bottom: 65px;
.service-logo {
margin-bottom: 30px;
.service-title {
@include font-size(18);
font-weight: 700;
color: white;
margin-bottom: 5px;
display: block;
}
.service-content {
margin-bottom: 65px;
.service-title {
@include font-size(18);
font-weight: 700;
color: white;
margin-bottom: 5px;
display: block;
}
.service-description {
@include font-size(16);
font-weight: 600;
color: white;
opacity: 0.8;
}
}
.service-link {
display: flex;
align-items: center;
.icon {
margin-left: 5px;
polyline {
stroke: white
}
}
span {
@include font-size(16);
font-weight: 700;
color: white;
}
.service-description {
@include font-size(16);
font-weight: 600;
color: white;
opacity: 0.8;
}
}
.skip-subscription-setup {
border: none !important;
}
.service-link {
display: flex;
align-items: center;
.auth-form input {
min-width: 380px;
.icon {
margin-left: 5px;
polyline {
stroke: white;
}
}
span {
@include font-size(16);
font-weight: 700;
color: white;
}
}
}
.skip-subscription-setup {
border: none !important;
}
.auth-form input {
min-width: 380px;
}
</style>

View File

@@ -1,12 +1,11 @@
<template>
<div class="sm:flex md:h-screen md:overflow-hidden">
<!--Loading Spinner-->
<Spinner v-if="isLoading" />
<!--File preview window-->
<FilePreview />
<Spotlight />
<Spotlight />
<!--Popups-->
<ProcessingPopup />
@@ -24,125 +23,125 @@
<DragUI />
<Alert />
<NavigationSharePanel v-if="sharedDetail && $router.currentRoute.name === 'Public'"/>
<NavigationSharePanel v-if="sharedDetail && $router.currentRoute.name === 'Public'" />
<div
@contextmenu.prevent.capture="contextMenu($event, undefined)"
class="md:grid md:content-start sm:flex-grow sm:px-3.5 transition-transform duration-300"
:class="{'transform scale-97 origin-center': isScaledDown}"
>
<DesktopToolbar />
<div
@contextmenu.prevent.capture="contextMenu($event, undefined)"
class="transition-transform duration-300 sm:flex-grow sm:px-3.5 md:grid md:content-start"
:class="{ 'origin-center scale-97 transform': isScaledDown }"
>
<DesktopToolbar />
<MobileToolbar />
<MobileToolbar />
<!--File list & info sidebar-->
<div class="flex space-x-6 md:overflow-hidden md:h-full">
<!--File list & info sidebar-->
<div class="flex space-x-6 md:h-full md:overflow-hidden">
<router-view
id="file-view"
:class="{
'w-full md:w-4/6 2xl:w-5/6': isVisibleSidebar,
'w-full': !isVisibleSidebar,
}"
class="relative"
:key="$route.fullPath"
/>
<router-view
id="file-view"
:class="{'2xl:w-5/6 md:w-4/6 w-full': isVisibleSidebar, 'w-full': ! isVisibleSidebar}"
class="relative"
:key="$route.fullPath"
/>
<InfoSidebar
v-if="isVisibleSidebar"
class="2xl:w-72 w-2/6 overflow-y-auto overflow-x-hidden h-screen md:block hidden"
/>
</div>
</div>
<InfoSidebar v-if="isVisibleSidebar" class="hidden h-screen w-2/6 overflow-y-auto overflow-x-hidden md:block 2xl:w-72" />
</div>
</div>
</div>
</template>
<script>
import MobileToolbar from "../components/FilesView/MobileToolbar";
import InfoSidebar from "../components/FilesView/InfoSidebar";
import MobileMultiSelectToolbar from "../components/FilesView/MobileMultiSelectToolbar";
import FileSortingMobile from "../components/FilesView/FileSortingMobile";
import CreateFolderPopup from "../components/Others/CreateFolderPopup";
import ProcessingPopup from "../components/FilesView/ProcessingPopup";
import NavigationSharePanel from "./FileView/Components/NavigationSharePanel"
import RenameItemPopup from "../components/Others/RenameItemPopup";
import FilePreview from "../components/FilePreview/FilePreview";
import MoveItemPopup from "../components/Others/MoveItemPopup";
import DesktopToolbar from "../components/FilesView/DesktopToolbar"
import Spinner from "../components/FilesView/Spinner";
import Vignette from "../components/Others/Vignette";
import DragUI from "../components/FilesView/DragUI";
import Alert from "../components/FilesView/Alert";
import Spotlight from "../components/Spotlight/Spotlight"
import {events} from '../bus'
import {mapGetters} from 'vuex'
import MobileToolbar from '../components/FilesView/MobileToolbar'
import InfoSidebar from '../components/FilesView/InfoSidebar'
import MobileMultiSelectToolbar from '../components/FilesView/MobileMultiSelectToolbar'
import FileSortingMobile from '../components/FilesView/FileSortingMobile'
import CreateFolderPopup from '../components/Others/CreateFolderPopup'
import ProcessingPopup from '../components/FilesView/ProcessingPopup'
import NavigationSharePanel from './FileView/Components/NavigationSharePanel'
import RenameItemPopup from '../components/Others/RenameItemPopup'
import FilePreview from '../components/FilePreview/FilePreview'
import MoveItemPopup from '../components/Others/MoveItemPopup'
import DesktopToolbar from '../components/FilesView/DesktopToolbar'
import Spinner from '../components/FilesView/Spinner'
import Vignette from '../components/Others/Vignette'
import DragUI from '../components/FilesView/DragUI'
import Alert from '../components/FilesView/Alert'
import Spotlight from '../components/Spotlight/Spotlight'
import { events } from '../bus'
import { mapGetters } from 'vuex'
export default {
name: 'Shared',
components: {
MobileToolbar,
InfoSidebar,
NavigationSharePanel,
MobileMultiSelectToolbar,
CreateFolderPopup,
FileSortingMobile,
ProcessingPopup,
RenameItemPopup,
DesktopToolbar,
MoveItemPopup,
FilePreview,
Spotlight,
Vignette,
Spinner,
DragUI,
Alert,
},
computed: {
...mapGetters([
'isVisibleSidebar',
'sharedDetail',
'config',
])
},
data() {
return {
isLoading: true,
isScaledDown: false
export default {
name: 'Shared',
components: {
MobileToolbar,
InfoSidebar,
NavigationSharePanel,
MobileMultiSelectToolbar,
CreateFolderPopup,
FileSortingMobile,
ProcessingPopup,
RenameItemPopup,
DesktopToolbar,
MoveItemPopup,
FilePreview,
Spotlight,
Vignette,
Spinner,
DragUI,
Alert,
},
computed: {
...mapGetters(['isVisibleSidebar', 'sharedDetail', 'config']),
},
data() {
return {
isLoading: true,
isScaledDown: false,
}
},
methods: {
spotlightListener(e) {
if (e.key === 'k' && e.metaKey) {
events.$emit('spotlight:show')
}
},
methods: {
spotlightListener(e) {
if (e.key === 'k' && e.metaKey) {
events.$emit('spotlight:show');
}
},
contextMenu(event, item) {
events.$emit('context-menu:show', event, item)
},
},
mounted() {
events.$on('mobile-menu:show', () => this.isScaledDown = true)
contextMenu(event, item) {
events.$emit('context-menu:show', event, item)
},
},
mounted() {
events.$on('mobile-menu:show', () => (this.isScaledDown = true))
this.$store.dispatch('getShareDetail', this.$route.params.token)
.then(response => {
this.isLoading = false
this.$store.dispatch('getShareDetail', this.$route.params.token).then((response) => {
this.isLoading = false
let type = response.data.data.attributes.type
let routeName = this.$router.currentRoute.name
let isProtected = response.data.data.attributes.protected
let type = response.data.data.attributes.type
let routeName = this.$router.currentRoute.name
let isProtected = response.data.data.attributes.protected
// Show public file browser
if (type === 'folder' && !isProtected && routeName !== 'Public') {
this.$router.replace({name: 'Public', params: {token: this.$route.params.token, id: response.data.data.attributes.item_id}})
}
// Show public single file
if (type !== 'folder' && !isProtected && routeName !== 'SharedSingleFile') {
this.$router.push({name: 'SharedSingleFile'})
}
// Show authentication page
if (isProtected && routeName !== 'SharedAuthentication') {
this.$router.push({name: 'SharedAuthentication'})
}
// Show public file browser
if (type === 'folder' && !isProtected && routeName !== 'Public') {
this.$router.replace({
name: 'Public',
params: {
token: this.$route.params.token,
id: response.data.data.attributes.item_id,
},
})
}
}
}
// Show public single file
if (type !== 'folder' && !isProtected && routeName !== 'SharedSingleFile') {
this.$router.push({ name: 'SharedSingleFile' })
}
// Show authentication page
if (isProtected && routeName !== 'SharedAuthentication') {
this.$router.push({ name: 'SharedAuthentication' })
}
})
},
}
</script>

View File

@@ -1,90 +1,100 @@
<template>
<AuthContentWrapper>
<AuthContent name="password" :visible="true">
<Headline
:title="$t('page_shared.title')"
:description="$t('page_shared.subtitle')"
/>
<ValidationObserver @submit.prevent="authenticateProtected" ref="authenticateProtected" v-slot="{ invalid }" tag="form" class="md:flex items-start md:space-x-4 md:space-y-0 space-y-4 mb-12">
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Password" rules="required" v-slot="{ errors }">
<input v-model="password" :placeholder="$t('page_shared.placeholder_pass')" type="password" class="font-bold px-5 py-3.5 dark:bg-2x-dark-foreground bg-light-background w-full rounded-lg focus-border-theme appearance-none border border-transparent" :class="{'border-red': errors[0]}" />
<span class="text-red-600 text-xs text-left" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<AuthButton class="md:w-min w-full justify-center" icon="chevron-right" :text="$t('page_shared.submit')" :loading="isLoading" :disabled="isLoading" />
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
<AuthContentWrapper>
<AuthContent name="password" :visible="true">
<Headline :title="$t('page_shared.title')" :description="$t('page_shared.subtitle')" />
<ValidationObserver
@submit.prevent="authenticateProtected"
ref="authenticateProtected"
v-slot="{ invalid }"
tag="form"
class="mb-12 items-start space-y-4 md:flex md:space-x-4 md:space-y-0"
>
<ValidationProvider tag="div" mode="passive" class="w-full text-left" name="Password" rules="required" v-slot="{ errors }">
<input
v-model="password"
:placeholder="$t('page_shared.placeholder_pass')"
type="password"
class="focus-border-theme w-full appearance-none rounded-lg border border-transparent bg-light-background px-5 py-3.5 font-bold dark:bg-2x-dark-foreground"
:class="{ 'border-red': errors[0] }"
/>
<span class="text-left text-xs text-red-600" v-if="errors[0]">{{ errors[0] }}</span>
</ValidationProvider>
<AuthButton class="w-full justify-center md:w-min" icon="chevron-right" :text="$t('page_shared.submit')" :loading="isLoading" :disabled="isLoading" />
</ValidationObserver>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper"
import AuthContent from "../../components/Auth/AuthContent";
import AuthButton from "../../components/Auth/AuthButton";
import Headline from "../Auth/Headline";
import {mapGetters} from "vuex";
import axios from "axios";
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import AuthContent from '../../components/Auth/AuthContent'
import AuthButton from '../../components/Auth/AuthButton'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'SharedAuthentication',
components: {
ValidationObserver,
ValidationProvider,
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
export default {
name: 'SharedAuthentication',
components: {
ValidationObserver,
ValidationProvider,
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
password: '',
isLoading: false,
}
},
methods: {
async authenticateProtected() {
// Validate fields
const isValid = await this.$refs.authenticateProtected.validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/browse/authenticate/' + this.$route.params.token, {
password: this.password,
})
.then((response) => {
// Show file browser
if (response.data.data.attributes.type === 'folder' && this.$router.currentRoute.name !== 'Public') {
this.$router.replace({
name: 'Public',
params: {
token: this.$route.params.token,
id: response.data.data.attributes.item_id,
},
})
}
// Show single file
if (response.data.data.attributes.type !== 'folder' && this.$router.currentRoute.name !== 'SharedSingleFile') {
this.$router.push({ name: 'SharedSingleFile' })
}
})
.catch((error) => {
if (error.response.status === 401)
this.$refs.authenticateProtected.setErrors({
Password: [error.response.data],
})
})
.finally(() => {
this.isLoading = false
})
},
computed: {
...mapGetters([
'config',
]),
},
data() {
return {
password: '',
isLoading: false,
}
},
methods: {
async authenticateProtected() {
// Validate fields
const isValid = await this.$refs.authenticateProtected.validate();
if (!isValid) return;
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/browse/authenticate/' + this.$route.params.token, {
password: this.password
})
.then(response => {
// Show file browser
if (response.data.data.attributes.type === 'folder' && this.$router.currentRoute.name !== 'Public') {
this.$router.replace({name: 'Public', params: {token: this.$route.params.token, id: response.data.data.attributes.item_id}})
}
// Show single file
if (response.data.data.attributes.type !== 'folder' && this.$router.currentRoute.name !== 'SharedSingleFile') {
this.$router.push({name: 'SharedSingleFile'})
}
})
.catch(error => {
if (error.response.status === 401)
this.$refs.authenticateProtected.setErrors({
'Password': [error.response.data]
});
})
.finally(() => {
this.isLoading = false
})
},
},
}
</script>
},
}
</script>

View File

@@ -1,12 +1,7 @@
<template>
<div class="h-screen flex justify-center items-center">
<div class="flex h-screen items-center justify-center">
<div>
<ItemGrid
v-if="sharedFile"
:entry="sharedFile"
:highlight="true"
:mobile-handler="true"
/>
<ItemGrid v-if="sharedFile" :entry="sharedFile" :highlight="true" :mobile-handler="true" />
<ButtonBase @click.native="download" button-style="theme">
{{ $t('page_shared.download_file') }}
@@ -16,35 +11,32 @@
</template>
<script>
import ButtonBase from "../../components/FilesView/ButtonBase";
import ItemGrid from "../../components/FilesView/ItemGrid"
import {mapGetters} from "vuex"
import ButtonBase from '../../components/FilesView/ButtonBase'
import ItemGrid from '../../components/FilesView/ItemGrid'
import { mapGetters } from 'vuex'
export default {
name: 'SharedSingleItem',
components: {
ButtonBase,
ItemGrid,
export default {
name: 'SharedSingleItem',
components: {
ButtonBase,
ItemGrid,
},
computed: {
...mapGetters(['sharedDetail', 'sharedFile']),
},
methods: {
download() {
this.$downloadFile(this.sharedFile.data.attributes.file_url, this.sharedFile.data.attributes.name + '.' + this.sharedFile.data.attributes.mimetype)
},
computed: {
...mapGetters([
'sharedDetail',
'sharedFile',
]),
},
methods: {
download() {
this.$downloadFile(this.sharedFile.data.attributes.file_url, this.sharedFile.data.attributes.name + '.' + this.sharedFile.data.attributes.mimetype)
},
},
mounted() {
if (!this.sharedDetail) {
this.$store.dispatch('getShareDetail', this.$route.params.token).then(() => {
this.$store.dispatch('getSingleFile')
})
} else {
},
mounted() {
if (!this.sharedDetail) {
this.$store.dispatch('getShareDetail', this.$route.params.token).then(() => {
this.$store.dispatch('getSingleFile')
}
})
} else {
this.$store.dispatch('getSingleFile')
}
}
</script>
},
}
</script>

View File

@@ -1,173 +1,167 @@
<template>
<AuthContentWrapper ref="auth">
<!--Invitation info-->
<AuthContent name="invitation" :visible="false">
<Headline
v-if="invitation"
:title="$t('Invitation To Join Team Folder')"
:description="$t('{name} invite you to join with his team into shared team folder', {name: invitation.data.relationships.inviter.data.attributes.name})"
>
<div class="text-center mb-10 w-24 mx-auto relative">
<VueFolderTeamIcon class="inline-block w-28" />
<MemberAvatar
:member="invitation.data.relationships.inviter"
class="absolute -bottom-2.5 -right-6"
:is-border="true"
:size="38"
/>
</div>
</Headline>
v-if="invitation"
:title="$t('Invitation To Join Team Folder')"
:description="
$t('{name} invite you to join with his team into shared team folder', {
name: invitation.data.relationships.inviter.data.attributes.name,
})
"
>
<div class="relative mx-auto mb-10 w-24 text-center">
<VueFolderTeamIcon class="inline-block w-28" />
<MemberAvatar :member="invitation.data.relationships.inviter" class="absolute -bottom-2.5 -right-6" :is-border="true" :size="38" />
</div>
</Headline>
<AuthButton
@click.native="acceptInvitation"
class="md:w-min w-full justify-center mb-12"
icon="chevron-right"
:text="$t('Accept Invitation')"
:loading="isLoading"
:disabled="isLoading"
/>
<AuthButton
@click.native="acceptInvitation"
class="mb-12 w-full justify-center md:w-min"
icon="chevron-right"
:text="$t('Accept Invitation')"
:loading="isLoading"
:disabled="isLoading"
/>
<div class="block">
Or
<b @click="declineInvitation" class="text-theme font-bold cursor-pointer">
Or
<b @click="declineInvitation" class="text-theme cursor-pointer font-bold">
{{ $t('decline') }}
</b>
your invitation.
your invitation.
</div>
</AuthContent>
<!--Accepted invitation screen-->
<AuthContent v-if="invitation" name="accepted" :visible="false">
<Headline
:title="$t('You are successfully joined')"
:description="$t('You can now proceed to your account and participate in team folder')"
/>
<Headline :title="$t('You are successfully joined')" :description="$t('You can now proceed to your account and participate in team folder')" />
<router-link replace v-if="! config.isAuthenticated" :to="{name: 'SignIn'}">
<AuthButton class="md:w-min w-full justify-center mb-12" icon="chevron-right" :text="$t('Proceed to your account')"/>
<router-link replace v-if="!config.isAuthenticated" :to="{ name: 'SignIn' }">
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Proceed to your account')" />
</router-link>
<router-link replace v-if="config.isAuthenticated" :to="{name: 'SharedWithMe', params: {id: invitation.data.attributes.parent_id}}">
<AuthButton class="md:w-min w-full justify-center mb-12" icon="chevron-right" :text="$t('Go to Team Folder')"/>
<router-link
replace
v-if="config.isAuthenticated"
:to="{
name: 'SharedWithMe',
params: { id: invitation.data.attributes.parent_id },
}"
>
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Go to Team Folder')" />
</router-link>
</AuthContent>
<!--Denied invitation screen-->
<AuthContent name="denied" :visible="false">
<Headline :title="$t('You are successfully denied invitation')" :description="$t('You can now proceed to your account')" />
<Headline
:title="$t('You are successfully denied invitation')"
:description="$t('You can now proceed to your account')"
/>
<router-link :to="{name: 'SignIn'}">
<AuthButton class="md:w-min w-full justify-center mb-12" icon="chevron-right" :text="$t('Proceed to your account')"/>
<router-link :to="{ name: 'SignIn' }">
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Proceed to your account')" />
</router-link>
</AuthContent>
<!--Used or Expired invitation screen-->
<AuthContent name="expired" :visible="false">
<Headline :title="$t('Your invitation has been used')" :description="$t('We are sorry but this invitation was used previously')" />
<Headline
:title="$t('Your invitation has been used')"
:description="$t('We are sorry but this invitation was used previously')"
/>
<router-link replace v-if="! config.isAuthenticated" :to="{name: 'SignIn'}">
<AuthButton class="md:w-min w-full justify-center mb-12" icon="chevron-right" :text="$t('Log In')"/>
<router-link replace v-if="!config.isAuthenticated" :to="{ name: 'SignIn' }">
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Log In')" />
</router-link>
<router-link replace v-if="config.isAuthenticated" :to="{name: 'SharedWithMe'}">
<AuthButton class="md:w-min w-full justify-center mb-12" icon="chevron-right" :text="$t('Go to your shared folders')"/>
<router-link replace v-if="config.isAuthenticated" :to="{ name: 'SharedWithMe' }">
<AuthButton class="mb-12 w-full justify-center md:w-min" icon="chevron-right" :text="$t('Go to your shared folders')" />
</router-link>
</AuthContent>
</AuthContentWrapper>
</template>
<script>
import {ValidationObserver, ValidationProvider} from 'vee-validate/dist/vee-validate.full'
import VueFolderTeamIcon from "../../components/FilesView/Icons/VueFolderTeamIcon"
import AuthContentWrapper from "../../components/Auth/AuthContentWrapper";
import AuthContent from "../../components/Auth/AuthContent";
import MemberAvatar from "../../components/FilesView/MemberAvatar"
import AuthButton from "../../components/Auth/AuthButton";
import Spinner from "../../components/FilesView/Spinner";
import Headline from "../Auth/Headline"
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationObserver, ValidationProvider } from 'vee-validate/dist/vee-validate.full'
import VueFolderTeamIcon from '../../components/FilesView/Icons/VueFolderTeamIcon'
import AuthContentWrapper from '../../components/Auth/AuthContentWrapper'
import AuthContent from '../../components/Auth/AuthContent'
import MemberAvatar from '../../components/FilesView/MemberAvatar'
import AuthButton from '../../components/Auth/AuthButton'
import Spinner from '../../components/FilesView/Spinner'
import Headline from '../Auth/Headline'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'Invitation',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
VueFolderTeamIcon,
MemberAvatar,
AuthContent,
AuthButton,
Headline,
Spinner,
},
computed: {
...mapGetters([
'config'
]),
},
data() {
return {
isLoading: false,
invitation: undefined,
isUsed: false,
}
},
methods: {
acceptInvitation() {
this.isLoading = true
axios.put(`/api/teams/invitations/${this.$router.currentRoute.params.id}`)
.then(() => {
this.goToAuthPage('accepted')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => this.isLoading = false)
},
declineInvitation() {
this.isLoading = true
axios.delete(`/api/teams/invitations/${this.$router.currentRoute.params.id}`)
.then(() => {
this.goToAuthPage('denied')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => this.isLoading = false)
},
goToAuthPage(slug) {
this.$refs.auth.$children.forEach(page => {
// Hide current step
page.isVisible = page.$props.name === slug;
})
},
},
created() {
axios.get(`/api/teams/invitations/${this.$router.currentRoute.params.id}`)
.then(response => {
this.invitation = response.data
this.goToAuthPage('invitation')
})
.catch(error => {
if (error.response.status === 410) {
this.goToAuthPage('expired')
} else {
this.$isSomethingWrong()
}
})
export default {
name: 'Invitation',
components: {
AuthContentWrapper,
ValidationProvider,
ValidationObserver,
VueFolderTeamIcon,
MemberAvatar,
AuthContent,
AuthButton,
Headline,
Spinner,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: false,
invitation: undefined,
isUsed: false,
}
}
},
methods: {
acceptInvitation() {
this.isLoading = true
axios
.put(`/api/teams/invitations/${this.$router.currentRoute.params.id}`)
.then(() => {
this.goToAuthPage('accepted')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => (this.isLoading = false))
},
declineInvitation() {
this.isLoading = true
axios
.delete(`/api/teams/invitations/${this.$router.currentRoute.params.id}`)
.then(() => {
this.goToAuthPage('denied')
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => (this.isLoading = false))
},
goToAuthPage(slug) {
this.$refs.auth.$children.forEach((page) => {
// Hide current step
page.isVisible = page.$props.name === slug
})
},
},
created() {
axios
.get(`/api/teams/invitations/${this.$router.currentRoute.params.id}`)
.then((response) => {
this.invitation = response.data
this.goToAuthPage('invitation')
})
.catch((error) => {
if (error.response.status === 410) {
this.goToAuthPage('expired')
} else {
this.$isSomethingWrong()
}
})
},
}
</script>

View File

@@ -1,14 +1,11 @@
<template>
<AuthContentWrapper ref="auth">
<AuthContent :visible="true">
<Headline
:title="$t('Temporary Unavailable')"
:description="$t('Unfortunately, this shared link is temporary unavailable. Please try it later.')"
/>
<Headline :title="$t('Temporary Unavailable')" :description="$t('Unfortunately, this shared link is temporary unavailable. Please try it later.')" />
<span class="additional-link">{{ $t('page_registration.have_an_account') }}
<router-link :to="{name: 'SignIn'}">
<span class="additional-link"
>{{ $t('page_registration.have_an_account') }}
<router-link :to="{ name: 'SignIn' }">
{{ $t('page_forgotten_password.password_remember_button') }}
</router-link>
</span>
@@ -17,22 +14,22 @@
</template>
<script>
import AuthContentWrapper from "../components/Auth/AuthContentWrapper";
import AuthContent from "../components/Auth/AuthContent";
import AuthButton from "../components/Auth/AuthButton";
import Headline from "./Auth/Headline"
import AuthContentWrapper from '../components/Auth/AuthContentWrapper'
import AuthContent from '../components/Auth/AuthContent'
import AuthButton from '../components/Auth/AuthButton'
import Headline from './Auth/Headline'
export default {
name: 'NotFound',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
export default {
name: 'NotFound',
components: {
AuthContentWrapper,
AuthContent,
AuthButton,
Headline,
},
}
</script>
<style scoped lang="scss">
@import '../../sass/vuefilemanager/auth';
@import '../../sass/vuefilemanager/auth';
</style>

View File

@@ -1,26 +1,26 @@
<template>
<PageTab>
<!-- Metered subscription components -->
<div v-if="config.subscriptionType === 'metered'">
<!--Failed Payments-->
<UserFailedPayments />
<!-- Metered subscription components -->
<div v-if="config.subscriptionType === 'metered'">
<!--Failed Payments-->
<UserFailedPayments />
<!--
<!--
...
Charge user and increase his balance
...
Available for PayPal, Paystack
...
-->
<UserBalance/>
<UserBalance />
<!--Usage Estimates-->
<UserUsageEstimates />
<!--Usage Estimates-->
<UserUsageEstimates />
<!--Billing Alert-->
<UserBillingAlerts />
<!--Billing Alert-->
<UserBillingAlerts />
<!--
<!--
...
We can store user credit card and charge for fixed billing and metered billing
This component handle storing and showing payment card UI
@@ -28,21 +28,21 @@
Handle only Stripe
...
-->
<UserStoredPaymentMethods />
<UserStoredPaymentMethods />
<!-- Show all users transactions -->
<UserTransactionsForMeteredBilling />
</div>
<!-- Show all users transactions -->
<UserTransactionsForMeteredBilling />
</div>
<!-- Fixed subscription components -->
<div v-if="config.subscriptionType === 'fixed'">
<!-- Empty subscription -->
<UserEmptySubscription />
<!-- Fixed subscription components -->
<div v-if="config.subscriptionType === 'fixed'">
<!-- Empty subscription -->
<UserEmptySubscription />
<!-- Subscription Detail -->
<UserFixedSubscriptionDetail />
<!-- Subscription Detail -->
<UserFixedSubscriptionDetail />
<!--
<!--
...
We can store user credit card and charge for fixed billing and metered billing
This component handle storing and showing payment card UI
@@ -50,9 +50,9 @@
Handle only Stripe
...
-->
<UserStoredPaymentMethods />
<UserStoredPaymentMethods />
<!--
<!--
...
Paystack or PayPal need generate external resources to update payment method.
This component will handle all user cases
@@ -60,53 +60,50 @@
Handle only Paypal, Paystack
...
-->
<UserUpdatePaymentMethodsExternally />
<UserUpdatePaymentMethodsExternally />
<!-- Component for cancel or upgrade subscription plan -->
<UserEditSubscription />
<!-- Show all users transactions -->
<UserTransactionsForFixedBilling />
</div>
<!-- Component for cancel or upgrade subscription plan -->
<UserEditSubscription />
<!-- Show all users transactions -->
<UserTransactionsForFixedBilling />
</div>
</PageTab>
</template>
<script>
import UserUpdatePaymentMethodsExternally from "../../components/Subscription/UserUpdatePaymentMethodsExternally"
import UserTransactionsForMeteredBilling from "../../components/Subscription/UserTransactionsForMeteredBilling"
import UserTransactionsForFixedBilling from "../../components/Subscription/UserTransactionsForFixedBilling"
import UserFixedSubscriptionDetail from "../../components/Subscription/UserFixedSubscriptionDetail"
import UserStoredPaymentMethods from "../../components/Subscription/UserStoredPaymentMethods"
import UserEmptySubscription from "../../components/Subscription/UserEmptySubscription"
import UserEditSubscription from "../../components/Subscription/UserEditSubscription"
import UserFailedPayments from "../../components/Subscription/UserFailedPayments"
import UserUsageEstimates from "../../components/Subscription/UserUsageEstimates"
import UserBillingAlerts from "../../components/Subscription/UserBillingAlerts"
import PageTab from "../../components/Others/Layout/PageTab";
import UserBalance from "../../components/Subscription/UserBalance"
import {mapGetters} from 'vuex'
import UserUpdatePaymentMethodsExternally from '../../components/Subscription/UserUpdatePaymentMethodsExternally'
import UserTransactionsForMeteredBilling from '../../components/Subscription/UserTransactionsForMeteredBilling'
import UserTransactionsForFixedBilling from '../../components/Subscription/UserTransactionsForFixedBilling'
import UserFixedSubscriptionDetail from '../../components/Subscription/UserFixedSubscriptionDetail'
import UserStoredPaymentMethods from '../../components/Subscription/UserStoredPaymentMethods'
import UserEmptySubscription from '../../components/Subscription/UserEmptySubscription'
import UserEditSubscription from '../../components/Subscription/UserEditSubscription'
import UserFailedPayments from '../../components/Subscription/UserFailedPayments'
import UserUsageEstimates from '../../components/Subscription/UserUsageEstimates'
import UserBillingAlerts from '../../components/Subscription/UserBillingAlerts'
import PageTab from '../../components/Others/Layout/PageTab'
import UserBalance from '../../components/Subscription/UserBalance'
import { mapGetters } from 'vuex'
export default {
name: 'Billing',
components: {
UserUpdatePaymentMethodsExternally,
UserTransactionsForMeteredBilling,
UserTransactionsForFixedBilling,
UserFixedSubscriptionDetail,
UserStoredPaymentMethods,
UserEmptySubscription,
UserEditSubscription,
UserFailedPayments,
UserUsageEstimates,
UserBillingAlerts,
UserBalance,
PageTab,
},
computed: {
...mapGetters([
'config',
]),
},
}
export default {
name: 'Billing',
components: {
UserUpdatePaymentMethodsExternally,
UserTransactionsForMeteredBilling,
UserTransactionsForFixedBilling,
UserFixedSubscriptionDetail,
UserStoredPaymentMethods,
UserEmptySubscription,
UserEditSubscription,
UserFailedPayments,
UserUsageEstimates,
UserBillingAlerts,
UserBalance,
PageTab,
},
computed: {
...mapGetters(['config']),
},
}
</script>

View File

@@ -1,291 +1,317 @@
<template>
<div v-if="user">
<!--2fa authentication-->
<div v-if="! user.data.attributes.socialite_account" class="card shadow-card">
<!--2fa authentication-->
<div v-if="!user.data.attributes.socialite_account" class="card shadow-card">
<FormLabel icon="smartphone">
{{ $t('2fa.settings.title') }}
</FormLabel>
<AppInputSwitch :title="$t('popup_2fa.switch_title')" :description="$t('popup_2fa.switch_info')" :is-last="! user.data.attributes.two_factor_authentication">
<AppInputSwitch :title="$t('popup_2fa.switch_title')" :description="$t('popup_2fa.switch_info')" :is-last="!user.data.attributes.two_factor_authentication">
<SwitchInput v-model="user.data.attributes.two_factor_authentication" class="switch" :state="user.data.attributes.two_factor_authentication" />
</AppInputSwitch>
<AppInputButton v-if="user && user.data.attributes.two_factor_authentication" :title="$t('popup_2fa.codes_title')" :description="$t('popup_2fa.codes_info')" :is-last="true">
<AppInputButton
v-if="user && user.data.attributes.two_factor_authentication"
:title="$t('popup_2fa.codes_title')"
:description="$t('popup_2fa.codes_info')"
:is-last="true"
>
<ButtonBase class="w-full" button-style="secondary" @click.native="showRecoveryCodes">
{{ $t('popup_2fa.codes_button') }}
</ButtonBase>
</AppInputButton>
</div>
<!--Get personal api keys-->
<!--Get personal api keys-->
<div class="card shadow-card">
<FormLabel icon="key">
{{ $t('personal_token.section_title') }}
</FormLabel>
<InfoBox v-if="tokens.length === 0">
<p>{{ $t("personal_token.section_description") }}</p>
<p>{{ $t('personal_token.section_description') }}</p>
</InfoBox>
<div class="mb-5">
<div v-if="tokens.length > 0" class="flex items-center justify-between py-2 border-b dark:border-opacity-5 border-light border-dashed" v-for="token in tokens" :key="token.id">
<div class="leading-none">
<b class="text-sm font-bold leading-none">
{{ token.name }}
</b>
<time class="text-xs text-gray-500 pt-2 leading-none block">
{{ $t('last_used') }}: {{ token.last_used_at ? formatDate(token.last_used_at) : $t('never') }}
</time>
</div>
<div class="text-right">
<div @click="confirmDeleteToken(token)" class="cursor-pointer 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">
<Trash2Icon size="15" class="opacity-75" />
</div>
</div>
</div>
</div>
<div class="mb-5">
<div
v-if="tokens.length > 0"
class="flex items-center justify-between border-b border-dashed border-light py-2 dark:border-opacity-5"
v-for="token in tokens"
:key="token.id"
>
<div class="leading-none">
<b class="text-sm font-bold leading-none">
{{ token.name }}
</b>
<time class="block pt-2 text-xs leading-none text-gray-500">
{{ $t('last_used') }}:
{{ token.last_used_at ? formatDate(token.last_used_at) : $t('never') }}
</time>
</div>
<div class="text-right">
<div
@click="confirmDeleteToken(token)"
class="flex h-8 w-8 cursor-pointer 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" />
</div>
</div>
</div>
</div>
<ButtonBase @click.native="openCreateTokenPopup" type="submit" button-style="theme" class="sm:w-auto w-full">
<ButtonBase @click.native="openCreateTokenPopup" type="submit" button-style="theme" class="w-full sm:w-auto">
{{ $t('personal_token.create_token') }}
</ButtonBase>
</div>
<!--Change password-->
<ValidationObserver ref="password" @submit.prevent="resetPassword" v-slot="{ invalid }" tag="form" class="card shadow-card">
<FormLabel>
{{ $t('user_password.title') }}
</FormLabel>
<!--Change password-->
<ValidationObserver ref="password" @submit.prevent="resetPassword" v-slot="{ invalid }" tag="form" class="card shadow-card">
<FormLabel>
{{ $t('user_password.title') }}
</FormLabel>
<ValidationProvider tag="div" mode="passive" name="Current Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('Current Password')" :error="errors[0]">
<input v-model="passwordForm.current" :placeholder="$t('Current password')" type="password" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}" />
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Current Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('Current Password')" :error="errors[0]">
<input
v-model="passwordForm.current"
:placeholder="$t('Current password')"
type="password"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="New Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('page_create_password.label_new_pass')" :error="errors[0]">
<input v-model="passwordForm.password" :placeholder="$t('page_create_password.label_new_pass')" type="password" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}" />
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="New Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('page_create_password.label_new_pass')" :error="errors[0]">
<input
v-model="passwordForm.password"
:placeholder="$t('page_create_password.label_new_pass')"
type="password"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Confirm Your Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('page_create_password.label_confirm_pass')" :error="errors[0]">
<input v-model="passwordForm.password_confirmation" :placeholder="$t('page_create_password.label_confirm_pass')" type="password" class="focus-border-theme input-dark" :class="{'border-red': errors[0]}" />
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Confirm Your Password" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('page_create_password.label_confirm_pass')" :error="errors[0]">
<input
v-model="passwordForm.password_confirmation"
:placeholder="$t('page_create_password.label_confirm_pass')"
type="password"
class="focus-border-theme input-dark"
:class="{ 'border-red': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ButtonBase type="submit" button-style="theme" class="sm:w-auto w-full">
{{ $t('profile.store_pass') }}
</ButtonBase>
</ValidationObserver>
<ButtonBase type="submit" button-style="theme" class="w-full sm:w-auto">
{{ $t('profile.store_pass') }}
</ButtonBase>
</ValidationObserver>
</div>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import SwitchInput from "../../components/Others/Forms/SwitchInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import ButtonBase from "../../components/FilesView/ButtonBase";
import InfoBox from "../../components/Others/Forms/InfoBox";
import AppInputSwitch from "../../components/Admin/AppInputSwitch"
import AppInputButton from "../../components/Admin/AppInputButton"
import AppInputText from "../../components/Admin/AppInputText"
import {required} from 'vee-validate/dist/rules'
import {XIcon, Trash2Icon} from 'vue-feather-icons'
import {events} from '../../bus'
import {mapGetters} from 'vuex'
import axios from 'axios'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import SwitchInput from '../../components/Others/Forms/SwitchInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import ButtonBase from '../../components/FilesView/ButtonBase'
import InfoBox from '../../components/Others/Forms/InfoBox'
import AppInputSwitch from '../../components/Admin/AppInputSwitch'
import AppInputButton from '../../components/Admin/AppInputButton'
import AppInputText from '../../components/Admin/AppInputText'
import { required } from 'vee-validate/dist/rules'
import { XIcon, Trash2Icon } from 'vue-feather-icons'
import { events } from '../../bus'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'Password',
components: {
ValidationProvider,
ValidationObserver,
AppInputButton,
AppInputSwitch,
AppInputText,
SwitchInput,
ButtonBase,
FormLabel,
Trash2Icon,
required,
InfoBox,
XIcon,
},
computed: {
...mapGetters([
'user',
])
},
watch: {
'user.data.attributes.two_factor_authentication': function (val) {
val ? this.enable2faPopup() : this.disable2faPopup()
}
},
data() {
return {
passwordForm: {
current: undefined,
password: undefined,
password_confirmation: undefined,
},
isLoading: false,
tokens: [],
}
},
methods: {
async resetPassword() {
export default {
name: 'Password',
components: {
ValidationProvider,
ValidationObserver,
AppInputButton,
AppInputSwitch,
AppInputText,
SwitchInput,
ButtonBase,
FormLabel,
Trash2Icon,
required,
InfoBox,
XIcon,
},
computed: {
...mapGetters(['user']),
},
watch: {
'user.data.attributes.two_factor_authentication': function (val) {
val ? this.enable2faPopup() : this.disable2faPopup()
},
},
data() {
return {
passwordForm: {
current: undefined,
password: undefined,
password_confirmation: undefined,
},
isLoading: false,
tokens: [],
}
},
methods: {
async resetPassword() {
// Validate fields
const isValid = await this.$refs.password.validate()
// Validate fields
const isValid = await this.$refs.password.validate();
if (!isValid) return
if (!isValid) return;
// Send request to get user reset link
axios
.post(this.$store.getters.api + '/user/password', this.passwordForm)
.then(() => {
// Reset inputs
this.passwordForm = {
current: undefined,
password: undefined,
password_confirmation: undefined,
}
// Send request to get user reset link
axios
.post(this.$store.getters.api + '/user/password', this.passwordForm)
.then(() => {
// Reset errors
this.$refs.password.reset()
// Reset inputs
this.passwordForm = {
current: undefined,
password: undefined,
password_confirmation: undefined,
}
// Show success message
events.$emit('success:open', {
title: this.$t('popup_pass_changed.title'),
message: this.$t('popup_pass_changed.message'),
})
})
.catch((error) => {
if (error.response.status === 422) {
if (error.response.data.errors['password']) {
this.$refs.password.setErrors({
'New Password': error.response.data.errors['password'],
})
}
// Reset errors
this.$refs.password.reset()
if (error.response.data.errors['current']) {
this.$refs.password.setErrors({
'Current Password': error.response.data.errors['current'],
})
}
}
})
},
getPersonalAccessTokens() {
axios
.get('/api/user/tokens')
.then((response) => {
this.tokens = response.data
})
.catch(() => this.$isSomethingWrong())
},
showRecoveryCodes() {
events.$emit('popup:open', {
name: 'confirm-password',
options: {
action: 'get-recovery-codes',
},
})
},
enable2faPopup() {
events.$emit('popup:open', {
name: 'confirm-password',
options: {
action: 'two-factor-qr-setup',
},
})
},
disable2faPopup() {
events.$emit('popup:open', {
name: 'confirm-password',
options: {
action: 'disable-2fa',
},
})
},
confirmDeleteToken(token) {
events.$emit('confirm:open', {
title: this.$t('popup_delete_personal_token.title'),
message: this.$t('popup_delete_personal_token.description'),
action: {
id: token.id,
operation: 'delete-personal-access-token',
},
})
},
openCreateTokenPopup() {
events.$emit('popup:open', { name: 'create-personal-token' })
},
formatDate(date) {
return new Intl.DateTimeFormat('en').format(new Date(date))
},
},
created() {
this.getPersonalAccessTokens()
// Show success message
events.$emit('success:open', {
title: this.$t('popup_pass_changed.title'),
message: this.$t('popup_pass_changed.message'),
})
})
.catch(error => {
// Actions confirmed
events.$on('action:confirmed', (data) => {
// Delete personal token
if (data.operation === 'delete-personal-access-token') {
axios
.delete(`/api/user/tokens/${data.id}`)
.then(() => {
this.tokens = this.tokens.filter((tokenItem) => tokenItem.id !== data.id)
if (error.response.status === 422) {
events.$emit('toaster', {
type: 'success',
message: this.$t('personal_token.token_deleted'),
})
})
.catch(() => this.$isSomethingWrong())
}
})
if (error.response.data.errors['password']) {
this.$refs.password.setErrors({
'New Password': error.response.data.errors['password']
});
}
// Password confirmed
events.$on('password:confirmed', (args) => {
// Get recovery tokens
if (args.options.action === 'get-recovery-codes') {
events.$emit('popup:open', {
name: 'two-factor-recovery-codes',
})
}
if (error.response.data.errors['current']) {
this.$refs.password.setErrors({
'Current Password': error.response.data.errors['current']
});
}
}
})
},
getPersonalAccessTokens() {
axios.get('/api/user/tokens')
.then(response => {
this.tokens = response.data
})
.catch(() => this.$isSomethingWrong())
},
showRecoveryCodes() {
events.$emit('popup:open', {
name: 'confirm-password',
options: {
action: 'get-recovery-codes',
}
})
},
enable2faPopup() {
events.$emit('popup:open', {
name: 'confirm-password',
options: {
action: 'two-factor-qr-setup',
}
})
},
disable2faPopup() {
events.$emit('popup:open', {
name: 'confirm-password',
options: {
action: 'disable-2fa',
}
})
},
confirmDeleteToken(token) {
events.$emit('confirm:open', {
title: this.$t('popup_delete_personal_token.title'),
message: this.$t('popup_delete_personal_token.description'),
action: {
id: token.id,
operation: 'delete-personal-access-token'
}
})
},
openCreateTokenPopup() {
events.$emit('popup:open', {name: 'create-personal-token'})
},
formatDate(date) {
return new Intl.DateTimeFormat('en').format(new Date(date))
},
},
created() {
this.getPersonalAccessTokens()
// Get 2fa qr code
if (args.options.action === 'two-factor-qr-setup') {
events.$emit('popup:open', { name: 'two-factor-qr-setup' })
}
// Actions confirmed
events.$on('action:confirmed', data => {
// Get 2fa qr code
if (args.options.action === 'disable-2fa') {
axios
.delete('/user/two-factor-authentication')
.then(() => {
this.$store.commit('CHANGE_TWO_FACTOR_AUTHENTICATION_STATE', false)
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
this.$closePopup()
// Delete personal token
if (data.operation === 'delete-personal-access-token') {
axios.delete(`/api/user/tokens/${data.id}`)
.then(() => {
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_disabled'),
})
})
}
})
this.tokens = this.tokens.filter(tokenItem => tokenItem.id !== data.id)
events.$emit('toaster', {
type: 'success',
message: this.$t('personal_token.token_deleted'),
})
})
.catch(() => this.$isSomethingWrong())
}
})
// Password confirmed
events.$on('password:confirmed', args => {
// Get recovery tokens
if (args.options.action === 'get-recovery-codes') {
events.$emit('popup:open', {name: 'two-factor-recovery-codes'})
}
// Get 2fa qr code
if (args.options.action === 'two-factor-qr-setup') {
events.$emit('popup:open', {name: 'two-factor-qr-setup'})
}
// Get 2fa qr code
if (args.options.action === 'disable-2fa') {
axios
.delete('/user/two-factor-authentication')
.then(() => {
this.$store.commit('CHANGE_TWO_FACTOR_AUTHENTICATION_STATE', false)
})
.catch(() => {
this.$isSomethingWrong()
})
.finally(() => {
this.$closePopup()
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_2fa.toaster_disabled'),
})
})
}
})
events.$on('reload-personal-access-tokens', () => this.getPersonalAccessTokens())
},
destroyed() {
events.$off('action:confirmed')
},
}
events.$on('reload-personal-access-tokens', () => this.getPersonalAccessTokens())
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -2,210 +2,223 @@
<div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Account Settings') }}
</FormLabel>
{{ $t('Account Settings') }}
</FormLabel>
<div class="md:flex justify-items md:space-x-4">
<AppInputText :title="$t('First Name')" class="w-full">
<input
@keyup="updateFirstName"
v-model="user.data.relationships.settings.data.attributes.first_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('Last Name')" class="w-full">
<input
@keyup="updateLastName"
v-model="user.data.relationships.settings.data.attributes.last_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<div class="justify-items md:flex md:space-x-4">
<AppInputText :title="$t('First Name')" class="w-full">
<input
@keyup="updateFirstName"
v-model="user.data.relationships.settings.data.attributes.first_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('Last Name')" class="w-full">
<input
@keyup="updateLastName"
v-model="user.data.relationships.settings.data.attributes.last_name"
:placeholder="$t('page_registration.placeholder_name')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<AppInputText :title="$t('GMT')" :is-last="true">
<AppInputText :title="$t('GMT')" :is-last="true">
<SelectInput
@input="$updateText('/user/settings', 'timezone', user.data.relationships.settings.data.attributes.timezone)"
v-model="user.data.relationships.settings.data.attributes.timezone"
:default="user.data.relationships.settings.data.attributes.timezone"
:options="timezones"
:placeholder="$t('user_settings.timezone_plac')" />
@input="$updateText('/user/settings', 'timezone', user.data.relationships.settings.data.attributes.timezone)"
v-model="user.data.relationships.settings.data.attributes.timezone"
:default="user.data.relationships.settings.data.attributes.timezone"
:options="timezones"
:placeholder="$t('user_settings.timezone_plac')"
/>
</AppInputText>
</div>
<div class="card shadow-card">
<FormLabel>
{{ $t('Appearance') }}
</FormLabel>
<div class="card shadow-card">
<FormLabel>
{{ $t('Appearance') }}
</FormLabel>
<AppInputText :title="$t('Theme Mode')" :description="$t('Set your theme mode on dark, light or based on your system settings.')" :is-last="! $isApple()">
<div class="md:flex items-center md:space-x-6 md:space-x-4 md:space-y-0 space-y-4">
<div
v-for="(theme, i) in themeSetup"
:key="i"
:title="theme.title"
@click="$store.dispatch('toggleThemeMode', theme.type)"
class="w-full rounded-xl shadow-lg overflow-hidden cursor-pointer border-3"
:class="{'border-theme': config.defaultThemeMode === theme.type, 'border-transparent': config.defaultThemeMode !== theme.type}"
>
<img :src="theme.image" :alt="theme.type">
</div>
</div>
<AppInputText :title="$t('Theme Mode')" :description="$t('Set your theme mode on dark, light or based on your system settings.')" :is-last="!$isApple()">
<div class="items-center space-y-4 md:flex md:space-x-6 md:space-x-4 md:space-y-0">
<div
v-for="(theme, i) in themeSetup"
:key="i"
:title="theme.title"
@click="$store.dispatch('toggleThemeMode', theme.type)"
class="w-full cursor-pointer overflow-hidden rounded-xl border-3 shadow-lg"
:class="{
'border-theme': config.defaultThemeMode === theme.type,
'border-transparent': config.defaultThemeMode !== theme.type,
}"
>
<img :src="theme.image" :alt="theme.type" />
</div>
</div>
</AppInputText>
<AppInputText v-if="$isApple()" :title="$t('Default Emojis')" :description="$t('Set your default emojis for your folder custom icons. You can set Twemoji or default Apple emojis.')" :is-last="true">
<div class="md:flex items-center md:space-x-6 md:space-x-4 md:space-y-0 space-y-4">
<div
v-for="(emoji, i) in emojiSetup"
:key="i"
:title="emoji.title"
@click="$store.dispatch('toggleEmojiType', emoji.type)"
class="w-full rounded-xl shadow-lg overflow-hidden cursor-pointer border-3"
:class="{'border-theme': currentEmojis === emoji.type, 'border-transparent': currentEmojis !== emoji.type}"
>
<img :src="isDarkMode ? emoji.image.dark : emoji.image.light" :alt="emoji.type">
</div>
</div>
</AppInputText>
</div>
<AppInputText
v-if="$isApple()"
:title="$t('Default Emojis')"
:description="$t('Set your default emojis for your folder custom icons. You can set Twemoji or default Apple emojis.')"
:is-last="true"
>
<div class="items-center space-y-4 md:flex md:space-x-6 md:space-x-4 md:space-y-0">
<div
v-for="(emoji, i) in emojiSetup"
:key="i"
:title="emoji.title"
@click="$store.dispatch('toggleEmojiType', emoji.type)"
class="w-full cursor-pointer overflow-hidden rounded-xl border-3 shadow-lg"
:class="{
'border-theme': currentEmojis === emoji.type,
'border-transparent': currentEmojis !== emoji.type,
}"
>
<img :src="isDarkMode ? emoji.image.dark : emoji.image.light" :alt="emoji.type" />
</div>
</div>
</AppInputText>
</div>
<div class="card shadow-card">
<FormLabel>
{{ $t('user_settings.title_billing') }}
</FormLabel>
<AppInputText :title="$t('user_settings.address')">
<input @keyup="$updateText('/user/settings', 'address', user.data.relationships.settings.data.attributes.address)"
v-model="user.data.relationships.settings.data.attributes.address"
:placeholder="$t('user_settings.address_plac')"
type="text"
class="focus-border-theme input-dark"
/>
<input
@keyup="$updateText('/user/settings', 'address', user.data.relationships.settings.data.attributes.address)"
v-model="user.data.relationships.settings.data.attributes.address"
:placeholder="$t('user_settings.address_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<div class="flex space-x-4">
<AppInputText :title="$t('user_settings.city')" class="w-full">
<input @keyup="$updateText('/user/settings', 'city', user.data.relationships.settings.data.attributes.city)"
v-model="user.data.relationships.settings.data.attributes.city"
:placeholder="$t('user_settings.city_plac')"
type="text"
class="focus-border-theme input-dark"
/>
<input
@keyup="$updateText('/user/settings', 'city', user.data.relationships.settings.data.attributes.city)"
v-model="user.data.relationships.settings.data.attributes.city"
:placeholder="$t('user_settings.city_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('user_settings.postal_code')" class="w-full">
<input @keyup="$updateText('/user/settings', 'postal_code', user.data.relationships.settings.data.attributes.postal_code)"
v-model="user.data.relationships.settings.data.attributes.postal_code"
:placeholder="$t('user_settings.postal_code_plac')"
type="text"
class="focus-border-theme input-dark"
/>
<input
@keyup="$updateText('/user/settings', 'postal_code', user.data.relationships.settings.data.attributes.postal_code)"
v-model="user.data.relationships.settings.data.attributes.postal_code"
:placeholder="$t('user_settings.postal_code_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<AppInputText :title="$t('user_settings.country')">
<SelectInput @input="$updateText('/user/settings', 'country', user.data.relationships.settings.data.attributes.country)"
v-model="user.data.relationships.settings.data.attributes.country"
:default="user.data.relationships.settings.data.attributes.country"
:options="countries"
:placeholder="$t('user_settings.country_plac')"
/>
<SelectInput
@input="$updateText('/user/settings', 'country', user.data.relationships.settings.data.attributes.country)"
v-model="user.data.relationships.settings.data.attributes.country"
:default="user.data.relationships.settings.data.attributes.country"
:options="countries"
:placeholder="$t('user_settings.country_plac')"
/>
</AppInputText>
<AppInputText :title="$t('user_settings.state')" :description="$t('State, county, province, or region.')">
<input @keyup="$updateText('/user/settings', 'state', user.data.relationships.settings.data.attributes.state)"
v-model="user.data.relationships.settings.data.attributes.state"
:placeholder="$t('user_settings.state_plac')"
type="text"
class="focus-border-theme input-dark"
/>
<input
@keyup="$updateText('/user/settings', 'state', user.data.relationships.settings.data.attributes.state)"
v-model="user.data.relationships.settings.data.attributes.state"
:placeholder="$t('user_settings.state_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('user_settings.phone_number')" :is-last="true">
<input @keyup="$updateText('/user/settings', 'phone_number', user.data.relationships.settings.data.attributes.phone_number)"
v-model="user.data.relationships.settings.data.attributes.phone_number"
:placeholder="$t('user_settings.phone_number_plac')"
type="text"
class="focus-border-theme input-dark"
/>
<input
@keyup="$updateText('/user/settings', 'phone_number', user.data.relationships.settings.data.attributes.phone_number)"
v-model="user.data.relationships.settings.data.attributes.phone_number"
:placeholder="$t('user_settings.phone_number_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
</div>
</template>
<script>
import AppInputText from "../../components/Admin/AppInputText";
import SelectInput from "../../components/Others/Forms/SelectInput";
import FormLabel from "../../components/Others/Forms/FormLabel";
import {required} from 'vee-validate/dist/rules'
import {mapGetters} from 'vuex'
import AppInputText from '../../components/Admin/AppInputText'
import SelectInput from '../../components/Others/Forms/SelectInput'
import FormLabel from '../../components/Others/Forms/FormLabel'
import { required } from 'vee-validate/dist/rules'
import { mapGetters } from 'vuex'
export default {
name: 'Settings',
components: {
AppInputText,
SelectInput,
FormLabel,
required,
},
computed: {
...mapGetters([
'isDarkMode',
'countries',
'timezones',
'config',
]),
currentEmojis() {
return this.config.defaultEmoji
},
},
data() {
return {
user: this.$store.getters.user,
isLoading: false,
themeSetup: [
{
title: this.$t('Light mode'),
type: 'light',
image: '/assets/setup/light-mode.jpg',
},
{
title: this.$t('Dark mode'),
type: 'dark',
image: '/assets/setup/dark-mode.jpg',
},
{
title: this.$t('Based on system settings'),
type: 'system',
image: '/assets/setup/system-mode.jpg',
},
],
emojiSetup: [
{
title: 'Twemoji',
type: 'twemoji',
image: {
dark: '/assets/setup/twemoji-preview-dark.jpg',
light: '/assets/setup/twemoji-preview.jpg',
}
},
{
title: 'Applemoji',
type: 'applemoji',
image: {
dark: '/assets/setup/applemoji-preview-dark.jpg',
light: '/assets/setup/applemoji-preview.jpg',
}
},
]
}
},
methods: {
updateFirstName() {
this.$store.commit('UPDATE_FIRST_NAME', this.user.data.relationships.settings.data.attributes.first_name)
this.$updateText('/user/settings', 'first_name', this.user.data.relationships.settings.data.attributes.first_name)
},
updateLastName() {
this.$store.commit('UPDATE_LAST_NAME', this.user.data.relationships.settings.data.attributes.last_name)
this.$updateText('/user/settings', 'last_name', this.user.data.relationships.settings.data.attributes.last_name)
}
},
}
export default {
name: 'Settings',
components: {
AppInputText,
SelectInput,
FormLabel,
required,
},
computed: {
...mapGetters(['isDarkMode', 'countries', 'timezones', 'config']),
currentEmojis() {
return this.config.defaultEmoji
},
},
data() {
return {
user: this.$store.getters.user,
isLoading: false,
themeSetup: [
{
title: this.$t('Light mode'),
type: 'light',
image: '/assets/setup/light-mode.jpg',
},
{
title: this.$t('Dark mode'),
type: 'dark',
image: '/assets/setup/dark-mode.jpg',
},
{
title: this.$t('Based on system settings'),
type: 'system',
image: '/assets/setup/system-mode.jpg',
},
],
emojiSetup: [
{
title: 'Twemoji',
type: 'twemoji',
image: {
dark: '/assets/setup/twemoji-preview-dark.jpg',
light: '/assets/setup/twemoji-preview.jpg',
},
},
{
title: 'Applemoji',
type: 'applemoji',
image: {
dark: '/assets/setup/applemoji-preview-dark.jpg',
light: '/assets/setup/applemoji-preview.jpg',
},
},
],
}
},
methods: {
updateFirstName() {
this.$store.commit('UPDATE_FIRST_NAME', this.user.data.relationships.settings.data.attributes.first_name)
this.$updateText('/user/settings', 'first_name', this.user.data.relationships.settings.data.attributes.first_name)
},
updateLastName() {
this.$store.commit('UPDATE_LAST_NAME', this.user.data.relationships.settings.data.attributes.last_name)
this.$updateText('/user/settings', 'last_name', this.user.data.relationships.settings.data.attributes.last_name)
},
},
}
</script>

View File

@@ -1,91 +1,88 @@
<template>
<PageTab>
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
<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">
{{ storage.data.attributes.used }}
</b>
<b class="-mt-3 block text-2xl font-extrabold sm:text-3xl">
{{ storage.data.attributes.used }}
</b>
<b v-if="config.subscriptionType === 'fixed' || (config.subscriptionType === 'none' && config.storageLimit)" class="mt-0.5 block text-sm text-gray-400">
{{ $t('Total of') }} {{ storage.data.attributes.capacity }} {{ $t('Used') }}
</b>
<b v-if="config.subscriptionType === 'fixed' || (config.subscriptionType === 'none' && config.storageLimit)" class="mt-0.5 block text-sm text-gray-400">
{{ $t('Total of') }} {{ storage.data.attributes.capacity }}
{{ $t('Used') }}
</b>
<ProgressLine :data="distribution" class="mt-5" />
</div>
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
<ProgressLine :data="distribution" class="mt-5" />
</div>
<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>
<div v-if="distribution" class="card shadow-card">
<FormLabel icon="hard-drive">
<BarChart :data="storage.data.meta.traffic.chart.upload" color="#FFBD2D" />
</div>
<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>
</PageTab>
</template>
<script>
import ProgressLine from "../../components/Admin/ProgressLine";
import FormLabel from "../../components/Others/Forms/FormLabel";
import PageTab from "../../components/Others/Layout/PageTab";
import axios from 'axios'
import BarChart from "../../components/UI/BarChart";
import {mapGetters} from "vuex";
import ProgressLine from '../../components/Admin/ProgressLine'
import FormLabel from '../../components/Others/Forms/FormLabel'
import PageTab from '../../components/Others/Layout/PageTab'
import axios from 'axios'
import BarChart from '../../components/UI/BarChart'
import { mapGetters } from 'vuex'
export default {
name: 'Storage',
components: {
BarChart,
ProgressLine,
FormLabel,
PageTab,
},
computed: {
...mapGetters([
'config'
])
},
data() {
return {
isLoading: true,
distribution: undefined,
storage: undefined
}
},
created() {
axios.get('/api/user/storage')
.then(response => {
this.distribution = this.$mapStorageUsage(response.data)
this.storage = response.data
this.isLoading = false
})
export default {
name: 'Storage',
components: {
BarChart,
ProgressLine,
FormLabel,
PageTab,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
isLoading: true,
distribution: undefined,
storage: undefined,
}
}
},
created() {
axios.get('/api/user/storage').then((response) => {
this.distribution = this.$mapStorageUsage(response.data)
this.storage = response.data
this.isLoading = false
})
},
}
</script>