deleted frontend code

This commit is contained in:
Čarodej
2022-04-27 08:17:06 +02:00
parent f45c1eb576
commit eb4d5b4cba
142 changed files with 338 additions and 15390 deletions

View File

@@ -1,6 +1,6 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:yQs3FfCdjY2KGHl1JpLP7L8LLVq8RNOIu/sygWVXUBM=
APP_KEY=base64:YrsSyfNE+I3JPF+zZZTddgxb5YZN6xggPFn6dlpkQlI=
APP_DEBUG=true
APP_URL=http://localhost
APP_DEMO=false

View File

@@ -1,7 +1,7 @@
<?php
return [
'version' => '2.1.0.1',
'version' => '1.0.0',
'is_demo' => env('APP_DEMO', false),
@@ -63,10 +63,6 @@ return [
// The update versions which need to run upgrade process
'updates' => [
'2_0_10',
'2_0_13',
'2_0_14',
'2_0_16',
'2_1_1',
],
];

View File

@@ -1,75 +1,75 @@
{
"/js/main.js": "/js/main.js",
"/chunks/request.js": "/chunks/request.js?id=a5136967a1882fd5",
"/chunks/request-upload.js": "/chunks/request-upload.js?id=1310f3005ea0d465",
"/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=3c2fc454c3fce8d2",
"/chunks/status-check.js": "/chunks/status-check.js?id=42f9d84c64fb105c",
"/chunks/purchase-code.js": "/chunks/purchase-code.js?id=f858e6cd4e9e59f6",
"/chunks/database.js": "/chunks/database.js?id=c36d1bfafcf1b15e",
"/chunks/environment.js": "/chunks/environment.js?id=1cf279f28dcb597f",
"/chunks/app-setup.js": "/chunks/app-setup.js?id=2efed338e56fccab",
"/chunks/admin-account.js": "/chunks/admin-account.js?id=e8c8b0f2c83e8736",
"/chunks/shared.js": "/chunks/shared.js?id=adb0dccc00e457ff",
"/chunks/shared/browser.js": "/chunks/shared/browser.js?id=e712d9ff304ae80f",
"/chunks/shared/single-file.js": "/chunks/shared/single-file.js?id=75925e92aa837216",
"/chunks/shared/authenticate.js": "/chunks/shared/authenticate.js?id=6f17bfddfaee9d15",
"/chunks/not-found.js": "/chunks/not-found.js?id=bd6f4028c9d5f194",
"/chunks/temporary-unavailable.js": "/chunks/temporary-unavailable.js?id=f1cbdc3b62c510de",
"/chunks/admin.js": "/chunks/admin.js?id=1560aebbd0a1ec39",
"/chunks/dashboard.js": "/chunks/dashboard.js?id=50f9ca92c1fb1056",
"/chunks/invoices.js": "/chunks/invoices.js?id=2ad8029efb381c2a",
"/chunks/subscriptions.js": "/chunks/subscriptions.js?id=e081b6ee62f749d1",
"/chunks/pages.js": "/chunks/pages.js?id=e4dffd90dbd041de",
"/chunks/page-edit.js": "/chunks/page-edit.js?id=9b21bfa01a9a4cc2",
"/chunks/plans.js": "/chunks/plans.js?id=65482bb311114624",
"/chunks/users.js": "/chunks/users.js?id=46520527325c005f",
"/chunks/user-create.js": "/chunks/user-create.js?id=4210ecb12e1bc179",
"/chunks/plan-create/fixed.js": "/chunks/plan-create/fixed.js?id=aa1f1f24b8ed53ec",
"/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=28dd455e83a6ad0a",
"/chunks/user.js": "/chunks/user.js?id=8f1f08244520be73",
"/chunks/user-detail.js": "/chunks/user-detail.js?id=d22afa05b9a3757f",
"/chunks/user-storage.js": "/chunks/user-storage.js?id=4e5dba60505fbd72",
"/chunks/user-subscription.js": "/chunks/user-subscription.js?id=66ae479b7881829a",
"/chunks/user-password.js": "/chunks/user-password.js?id=2f6feb7b2fe85993",
"/chunks/user-delete.js": "/chunks/user-delete.js?id=556ee0e95b61526c",
"/chunks/plan.js": "/chunks/plan.js?id=64c2482191d0264f",
"/chunks/plan-subscribers.js": "/chunks/plan-subscribers.js?id=b171c0d1b134f7b4",
"/chunks/plan-settings.js": "/chunks/plan-settings.js?id=2cd26776c594e78f",
"/chunks/plan-delete.js": "/chunks/plan-delete.js?id=c70ebcff48b7f1fd",
"/chunks/payments.js": "/chunks/payments.js?id=995c8bd6ac4c5770",
"/chunks/payments/billings.js": "/chunks/payments/billings.js?id=03f371c8b1ce81da",
"/chunks/payments/settings.js": "/chunks/payments/settings.js?id=5f97f1ac47be9927",
"/chunks/app-settings.js": "/chunks/app-settings.js?id=6fd0fd89ccfdd290",
"/chunks/app-appearance.js": "/chunks/app-appearance.js?id=0eb829fd80113549",
"/chunks/app-index.js": "/chunks/app-index.js?id=22b024b841518628",
"/chunks/app-environment.js": "/chunks/app-environment.js?id=4be35ec974ce1291",
"/chunks/app-others.js": "/chunks/app-others.js?id=44647e5dacec3146",
"/chunks/app-sign-in-out.js": "/chunks/app-sign-in-out.js?id=9c3408a70459090e",
"/chunks/app-adsense.js": "/chunks/app-adsense.js?id=abe3fd03fc6aa6b5",
"/chunks/app-server.js": "/chunks/app-server.js?id=5051f8a08707ed36",
"/chunks/app-language.js": "/chunks/app-language.js?id=0d67a5ffe354681b",
"/chunks/homepage.js": "/chunks/homepage.js?id=14c7ce5244e7ff7c",
"/chunks/dynamic-page.js": "/chunks/dynamic-page.js?id=8f0e89961cc767ef",
"/chunks/contact-us.js": "/chunks/contact-us.js?id=e6902071177114a9",
"/chunks/successfully-email-verified.js": "/chunks/successfully-email-verified.js?id=5ac8ec655bcb572a",
"/chunks/successfully-email-send.js": "/chunks/successfully-email-send.js?id=2086cf5e64520632",
"/chunks/sign-in.js": "/chunks/sign-in.js?id=25ccff57d079640f",
"/chunks/sign-up.js": "/chunks/sign-up.js?id=d19570e7be1a667b",
"/chunks/forgotten-password.js": "/chunks/forgotten-password.js?id=b3286fb8f390bbd4",
"/chunks/create-new-password.js": "/chunks/create-new-password.js?id=ba2e1007acbe3e19",
"/chunks/settings.js": "/chunks/settings.js?id=78234db26addeb1a",
"/chunks/profile.js": "/chunks/profile.js?id=9742ec84ddcbdea6",
"/chunks/settings-password.js": "/chunks/settings-password.js?id=3aecc301e7a35dcb",
"/chunks/settings-storage.js": "/chunks/settings-storage.js?id=4e4ff51f12ad9bc3",
"/chunks/billing.js": "/chunks/billing.js?id=5bbebe710852bcb5",
"/chunks/platform.js": "/chunks/platform.js?id=649383244cab95d7",
"/chunks/files.js": "/chunks/files.js?id=8a50e16ec0baeff0",
"/chunks/recent-uploads.js": "/chunks/recent-uploads.js?id=4de7ebe40af1bf04",
"/chunks/my-shared-items.js": "/chunks/my-shared-items.js?id=ecc811b50dcce19d",
"/chunks/trash.js": "/chunks/trash.js?id=03f841299c5ef282",
"/chunks/team-folders.js": "/chunks/team-folders.js?id=a12cb31542faaed0",
"/chunks/shared-with-me.js": "/chunks/shared-with-me.js?id=a9679bfb8e294251",
"/chunks/invitation.js": "/chunks/invitation.js?id=26703e583c07a989",
"/chunks/request.js": "/chunks/request.js?id=6b84fefd11208314",
"/chunks/request-upload.js": "/chunks/request-upload.js?id=a57ea65eceba1b87",
"/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=19a0784e59d768ec",
"/chunks/status-check.js": "/chunks/status-check.js?id=9239a586761b912d",
"/chunks/purchase-code.js": "/chunks/purchase-code.js?id=ba76b9a8adbfdc0b",
"/chunks/database.js": "/chunks/database.js?id=5113b0d4284f764f",
"/chunks/environment.js": "/chunks/environment.js?id=17210c2b24cf3f13",
"/chunks/app-setup.js": "/chunks/app-setup.js?id=cbe7bfed06400736",
"/chunks/admin-account.js": "/chunks/admin-account.js?id=78d257775f5fc485",
"/chunks/shared.js": "/chunks/shared.js?id=17463f7ccbd82a29",
"/chunks/shared/browser.js": "/chunks/shared/browser.js?id=e418615ad9b55450",
"/chunks/shared/single-file.js": "/chunks/shared/single-file.js?id=b64f21aef7584446",
"/chunks/shared/authenticate.js": "/chunks/shared/authenticate.js?id=b5519d193bce2339",
"/chunks/not-found.js": "/chunks/not-found.js?id=d31bd699138cf828",
"/chunks/temporary-unavailable.js": "/chunks/temporary-unavailable.js?id=26798085f527d955",
"/chunks/admin.js": "/chunks/admin.js?id=517fdb275cc1fa5b",
"/chunks/dashboard.js": "/chunks/dashboard.js?id=380940bb78feba70",
"/chunks/invoices.js": "/chunks/invoices.js?id=799928609f57ca10",
"/chunks/subscriptions.js": "/chunks/subscriptions.js?id=a0c4f59d0ec4aee0",
"/chunks/pages.js": "/chunks/pages.js?id=d385a3969fd87fcd",
"/chunks/page-edit.js": "/chunks/page-edit.js?id=0bdc8a5935fd2197",
"/chunks/plans.js": "/chunks/plans.js?id=4bfe7fedc1cb720d",
"/chunks/users.js": "/chunks/users.js?id=e1dc04b0e402dd27",
"/chunks/user-create.js": "/chunks/user-create.js?id=40254ae98547761e",
"/chunks/plan-create/fixed.js": "/chunks/plan-create/fixed.js?id=18f9d1ab17996507",
"/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=40e9f287b5258a40",
"/chunks/user.js": "/chunks/user.js?id=052364b014015a4d",
"/chunks/user-detail.js": "/chunks/user-detail.js?id=fc5475489f0cec6a",
"/chunks/user-storage.js": "/chunks/user-storage.js?id=a9e1ccc028fc989d",
"/chunks/user-subscription.js": "/chunks/user-subscription.js?id=797d77ff2b1c08cc",
"/chunks/user-password.js": "/chunks/user-password.js?id=900ae71c3d4199ea",
"/chunks/user-delete.js": "/chunks/user-delete.js?id=a3091617207684e5",
"/chunks/plan.js": "/chunks/plan.js?id=3e7b0b34c2247e6c",
"/chunks/plan-subscribers.js": "/chunks/plan-subscribers.js?id=36d925def6a82cb2",
"/chunks/plan-settings.js": "/chunks/plan-settings.js?id=df990f928a77c355",
"/chunks/plan-delete.js": "/chunks/plan-delete.js?id=630deb1fc4e17ed9",
"/chunks/payments.js": "/chunks/payments.js?id=d59a2a18b680d65c",
"/chunks/payments/billings.js": "/chunks/payments/billings.js?id=27d6c1b58dbd1e6c",
"/chunks/payments/settings.js": "/chunks/payments/settings.js?id=450162f937b7b2fd",
"/chunks/app-settings.js": "/chunks/app-settings.js?id=4ff5c1e5bbb0aed8",
"/chunks/app-appearance.js": "/chunks/app-appearance.js?id=8ba3feb2cc81a2c3",
"/chunks/app-index.js": "/chunks/app-index.js?id=0c50096e8de09288",
"/chunks/app-environment.js": "/chunks/app-environment.js?id=d5fbb0ea9249ce7f",
"/chunks/app-others.js": "/chunks/app-others.js?id=82cec4d0d2fab089",
"/chunks/app-sign-in-out.js": "/chunks/app-sign-in-out.js?id=f79027ce1f1f4c4b",
"/chunks/app-adsense.js": "/chunks/app-adsense.js?id=c7e7dc2975317062",
"/chunks/app-server.js": "/chunks/app-server.js?id=ff66d34e90ff98a0",
"/chunks/app-language.js": "/chunks/app-language.js?id=041070825d222906",
"/chunks/homepage.js": "/chunks/homepage.js?id=73f93c1932ffc158",
"/chunks/dynamic-page.js": "/chunks/dynamic-page.js?id=9553d7a2912cb901",
"/chunks/contact-us.js": "/chunks/contact-us.js?id=ea99d85aa3500595",
"/chunks/successfully-email-verified.js": "/chunks/successfully-email-verified.js?id=c26cb144101e7c79",
"/chunks/successfully-email-send.js": "/chunks/successfully-email-send.js?id=170d814982e1c475",
"/chunks/sign-in.js": "/chunks/sign-in.js?id=f87a4dd837cbe607",
"/chunks/sign-up.js": "/chunks/sign-up.js?id=3d7559511768cd0e",
"/chunks/forgotten-password.js": "/chunks/forgotten-password.js?id=27cda9364b6593d8",
"/chunks/create-new-password.js": "/chunks/create-new-password.js?id=2f0401ee2fc148c4",
"/chunks/settings.js": "/chunks/settings.js?id=e40960367c00597f",
"/chunks/profile.js": "/chunks/profile.js?id=5f1e6f1817817716",
"/chunks/settings-password.js": "/chunks/settings-password.js?id=d00bf503d8126dc4",
"/chunks/settings-storage.js": "/chunks/settings-storage.js?id=092e324aad54656b",
"/chunks/billing.js": "/chunks/billing.js?id=115c25478cee576d",
"/chunks/platform.js": "/chunks/platform.js?id=2add0f27d00ef117",
"/chunks/files.js": "/chunks/files.js?id=bee14c7818d47129",
"/chunks/recent-uploads.js": "/chunks/recent-uploads.js?id=91632ef82ff2959c",
"/chunks/my-shared-items.js": "/chunks/my-shared-items.js?id=3bf3fb855fd65a7d",
"/chunks/trash.js": "/chunks/trash.js?id=55241ed4bb60c8dd",
"/chunks/team-folders.js": "/chunks/team-folders.js?id=8c6e3f91c2bd3bdb",
"/chunks/shared-with-me.js": "/chunks/shared-with-me.js?id=470178ec3b181fce",
"/chunks/invitation.js": "/chunks/invitation.js?id=424b2783d9785a09",
"/css/tailwind.css": "/css/tailwind.css",
"/css/app.css": "/css/app.css"
}

View File

@@ -4,14 +4,10 @@
<Alert />
<ToasterWrapper />
<CookieDisclaimer />
<RemoteUploadProgress />
<!--Show spinner before translations is loaded-->
<Spinner v-if="!isLoaded" />
<!--Show warning bar when user functionality is restricted-->
<RestrictionWarningBar />
<div :class="{'lg:flex': isSidebarNavigation}">
<SidebarNavigation v-if="isSidebarNavigation" />
<router-view v-if="isLoaded" />
@@ -23,8 +19,6 @@
</template>
<script>
import RestrictionWarningBar from './components/Subscription/RestrictionWarningBar'
import RemoteUploadProgress from "./components/RemoteUpload/RemoteUploadProgress"
import ToasterWrapper from './components/Toaster/ToasterNotifications'
import SidebarNavigation from "./components/Sidebar/SidebarNavigation"
import CookieDisclaimer from './components/UI/Others/CookieDisclaimer'
@@ -37,8 +31,6 @@ import { events } from './bus'
export default {
name: 'App',
components: {
RestrictionWarningBar,
RemoteUploadProgress,
SidebarNavigation,
CookieDisclaimer,
ToasterWrapper,

View File

@@ -1,135 +0,0 @@
<template>
<DatatableWrapper api="/api/admin/dashboard/transactions" :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">
<span class="text-sm font-bold">
{{ row.data.attributes.note }}
</span>
</td>
<td class="px-3 md:px-1">
<div v-if="row.data.relationships.user" class="flex items-center">
<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>
</div>
<span v-if="!row.data.relationships.user" class="text-xs font-bold text-gray-500">
{{ $t('user_was_deleted') }}
</span>
</td>
<td class="px-3 md:px-1">
<ColorLabel
v-if="config.subscriptionType === 'fixed'"
:color="$getTransactionStatusColor(row.data.attributes.status)"
>
{{ $t(row.data.attributes.status) }}
</ColorLabel>
<ColorLabel
v-if="config.subscriptionType === 'metered'"
:color="$getTransactionTypeColor(row.data.attributes.type)"
>
{{ $t(row.data.attributes.type) }}
</ColorLabel>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold" :class="$getTransactionTypeTextColor(row.data.attributes.type)">
{{ $getTransactionMark(row.data.attributes.type) + row.data.attributes.price }}
</span>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.created_at }}
</span>
</td>
<td class="pl-3 md:pl-1">
<div class="w-28">
<img
class="inline-block max-h-5"
:src="$getPaymentLogo(row.data.attributes.driver)"
:alt="row.data.attributes.driver"
/>
</div>
</td>
</tr>
</template>
<!--Empty page-->
<template v-slot:empty-page>
<InfoBox style="margin-bottom: 0">
<p>{{ $t("not_any_transactions") }}</p>
</InfoBox>
</template>
</DatatableWrapper>
</template>
<script>
import DatatableCellImage from '../../UI/Table/DatatableCellImage'
import DatatableWrapper from '../../UI/Table/DatatableWrapper'
import ColorLabel from '../../UI/Labels/ColorLabel'
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
import MemberAvatar from '../../UI/Others/MemberAvatar'
import InfoBox from '../../UI/Others/InfoBox'
import { mapGetters } from 'vuex'
export default {
name: 'WidgetLatestTransactions',
props: ['icon', 'title'],
components: {
DatatableCellImage,
DatatableWrapper,
MemberAvatar,
Trash2Icon,
ColorLabel,
Edit2Icon,
InfoBox,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
columns: [
{
label: this.$t('note'),
field: 'note',
sortable: true,
},
{
label: this.$t('user'),
field: 'user_id',
sortable: true,
},
{
label: this.$t('status'),
field: 'status',
sortable: true,
},
{
label: this.$t('total'),
field: 'amount',
sortable: true,
},
{
label: this.$t('payed_at'),
field: 'created_at',
sortable: true,
},
{
label: this.$t('service'),
field: 'driver',
sortable: true,
},
],
}
},
}
</script>

View File

@@ -144,8 +144,6 @@ export default {
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]
},
},
@@ -180,28 +178,11 @@ export default {
},
],
},
{
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: {
resetData() {
this.$store.commit('SET_CURRENT_TEAM_FOLDER', null)
this.$store.commit('CLIPBOARD_CLEAR')
},
goToFolder(folder) {

View File

@@ -48,7 +48,7 @@
@click.native="$shareFileOrFolder(currentFile)"
:title="sharingTitle"
icon="share"
v-if="!$isThisRoute($route, ['Public', 'RequestUpload', 'SharedWithMe'])"
v-if="!$isThisRoute($route, ['Public'])"
/>
<Option
@click.native="$deleteFileOrFolder(currentFile)"
@@ -57,7 +57,7 @@
class="menu-option"
/>
</OptionGroup>
<OptionGroup v-if="!$isThisRoute($route, ['RequestUpload'])">
<OptionGroup>
<Option @click.native="downloadItem" :title="$t('download')" icon="download" />
</OptionGroup>
</PopoverItem>
@@ -79,7 +79,6 @@
<div class="ml-5">
<ToolbarButton
v-if="!$isThisRoute($route, ['RequestUpload'])"
@click.native="downloadItem"
source="download"
:action="$t('download_item')"

View File

@@ -1,202 +0,0 @@
<template>
<div>
<FormLabel icon="wifi">
{{ $te('broadcasting') ? $t('broadcasting') : 'Broadcasting' }}
</FormLabel>
<ValidationProvider
tag="div"
mode="passive"
name="Broadcast Driver"
rules="required"
v-slot="{ errors }"
>
<AppInputText
title="Broadcast Driver"
:error="errors[0]"
:is-last="broadcast.driver === 'none' || broadcast.driver === undefined"
>
<SelectInput
v-model="broadcast.driver"
:options="broadcastDrivers"
placeholder="Select your broadcast driver"
:isError="errors[0]"
/>
</AppInputText>
</ValidationProvider>
<div v-if="broadcast.driver === 'native'">
<ValidationProvider tag="div" mode="passive" name="Host" rules="required" v-slot="{ errors }">
<AppInputText title="Hostname or IP" :error="errors[0]">
<input
class="focus-border-theme input-dark"
v-model="broadcast.host"
placeholder="Type your hostname or IP"
type="text"
:class="{ '!border-rose-600': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="TLS" v-slot="{ errors }">
<AppInputSwitch
title="Required TLS Connection"
description="When enabled, you must have installed ssl certificate on your server host"
:is-last="true"
>
<SwitchInput v-model="broadcast.tls" :state="broadcast.tls" />
</AppInputSwitch>
</ValidationProvider>
</div>
<div v-if="broadcast.driver === 'pusher'">
<ValidationProvider tag="div" mode="passive" name="App ID" rules="required" v-slot="{ errors }">
<AppInputText title="App ID" :error="errors[0]">
<input
class="focus-border-theme input-dark"
v-model="broadcast.id"
placeholder="Type your app id"
type="text"
:class="{ '!border-rose-600': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Key" rules="required" v-slot="{ errors }">
<AppInputText title="Key" :error="errors[0]">
<input
class="focus-border-theme input-dark"
v-model="broadcast.key"
placeholder="Paste your key"
type="text"
:class="{ '!border-rose-600': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Secret" rules="required" v-slot="{ errors }">
<AppInputText title="Secret" :error="errors[0]">
<input
class="focus-border-theme input-dark"
v-model="broadcast.secret"
placeholder="Paste your secret"
type="text"
:class="{ '!border-rose-600': errors[0] }"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider
tag="div"
mode="passive"
name="Cluster"
rules="required"
v-slot="{ errors }"
>
<AppInputText title="Cluster" :error="errors[0]" :is-last="true">
<SelectInput
v-model="broadcast.cluster"
:options="pusherClusters"
placeholder="Select your cluster"
:isError="errors[0]"
/>
</AppInputText>
</ValidationProvider>
</div>
</div>
</template>
<script>
import {ValidationObserver, ValidationProvider} from 'vee-validate/dist/vee-validate.full'
import SelectInput from '../Inputs/SelectInput'
import AppInputText from './Layouts/AppInputText'
import FormLabel from '../UI/Labels/FormLabel'
import AppInputSwitch from "./Layouts/AppInputSwitch";
import SwitchInput from "../Inputs/SwitchInput";
export default {
name: 'BroadcastSetup',
components: {
SwitchInput,
AppInputSwitch,
ValidationObserver,
ValidationProvider,
AppInputText,
SelectInput,
FormLabel,
},
watch: {
broadcast: {
handler(newValue, oldValue) {
this.$emit('input', newValue)
},
deep: true
},
},
data() {
return {
broadcast: {
driver: undefined,
id: undefined,
key: undefined,
secret: undefined,
cluster: undefined,
tls: true,
},
broadcastDrivers: [
{
label: 'Pusher',
value: 'pusher',
},
{
label: 'VueFileManager',
value: 'native',
},
{
label: 'None',
value: 'none',
},
],
pusherClusters: [
{
label: 'US East (N. Virginia)',
value: 'mt1',
},
{
label: 'Asia Pacific (Singapore)',
value: 'ap1',
},
{
label: 'Asia Pacific (Mumbai)',
value: 'ap2',
},
{
label: 'US East (Ohio)',
value: 'us2',
},
{
label: 'Asia Pacific (Tokyo)',
value: 'ap3',
},
{
label: 'US West (Oregon)',
value: 'us3',
},
{
label: 'Asia Pacific (Sydney)',
value: 'ap4',
},
{
label: 'EU (Ireland)',
value: 'eu',
},
{
label: 'South America (São Paulo)',
value: 'sa1',
},
],
}
}
}
</script>

View File

@@ -1,19 +1,16 @@
<template>
<div>
<VueFolderIcon v-if="!item.data.attributes.isTeamFolder" />
<VueFolderTeamIcon v-if="item.data.attributes.isTeamFolder" style="width: 53px; height: 52px" />
</div>
</template>
<script>
import VueFolderTeamIcon from './VueFolderTeamIcon'
import VueFolderIcon from './VueFolderIcon'
export default {
name: 'FolderIcon',
props: ['item'],
components: {
VueFolderTeamIcon,
VueFolderIcon,
},
}

View File

@@ -1,21 +0,0 @@
<template>
<svg
fill="currentColor"
class="google-icon"
width="22px"
height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<path d="M11.0024865,21.9999898 L10.5295935,21.9999898 C4.79941267,21.6719961 0.249801856,17.0571507 0.00497307141,11.324452 C-0.108950196,7.53234629 1.74080914,3.949454 4.89870278,1.84553974 C8.05659642,-0.258374523 12.0763823,-0.586006523 15.5334621,0.978755386 C15.93211,1.16635174 16.2376499,1.50750632 16.3802706,1.9242707 C16.5207552,2.3463989 16.480972,2.80775257 16.2702955,3.19961693 L14.8516162,5.81627558 C14.4628126,6.53476037 13.5910392,6.83955556 12.8390713,6.51991489 C10.9682883,5.78493153 8.83865842,6.25418492 7.45028969,7.70730621 C6.53135645,8.69989447 6.06889516,10.0310827 6.17457812,11.3794238 C6.30701252,12.681964 6.96661172,13.8736694 8.00016536,14.677733 C9.05866753,15.5524314 10.4319771,15.9503782 11.7943075,15.7771694 C12.9539759,15.6214383 13.9992299,14.9976441 14.6866535,14.0510543 L11.4973746,14.0510543 C10.6209566,14.0510751 9.90877945,13.3440121 9.90273519,12.4678659 L9.90273519,9.52137628 C9.90273519,8.64093344 10.6166796,7.92719349 11.4973746,7.92719349 L20.4053605,7.92719349 C21.2793246,7.93309626 21.9880476,8.6367299 22,9.51038192 L22,11.5773224 C21.6954275,17.4249906 16.8597486,22.0079014 11.0024865,21.9999898 Z M11.0024865,2.21012416 C8.62265013,2.2090533 6.34389345,3.17185147 4.68614478,4.87884362 C3.02839611,6.58583578 2.13308042,8.89139591 2.20447576,11.2694802 C2.40041639,15.8515827 6.03742275,19.5398294 10.6175736,19.8011067 C15.4496383,20.0190768 19.5514723,16.2965621 19.8004973,11.4673787 L19.8004973,10.1260663 L12.1022379,10.1260663 L12.1022379,11.8631758 L17.9419175,11.8631758 L17.4910195,13.2924432 C16.7642506,15.7700545 14.6584095,17.5960263 12.1022379,17.9650479 C10.1464891,18.2487094 8.15978033,17.7203963 6.60348115,16.5027975 C5.06921332,15.2995219 4.11012255,13.508076 3.95960166,11.5644046 C3.80908078,9.62073328 4.48093145,7.70305715 5.81166018,6.27803888 C7.69032381,4.25377573 10.5650106,3.48826744 13.2019892,4.3100477 L14.0487978,2.73785364 C13.0730876,2.38128177 12.0413172,2.20254243 11.0024865,2.21012416 Z" id="Shape"></path>
</svg>
</template>
<script>
export default {
name: 'GoogleIcon',
}
</script>
<style lang="scss">
.google-icon path{
color: inherit;
}
</style>

View File

@@ -1,59 +0,0 @@
<template>
<svg
viewBox="0 0 53 39"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<g id="V2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="team-folder">
<path
d="M48.03125,6.5 L29.790833,6.5 C28.7431613,6.5 27.7373076,6.08896217 26.9894703,5.35523504 L22.6980297,1.14476496 C21.9501924,0.41103783 20.9443387,-6.36543387e-16 19.896667,0 L4.96875,0 L4.96875,0 C2.22455078,0 0,2.18257812 0,4.875 L0,34.125 C0,36.8174219 2.22455078,39 4.96875,39 L48.03125,39 C50.7754492,39 53,36.8174219 53,34.125 L53,11.375 C53,8.68257813 50.7754492,6.5 48.03125,6.5 Z"
class="svg-color-theme"
stroke="none"
stroke-width="0"
></path>
<path
d="M48.03125,6.5 L29.790833,6.5 C28.7431613,6.5 27.7373076,6.08896217 26.9894703,5.35523504 L22.6980297,1.14476496 C21.9501924,0.41103783 20.9443387,-6.36543387e-16 19.896667,0 L4.96875,0 L4.96875,0 C2.22455078,0 0,2.18257812 0,4.875 L0,34.125 C0,36.8174219 2.22455078,39 4.96875,39 L48.03125,39 C50.7754492,39 53,36.8174219 53,34.125 L53,11.375 C53,8.68257813 50.7754492,6.5 48.03125,6.5 Z"
fill="black"
fill-opacity="0.2"
stroke="none"
stroke-width="0"
></path>
<path
d="M48.03125,12.75 C49.0609313,12.75 49.9941504,13.1577174 50.6692739,13.8201027 C51.3356976,14.4739525 51.75,15.3766531 51.75,16.375 L51.75,16.375 L51.75,34.125 C51.75,35.1233469 51.3356976,36.0260475 50.6692739,36.6798973 C49.9941504,37.3422826 49.0609313,37.75 48.03125,37.75 L48.03125,37.75 L4.96875,37.75 C3.93906868,37.75 3.00584961,37.3422826 2.33072613,36.6798973 C1.66430239,36.0260475 1.25,35.1233469 1.25,34.125 L1.25,34.125 L1.25,16.375 C1.25,15.3766531 1.66430239,14.4739525 2.33072613,13.8201027 C3.00584961,13.1577174 3.93906868,12.75 4.96875,12.75 L4.96875,12.75 Z"
stroke-width="2"
class="svg-color-theme"
fill="green"
></path>
<g
id="Icon"
transform="translate(8.000000, 20.000000)"
class="svg-stroke-theme-darken"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.3"
stroke="black"
stroke-opacity="0.25"
>
<path
d="M9.59999943,10.7999994 L9.59999943,9.59999943 C9.59999943,8.27451611 8.52548289,7.19999957 7.19999957,7.19999957 L2.39999986,7.19999957 C1.07451654,7.19999957 0,8.27451611 0,9.59999943 L0,10.7999994"
></path>
<circle cx="4.79999971" cy="2.39999986" r="2.39999986"></circle>
<path
d="M13.1999992,10.7999994 L13.1999992,9.59999943 C13.1991834,8.50627014 12.4589985,7.55143166 11.3999993,7.27799957"
></path>
<path
d="M8.99999946,0.0779999954 C10.0619483,0.349901852 10.8047053,1.30679461 10.8047053,2.40299986 C10.8047053,3.4992051 10.0619483,4.45609786 8.99999946,4.72799972"
></path>
</g>
</g>
</g>
</svg>
</template>
<script>
export default {
name: 'VueFolderTeamIcon',
}
</script>

View File

@@ -1,83 +0,0 @@
<template>
<div class="page-title left" :class="type">
<h1 class="title" v-html="title"></h1>
<h2 class="description" v-if="description">
{{ description }}
</h2>
</div>
</template>
<script>
export default {
name: 'IndexPageTile',
props: ['title', 'description', 'type'],
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/landing-page';
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.page-title {
position: relative;
z-index: 1;
&.center {
text-align: center;
.title {
margin-left: auto;
margin-right: auto;
max-width: 780px;
}
.description {
margin-left: auto;
margin-right: auto;
}
}
.title {
max-width: 580px;
font-size: 48px;
font-weight: 800;
line-height: 1.3;
margin-bottom: 15px;
/deep/ span {
font-size: 48px;
}
}
.description {
max-width: 580px;
@include font-size(20);
font-weight: 500;
line-height: 1.65;
margin-bottom: 30px;
}
}
@media only screen and (max-width: 960px) {
.page-title {
.title {
max-width: 100%;
font-size: 32px;
line-height: 1.25;
margin-bottom: 15px;
/deep/ span {
font-size: 32px;
}
}
.description {
max-width: 100%;
@include font-size(16);
line-height: 1.6;
margin-bottom: 30px;
}
}
}
</style>

View File

@@ -1,225 +0,0 @@
<template>
<div class="text-center">
<PlanPeriodSwitcher v-if="plans && yearlyPlans.length > 0" v-model="isSelectedYearlyPlans" class="inline-block" />
<div class="plans-wrapper" v-if="plans">
<article class="plan" v-if="plan.data.attributes.interval === intervalPlanType" v-for="plan in plans.data" :key="plan.data.id">
<div class="plan-wrapper">
<header class="plan-header mb-8">
<div class="icon">
<hard-drive-icon class="text-theme mx-auto" size="26" />
</div>
<h1 class="title">{{ plan.data.attributes.name }}</h1>
<h2 class="description">
{{ plan.data.attributes.description }}
</h2>
</header>
<div class="justify-center flex py-1.5" v-for="(value, key, i) in plan.data.attributes.features" :key="i">
<div class="flex items-center">
<CheckIcon size="18" class="svg-stroke-theme mr-2" />
<span class="text-sm font-bold" v-if="value !== -1">
{{ $t( key === 'max_team_members' ? 'max_team_members_total' : key, { value: value }) }}
</span>
<span class="text-sm font-bold" v-if="value === -1">
{{ $t(`${key}.unlimited`) }}
</span>
</div>
</div>
<footer class="plan-footer mt-8">
<b class="price text-theme">
{{ plan.data.attributes.price }} / {{ $t(`interval.${plan.data.attributes.interval}`) }}
</b>
</footer>
</div>
</article>
</div>
</div>
</template>
<script>
import { CheckIcon, HardDriveIcon } from 'vue-feather-icons'
import axios from 'axios'
import PlanPeriodSwitcher from "../../Subscription/PlanPeriodSwitcher";
export default {
name: 'PricingTables',
components: {
PlanPeriodSwitcher,
HardDriveIcon,
CheckIcon,
},
computed: {
intervalPlanType() {
return this.isSelectedYearlyPlans ? 'year' : 'month'
},
yearlyPlans() {
return this.plans.data.filter((plan) => plan.data.attributes.interval === 'year')
},
},
data() {
return {
plans: undefined,
isSelectedYearlyPlans: false,
}
},
created() {
axios.get('api/subscriptions/plans').then((response) => {
this.plans = response.data
this.$emit('load', response.data)
})
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/variables';
@import '../../../../sass/vuefilemanager/mixins';
.plans-wrapper {
box-shadow: 0 7px 20px 5px hsla(220, 36%, 16%, 0.05);
border-radius: 8px;
background: white;
}
.plan {
text-align: center;
flex: 0 0 33%;
padding: 55px 25px 75px;
//border-right: 1px solid #F7F7F7;
&:last-child {
border-right: none;
}
.plan-header {
.icon {
path,
line,
polyline,
rect,
circle {
color: inherit;
}
}
.title {
@include font-size(25);
font-weight: 800;
padding-top: 10px;
}
.description {
@include font-size(14);
font-weight: 600;
}
}
.plan-features {
margin: 55px 0;
.storage-size {
@include font-size(48);
font-weight: 900;
line-height: 1.1;
}
.storage-description {
display: block;
@include font-size(15);
font-weight: 800;
}
}
.plan-footer {
.sign-in-button {
width: 100%;
text-align: center;
}
.price {
@include font-size(18);
display: block;
padding-top: 5px;
.vat-disclaimer {
@include font-size(11);
color: $text;
display: block;
font-weight: 300;
opacity: 0.45;
margin-top: 5px;
}
}
}
}
.plans-wrapper {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
@media only screen and (max-width: 960px) {
.plans-wrapper {
display: block;
margin: 0;
.plan {
padding: 30px 25px;
border-bottom: 1px solid #f7f7f7;
border-right: none;
}
}
}
.dark {
.plans-wrapper {
background: $dark_mode_foreground;
}
.plan {
border-color: $dark_mode_border_color !important;
.plan-wrapper {
background: $dark_mode_foreground;
}
.plan-header {
.title {
color: $dark_mode_text_primary;
}
.description {
color: $dark_mode_text_secondary;
}
}
.plan-features {
.storage-size {
color: $dark_mode_text_primary;
}
.storage-description {
color: $dark_mode_text_primary;
}
}
.plan-footer {
.sign-in-button {
background: rgba($theme, 0.1);
/deep/ .content {
color: $theme;
}
}
.price {
.vat-disclaimer {
color: $dark_mode_text_primary;
}
}
}
}
}
</style>

View File

@@ -1,348 +0,0 @@
<template>
<div class="page-wrapper large get-started" v-if="index.section_get_started === '1'">
<PageTitle
class="page-title"
type="center"
:title="index.get_started_title"
:description="index.get_started_description"
></PageTitle>
<router-link
tag="button"
class="get-started-button bg-theme-800 hover-bg-theme shadow-theme"
:to="{ name: 'SignUp' }"
>
<span class="content">{{ $t('sign_up_now') }}</span>
<chevron-right-icon size="22"></chevron-right-icon>
</router-link>
<cloud-icon size="790" class="cloud-bg svg-color-theme" />
<div class="icons">
<hard-drive-icon size="42" class="icon"></hard-drive-icon>
<settings-icon size="22" class="icon"></settings-icon>
<image-icon size="50" class="icon"></image-icon>
<link-icon size="24" class="icon"></link-icon>
<trash2-icon size="40" class="icon"></trash2-icon>
<search-icon size="18" class="icon"></search-icon>
<eye-icon size="36" class="icon"></eye-icon>
<star-icon size="34" class="icon"></star-icon>
<folder-plus-icon size="20" class="icon"></folder-plus-icon>
<grid-icon size="28" class="icon"></grid-icon>
<share-icon size="32" class="icon"></share-icon>
<folder-plus-icon size="48" class="icon"></folder-plus-icon>
<search-icon size="34" class="icon"></search-icon>
<star-icon size="22" class="icon"></star-icon>
<upload-cloud-icon size="42" class="icon"></upload-cloud-icon>
<grid-icon size="18" class="icon"></grid-icon>
<settings-icon size="32" class="icon"></settings-icon>
<link-icon size="36" class="icon"></link-icon>
<hard-drive-icon size="22" class="icon"></hard-drive-icon>
<info-icon size="36" class="icon"></info-icon>
</div>
</div>
</template>
<script>
import PageTitle from './Components/PageTitle'
import {
ChevronRightIcon,
UploadCloudIcon,
FolderPlusIcon,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
SearchIcon,
ShareIcon,
CloudIcon,
ImageIcon,
InfoIcon,
GridIcon,
LinkIcon,
StarIcon,
EyeIcon,
} from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'IndexGetStarted',
components: {
InfoIcon,
UploadCloudIcon,
ShareIcon,
ChevronRightIcon,
FolderPlusIcon,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
SearchIcon,
CloudIcon,
PageTitle,
ImageIcon,
GridIcon,
LinkIcon,
StarIcon,
EyeIcon,
},
computed: {
...mapGetters(['index']),
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.icons {
.icon {
position: absolute;
&:nth-child(20) {
bottom: -37%;
left: 37%;
transform: rotate(0deg);
circle,
line {
stroke: $yellow;
}
}
&:nth-child(19) {
bottom: -21%;
left: 23.5%;
transform: rotate(-20deg);
path,
line {
stroke: $purple;
}
}
&:nth-child(18) {
bottom: -4%;
left: 26.5%;
transform: rotate(0deg);
path {
stroke: $theme;
}
}
&:nth-child(17) {
bottom: -5%;
left: 8.5%;
transform: rotate(0deg);
}
&:nth-child(16) {
top: 86%;
left: 17%;
transform: rotate(18deg);
}
&:nth-child(15) {
top: 64%;
left: 17%;
transform: rotate(0deg);
polyline,
line,
path {
stroke: $red;
}
}
&:nth-child(14) {
top: 44%;
left: 28%;
transform: rotate(0deg);
polygon {
stroke: $purple;
}
}
&:nth-child(13) {
top: 33%;
left: 16%;
transform: rotate(0deg);
}
&:nth-child(12) {
top: 23%;
left: 32%;
transform: rotate(13deg);
line,
path {
stroke: $yellow;
}
}
&:nth-child(1) {
top: 35%;
right: 49%;
transform: rotate(-11deg);
line,
path {
stroke: $theme;
}
}
&:nth-child(2) {
top: 12%;
right: 45%;
transform: rotate(0);
circle,
path {
stroke: $red;
}
}
&:nth-child(3) {
top: 30%;
right: 30%;
transform: rotate(20deg);
}
&:nth-child(4) {
top: 14%;
right: 14.5%;
transform: rotate(-1deg);
}
&:nth-child(5) {
top: 62%;
right: 15.5%;
transform: rotate(21deg);
polyline,
path,
line {
stroke: $red;
}
}
&:nth-child(6) {
top: 66%;
right: 26.5%;
transform: rotate(0deg);
}
&:nth-child(7) {
bottom: 3%;
right: 21.5%;
transform: rotate(16deg);
}
&:nth-child(8) {
bottom: -13%;
right: 16.5%;
transform: rotate(0deg);
polygon {
stroke: $yellow;
}
}
&:nth-child(9) {
bottom: -32%;
right: 27%;
transform: rotate(-20deg);
}
&:nth-child(10) {
bottom: -5%;
right: 34%;
transform: rotate(16deg);
rect {
stroke: $purple;
}
}
&:nth-child(11) {
bottom: -28%;
right: 44%;
transform: rotate(-12deg);
polyline,
line,
path {
stroke: $red;
}
}
}
}
.cloud-bg {
z-index: 0;
position: absolute;
top: 70px;
right: 60px;
transform: scale(-1, 1) rotate(13deg);
opacity: 0.1;
path {
stroke: none;
}
}
.page-title {
padding-top: 340px;
}
.get-started-button {
display: flex;
align-items: center;
outline: none;
border: none;
margin-left: auto;
margin-right: auto;
cursor: pointer;
padding: 20px 36px;
border-radius: 6px;
//box-shadow: 0 5px 10px 2px rgba($theme, 0.34);
margin-bottom: 395px;
@include transition(150ms);
position: relative;
z-index: 1;
.content {
@include font-size(19);
font-weight: 700;
margin-right: 8px;
color: white;
}
polyline {
stroke: white;
}
}
@media only screen and (max-width: 1190px) {
.get-started-button {
margin-bottom: 280px;
}
}
@media only screen and (max-width: 960px) {
.page-title {
padding-top: 20px;
}
.get-started-button {
margin-bottom: 30px;
}
.cloud-bg,
.icons {
display: none;
}
}
</style>

View File

@@ -1,227 +0,0 @@
<template>
<div class="page-wrapper large hero-screenshot">
<img class="hero-light" src="/assets/images/vuefilemanager-screenshot-light.png" :alt="config.app_name" />
<img class="hero-dark" src="/assets/images/vuefilemanager-screenshot-dark.png" :alt="config.app_name" />
<div class="icons">
<link-icon size="20" class="icon"></link-icon>
<image-icon size="38" class="icon"></image-icon>
<hard-drive-icon size="34" class="icon"></hard-drive-icon>
<folder-plus-icon size="40" class="icon"></folder-plus-icon>
<settings-icon size="18" class="icon"></settings-icon>
<search-icon size="24" class="icon"></search-icon>
<star-icon size="18" class="icon"></star-icon>
<trash2-icon size="32" class="icon"></trash2-icon>
<search-icon size="18" class="icon"></search-icon>
<eye-icon size="30" class="icon"></eye-icon>
<star-icon size="30" class="icon"></star-icon>
<folder-plus-icon size="16" class="icon"></folder-plus-icon>
<grid-icon size="20" class="icon"></grid-icon>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import {
FolderPlusIcon,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
SearchIcon,
ImageIcon,
GridIcon,
LinkIcon,
StarIcon,
EyeIcon,
} from 'vue-feather-icons'
export default {
name: 'IndexHeroScreenshot',
components: {
FolderPlusIcon,
HardDriveIcon,
SettingsIcon,
Trash2Icon,
SearchIcon,
ImageIcon,
GridIcon,
LinkIcon,
StarIcon,
EyeIcon,
},
computed: {
...mapGetters(['config']),
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.icons {
.icon {
z-index: 0;
position: absolute;
&:nth-child(1) {
top: -14%;
right: 2%;
}
&:nth-child(2) {
top: -5%;
right: 14%;
transform: rotate(19deg);
}
&:nth-child(3) {
top: -6.5%;
right: 28.5%;
transform: rotate(-12deg);
line,
path {
stroke: $theme;
}
}
&:nth-child(4) {
top: -9.5%;
right: 41.5%;
transform: rotate(13deg);
path,
line {
stroke: $yellow;
}
}
&:nth-child(5) {
top: -16%;
right: 26%;
circle,
path {
stroke: $red;
}
}
&:nth-child(6) {
top: -13%;
right: 49%;
}
&:nth-child(7) {
top: 2.5%;
right: 46%;
polygon {
stroke: $purple;
}
}
&:nth-child(8) {
top: 13%;
right: 2.5%;
transform: rotate(22deg);
polyline,
path,
line {
stroke: $red;
}
}
&:nth-child(9) {
top: 14%;
right: 11%;
circle,
line {
stroke: $purple;
}
}
&:nth-child(10) {
top: 29%;
right: 7%;
transform: rotate(19deg);
}
&:nth-child(11) {
top: 38%;
right: 3%;
polygon {
stroke: $yellow;
}
}
&:nth-child(12) {
top: 50%;
right: 11.5%;
transform: rotate(-22deg);
}
&:nth-child(13) {
top: 34%;
right: 16%;
transform: rotate(13deg);
rect {
stroke: $theme;
}
}
}
}
.hero-screenshot {
position: relative;
z-index: 1;
padding-top: 75px;
padding-bottom: 130px;
img {
border-radius: 8px;
width: 80%;
box-shadow: 0 7px 255px rgba(#19363c, 0.1);
&.hero-dark {
display: none;
}
}
}
@media only screen and (max-width: 890px) {
.icons {
display: none;
}
.hero-screenshot {
padding-top: 40px;
padding-bottom: 90px;
img {
width: 100%;
}
}
}
.dark {
.hero-screenshot {
img {
&.hero-light {
display: none;
}
&.hero-dark {
display: block;
box-shadow: 0 7px 185px rgba(0, 0, 0, 0.8);
}
}
}
}
</style>

View File

@@ -1,198 +0,0 @@
<template>
<section class="main-features page-wrapper medium">
<PageTitle
v-if="index.section_features === '1'"
type="center"
:title="index.features_title"
:description="index.features_description"
></PageTitle>
<div v-if="index.section_feature_boxes === '1'" class="content">
<div class="hero">
<img src="/assets/images/hero-Illustration.svg" alt="Hero" />
</div>
<div class="features">
<div class="feature">
<div class="icon">
<cloud-icon size="24"></cloud-icon>
</div>
<h3 class="title">
{{ index.feature_title_1 }}
</h3>
<p class="description">
{{ index.feature_description_1 }}
</p>
</div>
<div class="feature">
<div class="icon">
<user-icon size="24"></user-icon>
</div>
<h3 class="title">
{{ index.feature_title_2 }}
</h3>
<p class="description">
{{ index.feature_description_2 }}
</p>
</div>
<div class="feature">
<div class="icon">
<hard-drive-icon size="24"></hard-drive-icon>
</div>
<h3 class="title">
{{ index.feature_title_3 }}
</h3>
<p class="description">
{{ index.feature_description_3 }}
</p>
</div>
</div>
</div>
</section>
</template>
<script>
import { UserIcon, CloudIcon, HardDriveIcon } from 'vue-feather-icons'
import PageTitle from './Components/PageTitle'
import { mapGetters } from 'vuex'
export default {
name: 'IndexMainFeatures',
components: {
PageTitle,
HardDriveIcon,
CloudIcon,
UserIcon,
},
computed: {
...mapGetters(['index']),
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.features {
padding-left: 75px;
.feature {
margin-bottom: 25px;
.title {
@include font-size(26);
font-weight: 800;
margin-bottom: 4px;
}
.description {
line-height: 1.5;
color: $text-muted;
@include font-size(18);
}
.icon {
border-radius: 12px;
width: 44px;
height: 44px;
display: flex;
align-items: center;
margin-bottom: 18px;
svg {
margin: 0 auto;
}
}
&:nth-child(1) .icon {
background: rgba($yellow, 0.1);
path,
line,
polyline,
rect,
circle {
stroke: $yellow;
}
}
&:nth-child(2) .icon {
background: rgba($theme, 0.1);
path,
line,
polyline,
rect,
circle {
stroke: $theme;
}
}
&:nth-child(3) .icon {
background: rgba($purple, 0.1);
path,
line,
polyline,
rect,
circle {
stroke: $purple;
}
}
}
}
.content {
margin-top: 107px;
display: flex;
}
@media only screen and (max-width: 1190px) {
.hero {
flex: 0 0 60%;
img {
width: 100%;
}
}
.features {
padding-left: 25px;
margin-top: 0px;
}
}
@media only screen and (max-width: 960px) {
.content {
display: block;
margin-top: 40px;
}
.features {
margin-top: 50px;
padding-left: 0;
.feature {
margin-bottom: 35px;
.title {
@include font-size(22);
}
.description {
@include font-size(16);
}
}
}
}
.dark {
.features {
.feature {
.description {
color: $dark_mode_text_secondary;
}
}
}
}
</style>

View File

@@ -1,140 +0,0 @@
<template>
<nav class="main-navigation">
<router-link :to="{ name: 'Homepage' }" tag="div" class="logo">
<img
class="max-h-6"
v-if="config.app_logo_horizontal"
:src="$getImage(logoSrc)"
:alt="config.app_name"
/>
<b v-if="!config.app_logo_horizontal" class="logo-text">{{ config.app_name }}</b>
</router-link>
<div class="navigation">
<ul class="navigation-links">
<!--<li v-if="config.stripe_public_key">
<a href="/#pricing">
{{ $t('pricing') }}
</a>
</li>-->
<li>
<router-link :to="{ name: 'ContactUs' }" class="hover-text-theme">
{{ $t('contact_us') }}
</router-link>
</li>
</ul>
<ul v-if="!config.isAuthenticated" class="navigation-links">
<li>
<router-link :to="{ name: 'SignIn' }" class="hover-text-theme">
{{ $t('log_in') }}
</router-link>
</li>
<li v-if="config.userRegistration">
<router-link class="cta-button text-theme bg-theme-100" :to="{ name: 'SignUp' }">
{{ $t('page_index.menu.sign_in') }}
</router-link>
</li>
</ul>
<ul v-if="config.isAuthenticated" class="navigation-links">
<li>
<router-link class="cta-button text-theme bg-theme-100" :to="{ name: 'Files' }">
{{ $t('go_to_files') }}
</router-link>
</li>
</ul>
</div>
<router-link class="cta-button log-in text-theme bg-theme-100" :to="{ name: 'SignIn' }">
{{ $t('log_in') }}
</router-link>
</nav>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'IndexNavigation',
computed: {
...mapGetters(['config', 'index', 'isDarkMode']),
logoSrc() {
return this.isDarkMode && this.config.app_logo_horizontal ? this.config.app_logo_horizontal_dark : this.config.app_logo_horizontal
}
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.main-navigation {
justify-content: space-between;
padding-bottom: 25px;
align-items: center;
padding-top: 25px;
display: flex;
}
.logo {
cursor: pointer;
img {
cursor: pointer;
height: 38px;
width: auto;
}
.logo-text {
font-weight: 800;
@include font-size(25);
}
}
.navigation-links {
display: inline-block;
margin-left: 25px;
&:first-child {
margin-left: 0;
}
li {
display: inline-block;
a {
padding: 14px;
font-weight: 700;
@include font-size(17);
@include transition(150ms);
}
}
}
.cta-button {
border-radius: 6px;
padding: 8px 23px;
@include font-size(17);
font-weight: 700;
&.log-in {
display: none;
}
}
@media only screen and (max-width: 690px) {
.navigation {
display: none;
}
.logo {
img {
height: auto;
width: 190px;
}
}
.cta-button.log-in {
display: block;
}
}
</style>

View File

@@ -1,120 +0,0 @@
<template>
<footer class="page-wrapper medium">
<router-link :to="{ name: 'Homepage' }" tag="div" class="logo">
<img
v-if="config.app_logo_horizontal"
:src="$getImage(logoSrc)"
:alt="config.app_name"
class="mx-auto max-h-6"
/>
<b v-if="!config.app_logo_horizontal" class="logo-text">{{ config.app_name }}</b>
</router-link>
<ul class="navigation-links">
<!-- <li>
<a href="/#pricing">
{{ $t('pricing') }}
</a>
</li>-->
<li>
<router-link :to="{ name: 'ContactUs' }" class="hover-text-theme">
{{ $t('contact_us') }}
</router-link>
</li>
</ul>
<ul class="navigation-links">
<li v-if="legal.visibility" v-for="(legal, index) in config.legal" :key="index">
<router-link :to="{ name: 'DynamicPage', params: { slug: legal.slug } }" class="hover-text-theme">
{{ legal.title }}
</router-link>
</li>
</ul>
<p class="copyright" v-html="config.app_footer"></p>
</footer>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'IndexPageFooter',
computed: {
...mapGetters(['config', 'isDarkMode']),
logoSrc() {
return this.isDarkMode && this.config.app_logo_horizontal ? this.config.app_logo_horizontal_dark : this.config.app_logo_horizontal
},
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
footer {
text-align: center;
padding-top: 80px;
}
.logo {
margin-bottom: 15px;
cursor: pointer;
img {
height: 38px;
width: auto;
}
.logo-text {
font-weight: 800;
@include font-size(25);
}
}
.navigation-links {
display: inline-block;
li {
display: inline-block;
a {
display: block;
padding: 19px;
font-weight: 700;
@include font-size(17);
@include transition(150ms);
}
}
}
.copyright {
@include font-size(17);
color: $text-muted;
padding-top: 50px;
padding-bottom: 20px;
/deep/ a {
font-weight: 700;
}
}
@media only screen and (max-width: 960px) {
.navigation-links {
display: block;
li {
display: block;
a {
padding: 10px 0;
}
}
}
}
.dark {
.copyright {
color: $dark_mode_text_secondary;
}
}
</style>

View File

@@ -1,139 +0,0 @@
<template>
<header class="main-header page-wrapper medium">
<PageTitle :title="index.header_title" :description="index.header_description" />
<div v-if="!config.isAuthenticated">
<!--User registration button-->
<router-link v-if="config.userRegistration" class="sign-up-button" :to="{ name: 'SignUp' }">
<AuthButton class="button" icon="chevron-right" :text="$t('page_index.sign_up_button')" />
</router-link>
<!--User login button-->
<router-link v-if="!config.userRegistration" class="sign-up-button" :to="{ name: 'SignIn' }">
<AuthButton class="button" icon="chevron-right" :text="$t('log_in')" />
</router-link>
<div class="features" v-if="config.subscriptionType === 'fixed'">
<div class="feature">
<credit-card-icon size="19" class="feature-icon"></credit-card-icon>
<b class="feature-title">{{ $t('page_index.sign_feature_1') }}</b>
</div>
<div class="feature">
<hard-drive-icon size="19" class="feature-icon"></hard-drive-icon>
<b class="feature-title">{{
$t('page_index.sign_feature_2', {
defaultSpace: config.storageDefaultSpaceFormatted,
})
}}</b>
</div>
</div>
</div>
</header>
</template>
<script>
import HardDriveIcon from 'vue-feather-icons/icons/HardDriveIcon'
import PageTitle from './Components/PageTitle'
import AuthButton from '../UI/Buttons/AuthButton'
import { CreditCardIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'IndexPageHeader',
components: {
PageTitle,
CreditCardIcon,
HardDriveIcon,
AuthButton,
},
computed: {
...mapGetters(['index', 'config']),
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.features {
display: flex;
margin-top: 35px;
.feature {
display: flex;
margin-right: 35px;
&:nth-child(1) {
path,
line,
polyline,
rect,
circle {
stroke: $yellow;
}
}
&:nth-child(2) {
path,
line,
polyline,
rect,
circle {
stroke: $purple;
}
}
&:last-child {
margin-right: 0;
}
.feature-title {
@include font-size(14);
font-weight: 700;
}
.feature-icon {
margin-right: 10px;
}
}
}
.main-header {
padding-top: 70px;
}
.sign-up-button {
margin-top: 65px;
display: block;
.button {
margin-left: 0;
margin-right: 0;
}
}
@media only screen and (max-width: 690px) {
.main-header {
padding-top: 50px;
}
.features {
display: block;
.feature {
margin-right: 0;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
}
.sign-up-button {
margin-top: 30px;
}
}
</style>

View File

@@ -1,176 +0,0 @@
<template>
<div
class="page-wrapper medium pricing"
v-if="!isEmpty && index.section_pricing_content === '1' && config.stripe_public_key"
>
<div id="pricing" class="page-title center">
<h1 class="title" v-html="index.pricing_title"></h1>
</div>
<PricingTables class="pricing-tables" @load="pricingLoaded" />
<div class="page-title center">
<h2 class="description">
{{ index.pricing_description }}
</h2>
<router-link class="sign-up-button" :to="{ name: 'SignUp' }">
<AuthButton class="button" icon="chevron-right" :text="$t('page_index.sign_up_button')" />
</router-link>
</div>
<cloud-icon size="800" class="cloud-bg"></cloud-icon>
<cloud-icon size="560" class="cloud-bg"></cloud-icon>
</div>
</template>
<script>
import PricingTables from './Components/PricingTables'
import AuthButton from '../UI/Buttons/AuthButton'
import { CloudIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'IndexPricingTables',
components: {
PricingTables,
AuthButton,
CloudIcon,
},
computed: {
...mapGetters(['index', 'config']),
},
data() {
return {
isEmpty: false,
}
},
methods: {
pricingLoaded(pricing) {
if (pricing.length === 0) this.isEmpty = true
},
},
}
</script>
<style lang="scss" scoped>
@import '../../../sass/vuefilemanager/landing-page';
@import '../../../sass/vuefilemanager/variables';
@import '../../../sass/vuefilemanager/mixins';
.pricing {
.cloud-bg {
z-index: 0;
path {
stroke: none;
fill: rgba($theme, 0.05);
}
&:first-of-type {
position: absolute;
top: 30px;
right: -130px;
transform: scale(-1, 1) rotate(-17deg);
}
&:last-of-type {
position: absolute;
bottom: 160px;
left: -230px;
transform: rotate(13deg);
}
}
}
.page-title {
position: relative;
z-index: 1;
&.center {
text-align: center;
.title {
margin-left: auto;
margin-right: auto;
}
.description {
margin-left: auto;
margin-right: auto;
}
}
.title {
max-width: 580px;
font-size: 48px;
font-weight: 800;
line-height: 1.25;
margin-bottom: 15px;
/deep/ span {
font-size: 48px;
}
}
.description {
max-width: 580px;
@include font-size(20);
font-weight: 500;
line-height: 1.6;
margin-bottom: 30px;
}
}
.pricing {
padding-top: 250px;
padding-bottom: 120px;
}
.pricing-tables {
margin-top: 50px;
margin-bottom: 60px;
position: relative;
z-index: 1;
}
.sign-up-button {
padding-top: 10px;
display: block;
}
@media only screen and (max-width: 1190px) {
.cloud-bg {
display: none;
}
.pricing {
padding-top: 150px;
padding-bottom: 60px;
}
}
@media only screen and (max-width: 960px) {
.page-title {
.title {
font-size: 28px;
line-height: 1.25;
margin-bottom: 15px;
/deep/ span {
font-size: 28px;
}
}
.description {
@include font-size(16);
line-height: 1.6;
margin-bottom: 30px;
}
}
.pricing {
padding-top: 50px;
padding-bottom: 120px;
}
}
</style>

View File

@@ -43,7 +43,7 @@
<ListInfoItem :title="$t('created_at')" :content="singleFile.data.attributes.created_at" />
<!--Location-->
<ListInfoItem v-if="$checkPermission(['master']) && !isTeamsHomepage" :title="$t('where')">
<ListInfoItem v-if="$checkPermission(['master'])" :title="$t('where')">
<div @click="$moveFileOrFolder(singleFile)" class="flex cursor-pointer items-center">
<b class="inline-block text-sm font-bold">
{{
@@ -84,16 +84,6 @@
</div>
</ListInfoItem>
<!--Author-->
<ListInfoItem v-if="canShowAuthor" :title="$t('author')">
<div class="mt-1.5 flex items-center">
<MemberAvatar :size="32" :member="singleFile.data.relationships.creator" />
<span class="ml-3 block text-sm font-bold">
{{ singleFile.data.relationships.creator.data.attributes.name }}
</span>
</div>
</ListInfoItem>
<!--Metadata-->
<ListInfoItem v-if="canShowMetaData" :title="$t('meta_data')">
<ImageMetaData />
@@ -108,7 +98,6 @@ import CopyShareLink from '../../Inputs/CopyShareLink'
import {Edit2Icon, LockIcon, UnlockIcon, EyeOffIcon} from 'vue-feather-icons'
import ImageMetaData from '../../UI/Others/ImageMetaData'
import TitlePreview from '../../UI/Labels/TitlePreview'
import TeamMembersPreview from '../../Teams/Components/TeamMembersPreview'
import ListInfoItem from '../../UI/List/ListInfoItem'
import MemberAvatar from '../../UI/Others/MemberAvatar'
import {mapGetters} from 'vuex'
@@ -116,7 +105,6 @@ import {mapGetters} from 'vuex'
export default {
name: 'InfoSidebar',
components: {
TeamMembersPreview,
FilePreviewDetail,
ImageMetaData,
CopyShareLink,
@@ -156,17 +144,6 @@ export default {
return title ? this.$t(title.label) : this.$t('can_download_file')
},
canShowAuthor() {
return (
this.$isThisRoute(this.$route, ['SharedWithMe', 'TeamFolders'])
&& this.clipboard[0].data.type !== 'folder'
&& this.clipboard[0].data.relationships.creator
&& this.user.data.id !== this.clipboard[0].data.relationships.creator.data.id
)
},
isTeamsHomepage() {
return this.$isThisRoute(this.$route, ['TeamFolders', 'SharedWithMe']) && !this.$route.params.id
},
},
}
</script>

View File

@@ -1,133 +0,0 @@
<template>
<div
class="hidden w-[300px] shrink-0 overflow-y-auto overflow-x-hidden px-2.5 pt-2 lg:block xl:w-[320px] 2xl:w-[360px]"
>
<!--Is empty clipboard-->
<div v-if="isEmpty" class="flex h-full items-center justify-center">
<div class="text-center">
<eye-off-icon size="22" class="vue-feather mb-3 inline-block text-gray-500" />
<small class="block text-xs text-gray-500">
{{ $t('nothing_to_preview') }}
</small>
</div>
</div>
<!--Multiple item selection-->
<TitlePreview
v-if="!isSingleFile && !isEmpty"
class="mb-6"
icon="check-square"
:title="$t('selected_multiple')"
:subtitle="this.clipboard.length + ' ' + $tc('items', this.clipboard.length)"
/>
<!--Single file preview-->
<div v-if="isSingleFile && !isEmpty">
<FilePreviewDetail />
<TitlePreview
class="mb-6"
:icon="clipboard[0].data.type"
:title="clipboard[0].data.attributes.name"
:subtitle="clipboard[0].data.attributes.mimetype"
/>
<!--Filesize-->
<ListInfoItem
v-if="singleFile.data.attributes.filesize"
:title="$t('size')"
:content="singleFile.data.attributes.filesize"
/>
<!--Created At-->
<ListInfoItem :title="$t('created_at')" :content="singleFile.data.attributes.created_at" />
<!--Location-->
<ListInfoItem :title="$t('where')">
<div @click="$moveFileOrFolder(singleFile)" class="flex cursor-pointer items-center">
<b class="inline-block text-sm font-bold">
{{
singleFile.data.relationships.parent
? singleFile.data.relationships.parent.data.attributes.name
: $getCurrentLocationName()
}}
</b>
<Edit2Icon size="10" class="ml-2" />
</div>
</ListInfoItem>
<!--Shared-->
<ListInfoItem
v-if="$checkPermission('master') && singleFile.data.relationships.shared"
:title="$t('shared')"
>
<div @click="$shareFileOrFolder(singleFile)" class="mb-2 flex cursor-pointer items-center">
<span class="inline-block text-sm font-bold">
{{ sharedInfo }}
</span>
<Edit2Icon size="10" class="ml-2" />
</div>
<div class="flex w-full items-center">
<lock-icon
v-if="isLocked"
@click="$shareFileOrFolder(singleFile)"
size="17"
class="hover-text-theme vue-feather cursor-pointer"
/>
<unlock-icon
v-if="!isLocked"
@click="$shareFileOrFolder(singleFile)"
size="17"
class="hover-text-theme vue-feather cursor-pointer"
/>
<CopyShareLink :item="singleFile" size="small" class="w-full pl-2.5" />
</div>
</ListInfoItem>
<!--Metadata-->
<ListInfoItem v-if="canShowMetaData" :title="$t('meta_data')">
<ImageMetaData />
</ListInfoItem>
</div>
</div>
</template>
<script>
import { Edit2Icon, LockIcon, UnlockIcon, EyeOffIcon } from 'vue-feather-icons'
import FilePreviewDetail from '../../Others/FilePreviewDetail'
import ListInfoItem from '../../UI/List/ListInfoItem'
import ImageMetaData from '../../UI/Others/ImageMetaData'
import TitlePreview from '../../UI/Labels/TitlePreview'
import { mapGetters } from 'vuex'
export default {
name: 'InfoSidebarUploadRequest',
components: {
FilePreviewDetail,
ImageMetaData,
TitlePreview,
ListInfoItem,
UnlockIcon,
EyeOffIcon,
Edit2Icon,
LockIcon,
},
computed: {
...mapGetters(['clipboard']),
isEmpty() {
return this.clipboard.length === 0
},
isSingleFile() {
return this.clipboard.length === 1
},
singleFile() {
return this.clipboard[0]
},
canShowMetaData() {
return (
this.clipboard[0].data.attributes.metadata && this.clipboard[0].data.attributes.metadata.ExifImageWidth
)
},
},
}
</script>

View File

@@ -13,48 +13,30 @@
/>
<PopoverItem name="desktop-create" side="left">
<OptionGroup
:title="$t('frequently_used')"
:title="$t('create')"
>
<OptionUpload
:title="$t('upload_files')"
type="file"
:class="{
'is-inactive': (isSharedWithMe && !canEdit) || canUploadInView || isTeamFolderHomepage || isSharedWithMeHomepage,
'is-inactive': canUploadInView,
}"
/>
<Option
@click.native="$createFolder"
:class="{
'is-inactive': (isSharedWithMe && !canEdit) || canCreateFolder || isTeamFolderHomepage || isSharedWithMeHomepage,
}"
:title="$t('create_folder')"
icon="folder-plus"
/>
</OptionGroup>
<OptionGroup :title="$t('others')">
<OptionUpload
:class="{
'is-inactive': (isSharedWithMe && !canEdit) || canUploadFolderInView || isTeamFolderHomepage || isSharedWithMeHomepage,
'is-inactive': canUploadFolderInView,
}"
:title="$t('upload_folder')"
type="folder"
/>
<Option
@click.stop.native="$openRemoteUploadPopup"
:title="$t('remote_upload')"
icon="remote-upload"
/>
<Option
@click.stop.native="$createTeamFolder"
:class="{ 'is-inactive': canCreateTeamFolder }"
:title="$t('create_team_folder')"
icon="users"
@click.native="$createFolder"
:class="{
'is-inactive': canCreateFolder,
}"
:title="$t('create_folder')"
icon="folder-plus"
/>
<Option
@click.native="$createFileRequest"
:title="$t('create_file_request')"
icon="upload-cloud"
/>
</OptionGroup>
</PopoverItem>
</PopoverWrapper>
@@ -64,53 +46,11 @@
<!--File Controls-->
<div class="ml-5 flex items-center xl:ml-8">
<!--Team Heads-->
<PopoverWrapper v-if="$isThisRoute($route, ['TeamFolders', 'SharedWithMe'])">
<TeamMembersButton
@click.stop.native="showTeamFolderMenu"
size="32"
class="cursor-pointer rounded-lg py-0.5 pl-2 pr-0.5 hover:bg-light-background dark:hover:bg-dark-foreground"
/>
<PopoverItem name="team-folder" side="left">
<TeamFolderPreview />
<OptionGroup v-if="$isThisRoute($route, ['TeamFolders'])" :title="$t('options')">
<Option
@click.native="$updateTeamFolder(teamFolder)"
:title="$t('edit_members')"
icon="rename"
/>
<Option
@click.native="$dissolveTeamFolder(teamFolder)"
:title="$t('dissolve_team')"
icon="trash"
/>
</OptionGroup>
<OptionGroup v-if="$isThisRoute($route, ['SharedWithMe'])" :title="$t('options')">
<Option
@click.native="$detachMeFromTeamFolder(teamFolder)"
:title="$t('leave_team_folder')"
icon="user-minus"
/>
</OptionGroup>
</PopoverItem>
</PopoverWrapper>
<!--Action buttons-->
<div v-if="!$isMobile()" class="flex items-center">
<ToolbarButton
v-if="canShowConvertToTeamFolder"
@click.native="$convertAsTeamFolder(clipboard[0])"
:class="{
'is-inactive': !canCreateTeamFolder,
}"
source="user-plus"
:action="$t('convert_into_team_folder')"
/>
<ToolbarButton
v-if="!$isThisRoute($route, ['SharedWithMe', 'Public'])"
v-if="!$isThisRoute($route, ['Public'])"
@click.native="$shareFileOrFolder(clipboard[0])"
:class="{
'is-inactive': canShareInView,
@@ -121,7 +61,7 @@
<ToolbarButton
@click.native="$moveFileOrFolder(clipboard[0])"
:class="{
'is-inactive': canMoveInView && !canEdit,
'is-inactive': canMoveInView,
}"
source="move"
:action="$t('move')"
@@ -129,7 +69,7 @@
<ToolbarButton
@click.native="$deleteFileOrFolder(clipboard[0])"
:class="{
'is-inactive': canDeleteInView && !canEdit,
'is-inactive': canDeleteInView,
}"
source="trash"
:action="$t('delete')"
@@ -163,8 +103,6 @@
</template>
<script>
import TeamMembersButton from '../../Teams/Components/TeamMembersButton'
import TeamFolderPreview from '../../Teams/Components/TeamFolderPreview'
import PopoverWrapper from '../../UI/Popover/PopoverWrapper'
import FileSortingOptions from '../../Menus/FileSortingOptions'
import PopoverItem from '../../UI/Popover/PopoverItem'
@@ -182,8 +120,6 @@ export default {
name: 'DesktopToolbar',
components: {
FileSortingOptions,
TeamMembersButton,
TeamFolderPreview,
SearchBarButton,
UploadProgress,
PopoverWrapper,
@@ -197,71 +133,34 @@ export default {
computed: {
...mapGetters([
'isVisibleNavigationBars',
'currentTeamFolder',
'currentFolder',
'sharedDetail',
'clipboard',
'user',
]),
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 false
},
teamFolder() {
return this.currentTeamFolder ? this.currentTeamFolder : this.clipboard[0]
},
isTeamFolderHomepage() {
return this.$isThisRoute(this.$route, ['TeamFolders']) && !this.$route.params.id
},
isSharedWithMe() {
return this.$isThisRoute(this.$route, ['SharedWithMe'])
},
isSharedWithMeHomepage() {
return this.$isThisRoute(this.$route, ['SharedWithMe']) && !this.$route.params.id
},
canCreateFolder() {
return !this.$isThisRoute(this.$route, ['Files', 'Public', 'TeamFolders', 'SharedWithMe'])
},
canShowConvertToTeamFolder() {
return this.$isThisRoute(this.$route, ['Files', 'MySharedItems'])
return !this.$isThisRoute(this.$route, ['Files', 'Public'])
},
canUploadInView() {
return !this.$isThisRoute(this.$route, ['Files', 'RecentUploads', 'Public', 'TeamFolders', 'SharedWithMe'])
return !this.$isThisRoute(this.$route, ['Files', 'RecentUploads', 'Public'])
},
canUploadFolderInView() {
return !this.$isThisRoute(this.$route, ['Files', 'Public', 'TeamFolders', 'SharedWithMe'])
return !this.$isThisRoute(this.$route, ['Files', 'Public'])
},
canDeleteInView() {
let routes = ['TeamFolders', 'SharedWithMe', 'RecentUploads', 'MySharedItems', 'Trash', 'Public', 'Files']
let routes = ['RecentUploads', 'MySharedItems', 'Trash', 'Public', 'Files']
return !this.$isThisRoute(this.$route, routes) || this.clipboard.length === 0
},
canMoveInView() {
let routes = ['SharedWithMe', 'RecentUploads', 'MySharedItems', 'Public', 'Files', 'TeamFolders']
let routes = ['RecentUploads', 'MySharedItems', 'Public', 'Files']
return !this.$isThisRoute(this.$route, routes) || this.clipboard.length === 0
},
canShareInView() {
let routes = ['TeamFolders', 'RecentUploads', 'MySharedItems', 'Public', 'Files']
let routes = ['RecentUploads', 'MySharedItems', 'Public', 'Files']
return !this.$isThisRoute(this.$route, routes) || this.clipboard.length > 1 || this.clipboard.length === 0
},
canCreateTeamFolder() {
return (
this.$isThisRoute(this.$route, ['MySharedItems', 'Files']) &&
this.clipboard.length === 1 &&
this.clipboard[0].data.type === 'folder'
)
},
},
methods: {
showTeamFolderMenu() {
if (this.teamFolder) events.$emit('popover:open', 'team-folder')
},
showCreateMenu() {
events.$emit('popover:open', 'desktop-create')
},

View File

@@ -1,144 +0,0 @@
<template>
<div class="hidden lg:block">
<div class="flex items-center justify-between py-3">
<NavigationBar />
<div class="flex items-center">
<!--I am Done-->
<div @click="uploadingDone" class="bg-theme-200 mr-6 flex cursor-pointer items-center rounded-lg py-1 pr-1 pl-4">
<b class="text-theme mr-3 text-sm leading-3">
{{ $t('tell_you_are_done', {name: uploadRequest.data.relationships.user.data.attributes.name}) }}
</b>
<MemberAvatar
:member="uploadRequest.data.relationships.user"
:size="34"
/>
</div>
<!--Create button-->
<PopoverWrapper>
<ToolbarButton
@click.stop.native="showCreateMenu"
source="cloud-plus"
:action="$t('create_something')"
/>
<PopoverItem name="desktop-create" side="left">
<OptionGroup :title="$t('frequently_used')">
<OptionUpload :title="$t('upload_files')" type="file" />
<Option
@click.native="$createFolder"
:title="$t('create_folder')"
icon="folder-plus"
/>
</OptionGroup>
<OptionGroup :title="$t('others')">
<OptionUpload :title="$t('upload_folder')" type="folder" />
<Option
@click.stop.native="$openRemoteUploadPopup"
:title="$t('remote_upload')"
icon="remote-upload"
/>
</OptionGroup>
</PopoverItem>
</PopoverWrapper>
<!--File Controls-->
<div v-if="!$isMobile()" class="ml-5 flex items-center xl:ml-8">
<ToolbarButton
@click.native="$moveFileOrFolder(clipboard[0])"
:class="{
'is-inactive': !canManipulate,
}"
source="move"
:action="$t('move')"
/>
<ToolbarButton
@click.native="$deleteFileOrFolder(clipboard[0])"
:class="{
'is-inactive': !canManipulate,
}"
source="trash"
:action="$t('delete')"
/>
</div>
<!--View Controls-->
<div class="ml-5 flex items-center xl:ml-8">
<PopoverWrapper>
<ToolbarButton
@click.stop.native="showSortingMenu"
source="preview-sorting"
:action="$t('sorting_view')"
/>
<PopoverItem name="desktop-sorting" side="left">
<FileSortingOptions />
</PopoverItem>
</PopoverWrapper>
<ToolbarButton
@click.native="$store.dispatch('fileInfoToggle')"
:action="$t('info_panel')"
source="info"
/>
</div>
</div>
</div>
<UploadProgress />
</div>
</template>
<script>
import PopoverWrapper from '../../UI/Popover/PopoverWrapper'
import FileSortingOptions from '../../Menus/FileSortingOptions'
import PopoverItem from '../../UI/Popover/PopoverItem'
import UploadProgress from '../../UI/Others/UploadProgress'
import NavigationBar from './NavigationBar'
import ToolbarButton from '../../UI/Buttons/ToolbarButton'
import MemberAvatar from "../../UI/Others/MemberAvatar"
import OptionUpload from '../../Menus/Components/OptionUpload'
import OptionGroup from '../../Menus/Components/OptionGroup'
import { events } from '../../../bus'
import { mapGetters } from 'vuex'
import Option from '../../Menus/Components/Option'
export default {
name: 'DesktopUploadRequestToolbar',
components: {
FileSortingOptions,
UploadProgress,
PopoverWrapper,
NavigationBar,
ToolbarButton,
MemberAvatar,
OptionUpload,
OptionGroup,
PopoverItem,
Option,
},
computed: {
...mapGetters(['isVisibleNavigationBars', 'currentTeamFolder', 'currentFolder', 'sharedDetail', 'clipboard', 'uploadRequest']),
canManipulate() {
return this.clipboard[0]
},
},
methods: {
uploadingDone() {
events.$emit('confirm:open', {
title: this.$t('closing_request_for_upload', {name: this.uploadRequest.data.relationships.user.data.attributes.name}),
message: this.$t('closing_request_for_upload_warn'),
action: {
id: this.$router.currentRoute.params.token,
operation: 'close-upload-request',
},
})
},
showCreateMenu() {
events.$emit('popover:open', 'desktop-create')
},
showSortingMenu() {
events.$emit('popover:open', 'desktop-sorting')
},
},
}
</script>

View File

@@ -5,12 +5,6 @@
<NavigationBar />
<div class="relative flex items-center">
<TeamMembersButton
v-if="$isThisRoute($route, ['TeamFolders', 'SharedWithMe'])"
size="28"
@click.stop.native="$showMobileMenu('team-menu')"
class="absolute right-10"
/>
<!--More Actions-->
<div class="flex items-center relative mr-[4px]">
@@ -24,8 +18,6 @@
</template>
<script>
import TeamMembersPreview from '../../Teams/Components/TeamMembersPreview'
import TeamMembersButton from '../../Teams/Components/TeamMembersButton'
import { MenuIcon } from 'vue-feather-icons'
import NavigationBar from './NavigationBar'
@@ -33,8 +25,6 @@ export default {
name: 'MobileToolBar',
components: {
NavigationBar,
TeamMembersPreview,
TeamMembersButton,
MenuIcon,
},
methods: {

View File

@@ -1,30 +0,0 @@
<template>
<div
class="sticky top-0 z-[19] block flex w-full items-center justify-between bg-white py-5 px-4 text-center dark:bg-dark-background lg:hidden"
>
<NavigationBar />
</div>
</template>
<script>
import TeamMembersPreview from '../../Teams/Components/TeamMembersPreview'
import TeamMembersButton from '../../Teams/Components/TeamMembersButton'
import { MenuIcon } from 'vue-feather-icons'
import NavigationBar from './NavigationBar'
export default {
name: 'MobileUploadRequestToolBar',
components: {
NavigationBar,
TeamMembersPreview,
TeamMembersButton,
MenuIcon,
},
methods: {
showMobileNavigation() {
this.$showMobileMenu('user-navigation')
this.$store.commit('DISABLE_MULTISELECT_MODE')
},
},
}
</script>

View File

@@ -1,24 +0,0 @@
<template>
<MenuMobile name="team-menu">
<TeamFolderPreview />
<MenuMobileGroup v-if="$slots.default">
<slot />
</MenuMobileGroup>
</MenuMobile>
</template>
<script>
import MenuMobileGroup from '../Mobile/MenuMobileGroup'
import TeamFolderPreview from '../Teams/Components/TeamFolderPreview'
import MenuMobile from '../Mobile/MenuMobile'
export default {
name: 'MobileTeamContextMenu',
components: {
TeamFolderPreview,
MenuMobileGroup,
MenuMobile,
},
}
</script>

View File

@@ -3,21 +3,6 @@
<!--User avatar-->
<UserHeadline v-if="!clickedSubmenu" class="p-5 pb-3" />
<!--User estimate-->
<div
v-if="config.subscriptionType === 'metered' && user && user.data.meta.usages && !clickedSubmenu"
class="block px-5 pt-2"
>
<div class="rounded-lg bg-light-background px-3 py-1.5 dark:bg-4x-dark-foreground">
<span class="text-sm font-semibold">
{{ $t('current_estimated_usage') }}
</span>
<span class="text-theme text-sm font-bold">
{{ user.data.meta.usages.costEstimate }}
</span>
</div>
</div>
<!--Go back button-->
<div v-if="clickedSubmenu" @click.stop="showSubmenu(undefined)" class="flex items-center p-5 pb-4">
<chevron-left-icon size="19" class="vue-feather text-theme mr-2 -ml-1" />
@@ -76,13 +61,6 @@
icon="hard-drive"
:is-hover-disabled="true"
/>
<Option
@click.native="goToRoute('Billing')"
v-if="config.subscriptionType !== 'none'"
:title="$t('billing')"
icon="cloud"
:is-hover-disabled="true"
/>
</OptionGroup>
<!--Submenu: Admin settings-->
@@ -105,16 +83,6 @@
icon="settings"
:is-hover-disabled="true"
/>
</OptionGroup>
<!--Submenu: Content settings-->
<OptionGroup v-if="clickedSubmenu === 'admin'">
<Option
@click.native="goToRoute('Pages')"
:title="$t('pages')"
icon="monitor"
:is-hover-disabled="true"
/>
<Option
@click.native="goToRoute('Language')"
:title="$t('languages')"
@@ -122,35 +90,6 @@
:is-hover-disabled="true"
/>
</OptionGroup>
<!--Submenu: Billing settings-->
<OptionGroup v-if="clickedSubmenu === 'admin' && config.subscriptionType !== 'none'">
<Option
@click.native="goToRoute('AppPayments')"
:title="$t('payments')"
icon="credit-card"
:is-hover-disabled="true"
/>
<Option
@click.native="goToRoute('Subscriptions')"
v-if="config.subscriptionType === 'fixed'"
:title="$t('subscriptions')"
icon="credit-card"
:is-hover-disabled="true"
/>
<Option
@click.native="goToRoute('Plans')"
:title="$t('plans')"
icon="database"
:is-hover-disabled="true"
/>
<Option
@click.native="goToRoute('Invoices')"
:title="$t('transactions')"
icon="file-text"
:is-hover-disabled="true"
/>
</OptionGroup>
</MenuMobileGroup>
</MenuMobile>
</template>

View File

@@ -1,161 +0,0 @@
<template>
<article
class="delay-[3000ms] duration-700 transition-all relative z-[11] mb-1.5 flex items-start space-x-4 rounded-xl p-2.5"
:class="{'dark:bg-4x-dark-foreground bg-light-background/80': isUnread}"
>
<gift-icon
v-if="notification.data.attributes.category === 'gift'"
size="22"
class="vue-feather text-theme shrink-0"
/>
<user-plus-icon
v-if="notification.data.attributes.category === 'team-invitation'"
size="22"
class="vue-feather text-theme shrink-0"
/>
<trending-up-icon
v-if="notification.data.attributes.category === 'subscription-created'"
size="22"
class="vue-feather text-theme shrink-0"
/>
<alert-triangle-icon
v-if="['billing-alert', 'insufficient-balance'].includes(notification.data.attributes.category)"
size="22"
class="vue-feather text-theme shrink-0"
/>
<upload-cloud-icon
v-if="['file-request', 'remote-upload-done'].includes(notification.data.attributes.category)"
size="22"
class="vue-feather text-theme shrink-0"
/>
<div>
<b class="mb-1.5 block font-extrabold">
{{ notification.data.attributes.title }}
</b>
<p class="mb-1.5 text-sm dark:text-gray-500">
{{ notification.data.attributes.description }}
</p>
<div class="flex items-center">
<!--<MemberAvatar class="mr-2" :size="22" :is-border="false" :member="user" />-->
<time class="block text-xs text-gray-400 dark:text-gray-400">
{{ notification.data.attributes.created_at }}
</time>
</div>
<!--Accept or decline team invitation-->
<div v-if="action && action.type === 'invitation'" class="flex items-center space-x-3 mt-4">
<div
@click="acceptAction"
class="relative flex cursor-pointer items-center rounded-xl py-1.5 px-2 transition-colors bg-green-100 dark:bg-green-900"
>
<refresh-cw-icon v-if="isAccepting" size="16" class="animate-spin left-0 right-0 mx-auto vue-feather text-green-600 dark:text-green-600 absolute justify-center" />
<check-icon size="16" class="vue-feather mr-1 text-green-600 dark:text-green-600" :class="{'opacity-0': isAccepting}" />
<span class="text-sm font-bold text-green-600 dark:text-green-600" :class="{'opacity-0': isAccepting}">
{{ $t('accept') }}
</span>
</div>
<div
@click="declineAction"
class="relative flex cursor-pointer items-center rounded-xl py-1.5 px-2 transition-colors bg-rose-100 dark:bg-rose-900"
>
<refresh-cw-icon v-if="isDeclining" size="16" class="animate-spin left-0 right-0 mx-auto vue-feather text-rose-600 dark:text-rose-600 absolute justify-center" />
<x-icon size="16" class="vue-feather mr-1 text-rose-600 dark:text-rose-600" :class="{'opacity-0': isDeclining}" />
<span class="text-sm font-bold text-rose-600 dark:text-rose-600 capitalize" :class="{'opacity-0': isDeclining}">
{{ $t('decline') }}
</span>
</div>
</div>
<!--Go to route-->
<router-link
@click.native="closeCenter"
v-if="action && action.type === 'route'"
:to="{ name: action.params.route, params: { id: action.params.id } }"
class="mt-4 flex items-center"
>
<span class="mr-2 whitespace-nowrap text-xs font-bold">
{{ action.params.button }}
</span>
<chevron-right-icon size="16" class="text-theme vue-feather" />
</router-link>
</div>
</article>
</template>
<script>
import { RefreshCwIcon, TrendingUpIcon, GiftIcon, CheckIcon, XIcon, MailIcon, UserPlusIcon, UploadCloudIcon, ChevronRightIcon, AlertTriangleIcon } from 'vue-feather-icons'
import MemberAvatar from '../../UI/Others/MemberAvatar'
import {events} from "../../../bus";
export default {
name: 'Notification',
props: ['notification'],
components: {
MemberAvatar,
AlertTriangleIcon,
ChevronRightIcon,
UploadCloudIcon,
TrendingUpIcon,
RefreshCwIcon,
UserPlusIcon,
CheckIcon,
GiftIcon,
MailIcon,
XIcon,
},
computed: {
action() {
return this.notification.data.attributes.action
},
},
data() {
return {
isUnread: false,
isAccepting: false,
isDeclining: false,
}
},
methods: {
acceptAction() {
this.isAccepting = true
axios.put(`/api/teams/invitations/${this.notification.data.attributes.action.params.id}`)
.then(() => {
this.$store.commit('CLEAR_NOTIFICATION_ACTION_DATA', this.notification.data.id)
events.$emit('toaster', {
type: 'success',
message: this.$t('you_accepted_invitation'),
})
})
.finally(() => this.isAccepting = false)
},
declineAction() {
this.isDeclining = true
axios.delete(`/api/teams/invitations/${this.notification.data.attributes.action.params.id}`)
.then(() => {
this.$store.commit('CLEAR_NOTIFICATION_ACTION_DATA', this.notification.data.id)
events.$emit('toaster', {
type: 'success',
message: this.$t('you_decline_invitation'),
})
})
.finally(() => this.isDeclining = false)
},
closeCenter() {
this.$store.commit('CLOSE_NOTIFICATION_CENTER')
this.$closePopup()
},
},
created() {
this.isUnread = this.notification.data.attributes.read_at === null
setTimeout(() => this.isUnread = false, 1000)
}
}
</script>

View File

@@ -1,25 +0,0 @@
<template>
<div class="relative button-icon inline-block cursor-pointer rounded-xl p-3">
<bell-icon size="18" class="vue-feather dark:text-gray-100 transition" :class="{'rotate-[30deg]': notificationCount}" />
<span v-if="notificationCount" class="absolute z-[9] right-1.5 bottom-1.5 flex items-center justify-center w-4 h-4 bg-theme text-white rounded-full text-xs font-bold">
{{ notificationCount }}
</span>
<span v-if="notificationCount" class="animate-ping absolute z-[8] right-1.5 bottom-1.5 flex items-center justify-center w-4 h-4 bg-theme text-white rounded-full text-xs font-bold"></span>
</div>
</template>
<script>
import {mapGetters} from "vuex";
import {BellIcon} from "vue-feather-icons"
export default {
name: 'NotificationBell',
components: {
BellIcon,
},
computed: {
...mapGetters([
'notificationCount'
]),
}
}
</script>

View File

@@ -1,101 +0,0 @@
<template>
<transition name="popup">
<div class="fixed popup z-20 top-[27px] bottom-[27px] left-20 w-[360px]">
<!--Triangle-->
<div class="z-20 absolute left-0 top-[64px] w-4 translate-x-[-15px] overflow-hidden inline-block" :class="{'!top-[102px]': config.subscriptionType === 'metered'}">
<div class="h-12 -rotate-45 transform origin-top-right dark:bg-2x-dark-foreground bg-white bg-opacity-80 backdrop-blur-2xl"></div>
</div>
<div v-click-outside="clickOutside" class="dark:bg-2x-dark-foreground bg-white dark:bg-opacity-80 dark:backdrop-blur-2xl bg-opacity-80 backdrop-blur-2xl shadow-xl rounded-xl text-left p-3 overflow-y-auto max-h-full">
<!--Title-->
<b class="dark:text-gray-200 text-xl font-extrabold px-2.5 mb-2.5 block">
{{ $t('notification_center') }}
</b>
<div class="px-2.5">
<MobileActionButton v-if="readNotifications.length || unreadNotifications.length" @click.native="$store.dispatch('deleteAllNotifications')" icon="check-square" class="mb-2 dark:!bg-4x-dark-foreground">
{{ $t('clear_all') }}
</MobileActionButton>
<p v-if="!readNotifications.length && !unreadNotifications.length" class="text-sm mt-8">
{{ $t("not_any_notifications") }}
</p>
</div>
<b v-if="unreadNotifications.length" class="dark-text-theme mt-1.5 block px-2.5 mb-2.5 text-xs text-gray-400">
{{ $t('unread') }}
</b>
<Notification :notification="notification" v-for="notification in unreadNotifications" :key="notification.id" />
<b v-if="readNotifications.length" class="dark-text-theme mt-2.5 block px-2.5 mb-2.5 text-xs text-gray-400">
{{ $t('read') }}
</b>
<Notification :notification="notification" v-for="notification in readNotifications" :key="notification.id" />
</div>
</div>
</transition>
</template>
<script>
import MobileActionButton from "../UI/Buttons/MobileActionButton"
import Notification from "./Components/Notification"
import vClickOutside from 'v-click-outside'
import {mapGetters} from "vuex";
export default {
name: 'NotificationCenter',
components: {
MobileActionButton,
Notification
},
directives: {
clickOutside: vClickOutside.directive
},
computed: {
...mapGetters([
'user', 'config', 'isVisibleNotificationCenter',
]),
readNotifications() {
return this.user.data.relationships.readNotifications.data
},
unreadNotifications() {
return this.user.data.relationships.unreadNotifications.data
}
},
methods: {
clickOutside() {
if (this.isVisibleNotificationCenter)
this.$store.commit('CLOSE_NOTIFICATION_CENTER')
},
},
created() {
this.$store.dispatch('readAllNotifications')
}
}
</script>
<style lang="scss" scoped>
.popup-leave-active {
animation: popup-slide-in 0.15s ease reverse;
}
.popup-enter-active {
animation: popup-slide-in 0.25s 0.1s ease both;
}
@keyframes popup-slide-in {
0% {
opacity: 0;
transform: translateY(50px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -1,94 +0,0 @@
<template>
<PopupWrapper name="notifications-mobile">
<!--Title-->
<PopupHeader :title="$t('notifications')" icon="bell" />
<!--Content-->
<PopupContent v-if="readNotifications && unreadNotifications">
<MobileActionButton
v-if="readNotifications.length || unreadNotifications.length"
@click.native="$store.dispatch('deleteAllNotifications')"
icon="check-square"
class="mb-2 dark:!bg-4x-dark-foreground"
>
{{ $t('clear_all') }}
</MobileActionButton>
<p v-if="!readNotifications.length && !unreadNotifications.length" class="text-sm text-gray-500">
{{ $t("not_any_notifications") }}
</p>
<b
v-if="unreadNotifications.length"
class="dark-text-theme mt-1.5 mb-2.5 block px-2.5 text-xs text-gray-400"
>
{{ $t('unread') }}
</b>
<Notification
:notification="notification"
v-for="notification in unreadNotifications"
:key="notification.id"
/>
<b v-if="readNotifications.length" class="dark-text-theme mt-2.5 mb-2.5 block px-2.5 text-xs text-gray-400">
{{ $t('read') }}
</b>
<Notification
:notification="notification"
v-for="notification in readNotifications"
:key="notification.id"
/>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('close') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import MobileActionButton from '../UI/Buttons/MobileActionButton'
import Notification from './Components/Notification'
import ButtonBase from '../UI/Buttons/ButtonBase'
import PopupWrapper from '../Popups/Components/PopupWrapper'
import PopupActions from '../Popups/Components/PopupActions'
import PopupContent from '../Popups/Components/PopupContent'
import PopupHeader from '../Popups/Components/PopupHeader'
import vClickOutside from 'v-click-outside'
import { mapGetters } from 'vuex'
export default {
name: 'NotificationsPopup',
components: {
MobileActionButton,
Notification,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
},
directives: {
clickOutside: vClickOutside.directive,
},
computed: {
...mapGetters(['user', 'config']),
readNotifications() {
return this.user?.data.relationships.readNotifications.data
},
unreadNotifications() {
return this.user?.data.relationships.unreadNotifications.data
},
},
methods: {
clickOutside() {
if (this.isVisibleNotificationCenter) this.$store.commit('CLOSE_NOTIFICATION_CENTER')
},
},
}
</script>

View File

@@ -1,164 +0,0 @@
<template>
<PopupWrapper name="remote-upload">
<PopupHeader :title="$t('upload_files_remotely')" icon="remote-upload" />
<PopupContent>
<ValidationObserver @submit.prevent ref="createForm" v-slot="{ invalid }" tag="form">
<ValidationProvider tag="div" mode="passive" name="Remote Links" rules="required" v-slot="{ errors }">
<AppInputText
:title="$t('remote_links')"
:description="$t('remote_links_help')"
:error="errors[0]"
:is-last="true"
>
<textarea
v-model="links"
class="focus-border-theme input-dark whitespace-nowrap"
rows="6"
:placeholder="$t('paste_remote_links_here')"
:class="{ '!border-rose-600': errors[0] }"
ref="textarea"
>
</textarea>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</PopupContent>
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('cancel') }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="upload" button-style="theme" :loading="loading">
{{ $t('upload') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from '../Popups/Components/PopupWrapper'
import PopupContent from '../Popups/Components/PopupContent'
import PopupActions from '../Popups/Components/PopupActions'
import PopupHeader from '../Popups/Components/PopupHeader'
import AppInputText from '../Forms/Layouts/AppInputText'
import { required } from 'vee-validate/dist/rules'
import ButtonBase from '../UI/Buttons/ButtonBase'
import { events } from '../../bus'
import i18n from "../../i18n";
export default {
name: 'RemoteUploadPopup',
components: {
ValidationProvider,
ValidationObserver,
required,
PopupWrapper,
PopupContent,
PopupHeader,
PopupActions,
AppInputText,
ButtonBase,
},
data() {
return {
links: undefined,
loading: false,
}
},
methods: {
async upload() {
// Validate fields
const isValid = await this.$refs.createForm.validate()
if (!isValid) return
this.loading = true
this.urls = this.links
.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, "")
.split(/\r?\n/)
// If demo, return success message
if (this.$store.getters.config.isDemo && this.$store.getters.user.data.attributes.email === 'ho**@hi5ve.digital') {
events.$emit('toaster', {
type: 'success',
message: i18n.t('remote_download_finished'),
})
events.$emit('popup:close')
return
}
// If broadcasting
if (this.$store.getters.isBroadcasting) {
this.$store.commit('UPDATE_REMOTE_UPLOAD_QUEUE', {
progress: {
total: this.urls.length,
processed: 0,
failed: 0,
}
})
}
// Get route
let route = {
RequestUpload: `/api/upload-request/${this.$router.currentRoute.params.token}/upload/remote`,
Public: `/api/editor/upload/remote/${this.$router.currentRoute.params.token}`,
}[this.$router.currentRoute.name] || '/api/upload/remote'
let parentId = this.$store.getters.currentFolder
? this.$store.getters.currentFolder.data.id
: undefined
axios
.post(route, {
urls: this.urls,
parent_id: parentId,
})
.then(() => {
// If broadcasting is not set
if (!this.$store.getters.isBroadcasting) {
// Reload data
this.$getDataByLocation()
events.$emit('toaster', {
type: 'success',
message: i18n.t('remote_download_finished'),
})
}
events.$emit('popup:close')
})
.catch((error) => {
if (error.response.status === 422) {
this.$refs.createForm.setErrors({
'Remote Links': error.response.data.message,
})
}
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
.finally(() => {
this.loading = false
})
},
},
mounted() {
events.$on('popup:open', (args) => {
if (args.name !== 'remote-upload') return
this.links = undefined
this.$nextTick(() => {
setTimeout(() => this.$refs.textarea.focus(), 100)
})
})
},
}
</script>

View File

@@ -1,54 +0,0 @@
<template>
<transition name="popup">
<div v-if="remoteUploadQueue" class="fixed left-0 right-0 bottom-8 text-center select-none pointer-events-none z-10">
<div class="relative inline-block rounded-lg overflow-hidden bg-theme shadow-lg px-3 py-2">
<div class="flex items-center">
<RefreshCwIcon size="14" class="vue-feather text-white animate-[spin_2s_linear_infinite] z-10 relative" />
<span class="text-xs font-bold text-white z-10 relative ml-2 dark:text-black">
{{ this.$t('remote_upload_progress', {processed: remoteUploadQueue.processed, total: remoteUploadQueue.total}) }}{{ remoteUploadQueue.failed > 0 ? ", " + this.$t('remote_upload_failed_count', {count: remoteUploadQueue.failed}) : '' }}
</span>
</div>
<span class="absolute w-full h-full top-0 bottom-0 left-0 right-0 block bg-theme brightness-125 animate-[pulse_3s_ease-in-out_infinite] z-[5]"></span>
<span class="absolute w-full h-full top-0 bottom-0 left-0 right-0 block bg-theme z-0"></span>
</div>
</div>
</transition>
</template>
<script>
import {RefreshCwIcon} from "vue-feather-icons";
import {mapGetters} from "vuex";
export default {
name: 'RemoteUploadProgress',
components: {
RefreshCwIcon,
},
computed: {
...mapGetters([
'remoteUploadQueue'
]),
},
}
</script>
<style lang="scss">
.popup-leave-active {
animation: popup-slide-in 0.15s ease reverse;
}
.popup-enter-active {
animation: popup-slide-in 0.25s 0.1s ease both;
}
@keyframes popup-slide-in {
0% {
opacity: 0;
transform: translateY(100px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@@ -7,26 +7,6 @@
<div v-if="user" class="mb-auto text-center">
<MemberAvatar class="mx-auto" :size="44" :is-border="false" :member="user" />
<!--Usage-->
<div
v-if="config.subscriptionType === 'metered' && user.data.meta.usages"
class="mt-2.5 text-center leading-3"
>
<b class="text-theme block text-xs font-bold leading-3">
{{ user.data.meta.usages.costEstimate }}
</b>
<span class="text-xs text-gray-500">
{{ $t('usage') }}
</span>
</div>
<!--Navigation-->
<div class="mt-2 relative">
<NotificationBell @click.native="$store.commit('TOGGLE_NOTIFICATION_CENTER')" class="hover:bg-light-300 dark:hover:bg-4x-dark-foreground" />
</div>
<NotificationCenter v-if="isVisibleNotificationCenter" />
<!--Navigation-->
<div class="mt-6">
<router-link
@@ -75,14 +55,10 @@
import MemberAvatar from '../UI/Others/MemberAvatar'
import {mapGetters} from 'vuex'
import {HardDriveIcon, MoonIcon, PowerIcon, SettingsIcon, SunIcon, UserIcon,} from 'vue-feather-icons'
import NotificationCenter from "../Notifications/NotificationCenter"
import NotificationBell from "../Notifications/Components/NotificationBell";
export default {
name: 'SidebarNavigation',
components: {
NotificationBell,
NotificationCenter,
HardDriveIcon,
SettingsIcon,
MemberAvatar,
@@ -92,7 +68,7 @@ export default {
SunIcon,
},
computed: {
...mapGetters(['isVisibleNavigationBars', 'isDarkMode', 'config', 'user', 'isVisibleNotificationCenter']),
...mapGetters(['isVisibleNavigationBars', 'isDarkMode', 'config', 'user']),
navigation() {
if (this.user.data.attributes.role === 'admin') {
return [
@@ -133,11 +109,6 @@ export default {
]
},
},
data() {
return {
isNotificationCenter: false,
}
},
methods: {
isSection(section) {
return this.$route.matched[0].name === section

View File

@@ -99,11 +99,6 @@
size="18"
class="vue-feather text-theme"
/>
<credit-card-icon
v-if="result.action.value === 'AppPayments'"
size="18"
class="vue-feather text-theme"
/>
<home-icon
v-if="result.action.value === 'Files'"
size="18"
@@ -114,23 +109,13 @@
size="18"
class="vue-feather text-theme"
/>
<database-icon
v-if="['CreateFixedPlan', 'CreateMeteredPlan'].includes(result.action.value)"
size="18"
class="vue-feather text-theme"
/>
<user-plus-icon
v-if="result.action.value === 'UserCreate'"
size="18"
class="vue-feather text-theme"
/>
<users-icon
v-if="['TeamFolders', 'Users'].includes(result.action.value)"
size="18"
class="vue-feather text-theme"
/>
<user-check-icon
v-if="result.action.value === 'SharedWithMe'"
v-if="['Users'].includes(result.action.value)"
size="18"
class="vue-feather text-theme"
/>
@@ -139,41 +124,16 @@
size="18"
class="vue-feather text-theme"
/>
<link2-icon
v-if="result.action.value === 'remote-upload'"
size="18"
class="vue-feather text-theme"
/>
<upload-cloud-icon
v-if="result.action.value === 'RecentUploads'"
size="18"
class="vue-feather text-theme"
/>
<file-text-icon
v-if="['Invoices', 'Invoice'].includes(result.action.value)"
size="18"
class="vue-feather text-theme"
/>
<database-icon
v-if="result.action.value === 'Plans'"
size="18"
class="vue-feather text-theme"
/>
<dollar-sign-icon
v-if="['Subscriptions', 'Billing'].includes(result.action.value)"
size="18"
class="vue-feather text-theme"
/>
<globe-icon
v-if="result.action.value === 'Language'"
size="18"
class="vue-feather text-theme"
/>
<monitor-icon
v-if="result.action.value === 'Pages'"
size="18"
class="vue-feather text-theme"
/>
<box-icon
v-if="result.action.value === 'Dashboard'"
size="18"
@@ -214,16 +174,6 @@
size="18"
class="vue-feather text-theme"
/>
<folder-plus-icon
v-if="result.action.value === 'create-team-folder'"
size="18"
class="vue-feather text-theme"
/>
<upload-cloud-icon
v-if="result.action.value === 'create-file-request'"
size="18"
class="vue-feather text-theme"
/>
<b class="ml-3.5 text-sm font-bold">
{{ result.title }}
@@ -397,20 +347,6 @@ export default {
value: 'AppOthers',
},
},
{
title: this.$t('go_to_payments'),
action: {
type: 'route',
value: 'AppPayments',
},
},
{
title: this.$t('go_to_pages'),
action: {
type: 'route',
value: 'Pages',
},
},
{
title: this.$t('go_to_languages'),
action: {
@@ -425,20 +361,6 @@ export default {
value: 'Users',
},
},
{
title: this.$t('show_all_plans'),
action: {
type: 'route',
value: 'Plans',
},
},
{
title: this.$t('show_transactions'),
action: {
type: 'route',
value: 'Invoices',
},
},
{
title: this.$t('application_settings'),
action: {
@@ -446,13 +368,6 @@ export default {
value: 'AppOthers',
},
},
{
title: this.$t('login_registration_settings'),
action: {
type: 'route',
value: 'AppSignInUp',
},
},
{
title: this.$t('appearance_settings'),
action: {
@@ -460,20 +375,6 @@ export default {
value: 'AppAppearance',
},
},
{
title: this.$t('adsense_settings'),
action: {
type: 'route',
value: 'AppAdsense',
},
},
{
title: this.$t('homepage_settings'),
action: {
type: 'route',
value: 'AppIndex',
},
},
{
title: this.$t('environment_settings'),
action: {
@@ -519,20 +420,6 @@ export default {
value: 'Trash',
},
},
{
title: this.$t('go_to_team_folders'),
action: {
type: 'route',
value: 'TeamFolders',
},
},
{
title: this.$t('go_to_shared_with_me'),
action: {
type: 'route',
value: 'SharedWithMe',
},
},
]
let adminActions = [
@@ -567,13 +454,6 @@ export default {
value: 'Storage',
},
},
{
title: this.$t('show_billing'),
action: {
type: 'route',
value: 'Billing',
},
},
{
title: this.$t('empty_your_trash'),
action: {
@@ -590,29 +470,7 @@ export default {
},
]
let createList = [
{
title: this.$t('create_team_folder'),
action: {
type: 'function',
value: 'create-team-folder',
},
},
{
title: this.$t('create_file_request'),
action: {
type: 'function',
value: 'create-file-request',
},
},
{
title: this.$t('remote_upload'),
action: {
type: 'function',
value: 'remote-upload',
},
},
]
let createList = []
let functionList = [
{
@@ -656,31 +514,6 @@ export default {
// Return commands for logged admin
if (this.isAdmin) {
// Available only for fixed subscription
if (this.config.subscriptionType === 'fixed') {
adminLocations.push({
title: this.$t('show_all_subscriptions'),
action: {
type: 'route',
value: 'Subscriptions',
},
})
}
// Available only when is metered billing and plan doesnt exist or when is fixed billing
if (
(this.config.subscriptionType === 'metered' && !this.config.isCreatedMeteredPlan) ||
this.config.subscriptionType === 'fixed'
) {
adminActions.push({
title: this.$t('create_plan'),
action: {
type: 'route',
value: this.config.subscriptionType === 'fixed' ? 'CreateFixedPlan' : 'CreateMeteredPlan',
},
})
}
return [].concat.apply(
[],
[functionList, createList, userSettings, fileLocations, adminLocations, adminActions]
@@ -819,18 +652,6 @@ export default {
if (arg.action.value === 'empty-trash') {
this.$emptyTrashQuietly()
}
if (arg.action.value === 'create-team-folder') {
this.$createTeamFolder()
}
if (arg.action.value === 'create-file-request') {
this.$createFileRequest()
}
if (arg.action.value === 'remote-upload') {
this.$openRemoteUploadPopup()
}
}
this.exitSpotlight()
@@ -854,15 +675,6 @@ export default {
id: file.data.id,
},
})
} else if (file.data.attributes.isTeamFolder) {
let route = file.data.relationships.user.data.id === this.user.data.id
? 'TeamFolders'
: 'SharedWithMe'
this.$router.push({
name: route,
params: { id: file.data.id },
})
} else {
this.$router.push({
name: 'Files',

View File

@@ -1,98 +0,0 @@
<template>
<tr class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5">
<td class="py-5 pr-3 md:pr-1">
<span class="text-sm font-bold">
{{ row.data.attributes.note }}
</span>
</td>
<td v-if="user" class="whitespace-nowrap px-3 md:px-1">
<div v-if="row.data.relationships.user" class="flex items-center">
<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>
</div>
<span v-if="!row.data.relationships.user" class="text-xs font-bold text-gray-500">
{{ $t('user_was_deleted') }}
</span>
</td>
<td class="px-3 md:px-1">
<ColorLabel class="capitalize" :color="$getTransactionStatusColor(row.data.attributes.status)">
{{ $t(row.data.attributes.status) }}
</ColorLabel>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold" :class="$getTransactionTypeTextColor(row.data.attributes.type)">
{{ $getTransactionMark(row.data.attributes.type) + row.data.attributes.price }}
</span>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.created_at }}
</span>
</td>
<td class="px-3 md:px-1">
<div class="w-28">
<img
class="inline-block max-h-5"
:src="$getPaymentLogo(row.data.attributes.driver)"
:alt="row.data.attributes.driver"
/>
</div>
</td>
<td class="pl-3 text-right md:pl-1">
<div class="inline-block">
<a
:href="$getInvoiceLink(row.data.id)"
target="_blank"
class="inline-block flex h-8 w-8 cursor-pointer items-center justify-center rounded-md bg-light-background transition-colors hover:bg-purple-100 dark:bg-2x-dark-foreground"
>
<FileTextIcon size="15" class="opacity-75" />
</a>
</div>
</td>
</tr>
</template>
<script>
import MemberAvatar from '../UI/Others/MemberAvatar'
import MeteredTransactionDetailRow from './MeteredTransactionDetailRow'
import ColorLabel from '../UI/Labels/ColorLabel'
import { EyeIcon, FileTextIcon } from 'vue-feather-icons'
export default {
name: 'FixedTransactionRow',
components: {
MeteredTransactionDetailRow,
MemberAvatar,
FileTextIcon,
ColorLabel,
EyeIcon,
},
props: {
row: {},
user: {
type: Boolean,
default: false,
},
},
data() {
return {
showedTransactionDetailById: undefined,
}
},
methods: {
showTransactionDetail(id) {
if (this.showedTransactionDetailById === id) this.showedTransactionDetailById = undefined
else this.showedTransactionDetailById = id
},
},
}
</script>

View File

@@ -1,36 +0,0 @@
<template>
<tr>
<td colspan="10" class="overflow-hidden rounded-lg py-2">
<div
class="flex items-center justify-between border-b border-dashed border-light py-2 dark:border-opacity-5"
v-for="(usage, i) in row.data.attributes.metadata"
:key="i"
>
<div class="w-2/4 leading-none">
<b class="text-sm font-bold leading-none">
{{ $t(usage.feature) }}
</b>
<small class="hidden pt-2 text-xs leading-none text-gray-500 sm:block">
{{ $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>
</td>
</tr>
</template>
<script>
export default {
name: 'MeteredTransactionDetailRow',
props: ['row'],
}
</script>

View File

@@ -1,110 +0,0 @@
<template>
<tr class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5">
<td class="py-5 pr-3 md:pr-1">
<span class="text-sm font-bold">
{{ row.data.attributes.note }}
</span>
</td>
<td v-if="user" class="whitespace-nowrap px-3 md:px-1">
<div v-if="row.data.relationships.user" class="flex items-center">
<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>
</div>
<span v-if="!row.data.relationships.user" class="text-xs font-bold text-gray-500">
{{ $t('user_was_deleted') }}
</span>
</td>
<td class="px-3 md:px-1">
<ColorLabel class="capitalize" :color="$getTransactionStatusColor(row.data.attributes.status)">
{{ $t(row.data.attributes.status) }}
</ColorLabel>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold capitalize">
{{ $t(row.data.attributes.type) }}
</span>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold" :class="$getTransactionTypeTextColor(row.data.attributes.type)">
{{ $getTransactionMark(row.data.attributes.type) + row.data.attributes.price }}
</span>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.created_at }}
</span>
</td>
<td class="px-3 md:px-1">
<div class="w-28">
<img
class="inline-block max-h-5"
:src="$getPaymentLogo(row.data.attributes.driver)"
:alt="row.data.attributes.driver"
/>
</div>
</td>
<td class="pl-3 text-right md:pl-1">
<div v-if="row.data.attributes.metadata" class="flex w-full justify-end space-x-2">
<div
@click="$emit('showDetail', row.data.id)"
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground"
>
<EyeIcon size="15" class="opacity-75" />
</div>
<a
:href="$getInvoiceLink(row.data.id)"
target="_blank"
class="flex h-8 w-8 cursor-pointer items-center justify-center rounded-md bg-light-background transition-colors hover:bg-purple-100 dark:bg-2x-dark-foreground"
>
<FileTextIcon size="15" class="opacity-75" />
</a>
</div>
<div v-else>-</div>
</td>
</tr>
</template>
<script>
import MemberAvatar from '../UI/Others/MemberAvatar'
import MeteredTransactionDetailRow from './MeteredTransactionDetailRow'
import ColorLabel from '../UI/Labels/ColorLabel'
import { EyeIcon, FileTextIcon } from 'vue-feather-icons'
export default {
name: 'MeteredTransactionRow',
components: {
MeteredTransactionDetailRow,
MemberAvatar,
FileTextIcon,
ColorLabel,
EyeIcon,
},
props: {
row: {},
user: {
type: Boolean,
default: false,
},
},
data() {
return {
showedTransactionDetailById: undefined,
}
},
methods: {
showTransactionDetail(id) {
if (this.showedTransactionDetailById === id) this.showedTransactionDetailById = undefined
else this.showedTransactionDetailById = id
},
},
}
</script>

View File

@@ -1,59 +0,0 @@
<template>
<div
class="flex items-center justify-between rounded-lg bg-light-background py-3 px-2 dark:bg-2x-dark-foreground md:px-4"
>
<div class="flex items-center">
<img :src="`/assets/gateways/${card.data.attributes.brand}.svg`" alt="" class="mr-3 h-5 rounded" />
<b class="whitespace-nowrap text-sm font-bold capitalize leading-none">
{{ card.data.attributes.brand }}
{{ card.data.attributes.last4 }}
</b>
</div>
<b class="text-sm font-bold leading-none"> {{ $t('expires') }} {{ card.data.attributes.expiration }} </b>
<Trash2Icon @click="deleteCreditCard(card.data.id)" size="15" class="cursor-pointer" />
</div>
</template>
<script>
import { Trash2Icon } from 'vue-feather-icons'
import { events } from '../../bus'
import axios from 'axios'
export default {
name: 'PaymentCard',
components: {
Trash2Icon,
},
props: ['card'],
methods: {
deleteCreditCard(id) {
events.$emit('confirm:open', {
title: this.$t('want_to_delete_card_title'),
message: this.$t('want_to_delete_card_description'),
action: {
id: id,
operation: 'delete-credit-card',
},
})
},
},
created() {
events.$on('action:confirmed', (data) => {
if (data.operation === 'delete-credit-card')
axios
.delete(`/api/stripe/credit-cards/${data.id}`)
.then(() => {
this.$store.dispatch('getAppData')
events.$emit('toaster', {
type: 'success',
message: this.$t('credit_card_deleted'),
})
})
.catch(() => this.$isSomethingWrong())
})
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -1,19 +0,0 @@
<template>
<div class="flex items-center justify-between border-b border-dashed border-light py-4 dark:border-opacity-5">
<div>
<img :src="$getPaymentLogo(driver)" :alt="driver" class="h-6" />
<small class="block pt-2 text-xs leading-4 dark:text-gray-500 text-gray-500">
{{ description }}
</small>
</div>
<div v-if="$slots.default" class="bg-theme-200 relative inline-block rounded-lg px-3 py-1">
<slot />
</div>
</div>
</template>
<script>
export default {
name: 'PaymentMethod',
props: ['description', 'driver'],
}
</script>

View File

@@ -1,45 +0,0 @@
<template>
<div
class="block cursor-pointer select-none rounded-lg py-3 px-4"
:class="{
'bg-light-background dark:bg-2x-dark-foreground': isSelected,
}"
>
<div class="mb-1.5 flex items-center justify-between">
<CheckBox :is-clicked="isSelected" />
<b class="flex-1 pl-4 text-left text-lg">
{{ plan.data.attributes.name }}
</b>
<span
class="text-theme bg-theme-100 ml-9 inline-block whitespace-nowrap rounded-xl py-1 px-2 text-sm font-extrabold"
>
{{ plan.data.attributes.price }} /
{{ $t(`interval.${plan.data.attributes.interval}`) }}
</span>
</div>
<ul class="ml-9 mb-3">
<li class="mb-1.5 flex items-center" v-for="(value, key, i) in plan.data.attributes.features" :key="i">
<CheckIcon size="12" class="svg-stroke-theme" />
<small class="pl-1.5 text-xs font-bold text-gray-600" v-if="value !== -1">
{{ $t(key === 'max_team_members' ? 'max_team_members_total' : key, { value: value }) }}
</small>
<small class="pl-1.5 text-xs font-bold text-gray-600" v-if="value === -1">
{{ $t(`${key}.unlimited`) }}
</small>
</li>
</ul>
</div>
</template>
<script>
import { CheckIcon } from 'vue-feather-icons'
import CheckBox from '../Inputs/CheckBox'
export default {
name: 'PlanDetail',
components: {
CheckIcon,
CheckBox,
},
props: ['isSelected', 'plan'],
}
</script>

View File

@@ -1,37 +0,0 @@
<template>
<div class="mb-2 text-right">
<label
:class="{ 'text-gray-400': !isSelectedYearlyPlans }"
class="cursor-pointer text-xs font-bold"
>
{{ $t('billed_annually') }}
</label>
<div class="relative inline-block w-12 select-none align-middle">
<SwitchInput
class="scale-75 transform"
v-model="isSelectedYearlyPlans"
:state="isSelectedYearlyPlans"
/>
</div>
</div>
</template>
<script>
import SwitchInput from '../Inputs/SwitchInput'
export default {
name: 'PlanPeriodSwitcher',
components: {
SwitchInput
},
watch: {
'isSelectedYearlyPlans': function () {
this.$emit('input', this.isSelectedYearlyPlans)
}
},
data() {
return {
isSelectedYearlyPlans: false
}
}
}
</script>

View File

@@ -1,196 +0,0 @@
<template>
<PopupWrapper name="change-plan-subscription">
<PopupHeader :title="$t('change_your_plan')" icon="credit-card" />
<!--Select Payment Plans-->
<PopupContent v-if="plans">
<InfoBox v-if="plans.data.length === 0" class="!mb-0">
<p>{{ $t("not_any_plan") }}</p>
</InfoBox>
<!--Toggle yearly billing-->
<PlanPeriodSwitcher v-if="yearlyPlans.length > 0" v-model="isSelectedYearlyPlans" />
<!--List available plans-->
<div>
<PlanDetail
v-for="(plan, i) in plans.data"
:plan="plan"
:key="plan.data.id"
v-if="plan.data.attributes.interval === intervalPlanType"
:class="{'opacity-50 pointer-events-none': userSubscribedPlanId === plan.data.id}"
:is-selected="selectedPlan && selectedPlan.data.id === plan.data.id"
@click.native="selectPlan(plan)"
/>
</div>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary"
>{{ $t('cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
v-if="plans && plans.data.length !== 0"
:button-style="buttonStyle"
:loading="isLoading"
@click.native="proceedToPayment"
>{{ $t('change_plan') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '../../Popups/Components/PopupWrapper'
import PopupActions from '../../Popups/Components/PopupActions'
import PopupContent from '../../Popups/Components/PopupContent'
import PopupHeader from '../../Popups/Components/PopupHeader'
import ButtonBase from '../../UI/Buttons/ButtonBase'
import PlanDetail from '../PlanDetail'
import {mapGetters} from 'vuex'
import {events} from '../../../bus'
import axios from 'axios'
import Spinner from '../../UI/Others/Spinner'
import InfoBox from '../../UI/Others/InfoBox'
import PlanPeriodSwitcher from "../PlanPeriodSwitcher";
export default {
name: 'ChangeSubscriptionPopup',
components: {
PlanPeriodSwitcher,
InfoBox,
Spinner,
PlanDetail,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
},
watch: {
isSelectedYearlyPlans() {
this.selectedPlan = undefined
},
},
computed: {
...mapGetters(['config', 'user']),
intervalPlanType() {
return this.isSelectedYearlyPlans ? 'year' : 'month'
},
buttonStyle() {
return this.selectedPlan ? 'theme' : 'secondary'
},
userSubscribedPlanId() {
return (
this.user &&
this.user.data.relationships.subscription &&
this.user.data.relationships.subscription.data.relationships.plan.data.id
)
},
yearlyPlans() {
return this.plans.data.filter((plan) => plan.data.attributes.interval === 'year')
},
subscriptionDriver() {
return this.user.data.relationships.subscription.data.attributes.driver
},
subscription() {
return this.user.data.relationships.subscription
}
},
data() {
return {
isSelectedYearlyPlans: false,
isLoading: false,
selectedPlan: undefined,
plans: undefined,
}
},
methods: {
proceedToPayment() {
// Start button spinner
this.isLoading = true
if (this.subscriptionDriver === 'stripe') {
this.payByStripe()
}
if (this.subscriptionDriver === 'paypal') {
this.payByPayPal()
}
if (this.subscriptionDriver === 'paystack') {
this.payByPaystack()
}
},
payByPayPal() {
axios.post(`/api/subscriptions/swap/${this.selectedPlan.data.id}`)
.then((response) => {
window.location = response.data.links[0].href
})
},
payByStripe() {
// Subscribe to the new plan
if (['inactive', 'cancelled', 'completed'].includes(this.subscription.data.attributes.status)) {
axios
.post('/api/stripe/checkout', {
planCode: this.selectedPlan.data.meta.driver_plan_id.stripe,
})
.then((response) => {
window.location = response.data.url
})
.catch((error) => {
if (error.response.status === 500 && error.response.data.type) {
events.$emit('alert:open', {
title: error.response.data.title,
message: error.response.data.message,
})
} else {
this.$isSomethingWrong()
}
})
}
// Change active subscription
if (this.subscription.data.attributes.status === 'active') {
axios
.post(`/api/subscriptions/swap/${this.selectedPlan.data.id}`)
.then(() => {
this.$closePopup()
events.$emit('toaster', {
type: 'success',
message: this.$t('subscription_changed'),
})
})
}
},
payByPaystack() {
axios
.post('/api/paystack/checkout', {
planCode: this.selectedPlan.data.meta.driver_plan_id.paystack,
})
.then((response) => {
window.location = response.data.data.authorization_url
})
},
selectPlan(plan) {
this.selectedPlan = plan
},
},
created() {
// Load available plans
axios.get('/api/subscriptions/plans').then((response) => {
this.plans = response.data
})
// Reset states on popup close
events.$on('popup:close', () => {
this.isSelectedYearlyPlans = false
this.selectedPlan = undefined
})
},
}
</script>

View File

@@ -1,182 +0,0 @@
<template>
<PopupWrapper name="select-payment-method">
<PopupHeader :title="$t('select_payment_method')" icon="credit-card" />
<PopupContent style="padding: 0 20px">
<InfoBox v-if="!config.isPayPal && !config.isPaystack" class="!mb-0">
<p>{{ $t("not_any_payment_method") }}</p>
</InfoBox>
<!--PayPal implementation-->
<div
v-if="config.isPayPal"
:class="{
'mb-2 rounded-xl bg-light-background px-4 dark:bg-2x-dark-foreground': paypal.isMethodsLoaded,
}"
>
<PaymentMethod
@click.native="payByPayPal"
driver="paypal"
:description="config.paypal_payment_description"
>
<div v-if="paypal.isMethodLoading" class="translate-y-3 scale-50 transform">
<Spinner />
</div>
<span
v-if="!paypal.isMethodsLoaded"
:class="{ 'opacity-0': paypal.isMethodLoading }"
class="text-theme cursor-pointer text-sm font-bold"
>
{{ $t('select') }}
</span>
</PaymentMethod>
<!--PayPal Buttons-->
<div id="paypal-button-container"></div>
</div>
<!--Paystack implementation-->
<PaymentMethod
v-if="config.isPaystack"
driver="paystack"
:description="$t(config.paystack_payment_description)"
>
<div v-if="paystack.isGettingCheckoutLink" class="translate-y-3 scale-50 transform">
<Spinner />
</div>
<span
@click="payByPaystack()"
:class="{ 'opacity-0': paystack.isGettingCheckoutLink }"
class="text-theme cursor-pointer text-sm font-bold"
>
{{ $t('select') }}
</span>
</PaymentMethod>
</PopupContent>
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('cancel_payment') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import PopupWrapper from '../../Popups/Components/PopupWrapper'
import PopupActions from '../../Popups/Components/PopupActions'
import PopupContent from '../../Popups/Components/PopupContent'
import PopupHeader from '../../Popups/Components/PopupHeader'
import ButtonBase from '../../UI/Buttons/ButtonBase'
import { loadScript } from '@paypal/paypal-js'
import PaymentMethod from '../PaymentMethod'
import Spinner from '../../UI/Others/Spinner'
import InfoBox from "../../UI/Others/InfoBox"
import { events } from '../../../bus'
import { mapGetters } from 'vuex'
import axios from "axios";
export default {
name: 'ChargePaymentPopup',
components: {
PaymentMethod,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
Spinner,
InfoBox,
},
data() {
return {
paypal: {
isMethodsLoaded: false,
isMethodLoading: false,
},
paystack: {
isGettingCheckoutLink: false,
},
}
},
computed: {
...mapGetters(['singleChargeAmount', 'config', 'user']),
},
methods: {
payByPaystack() {
this.paystack.isGettingCheckoutLink = true
axios
.post('/api/paystack/checkout', {
amount: this.singleChargeAmount * 100,
})
.then((response) => {
window.location = response.data.data.authorization_url
})
},
async payByPayPal() {
if (this.paypal.isMethodLoading) {
return
}
this.paypal.isMethodLoading = true
let paypal
try {
paypal = await loadScript({
'client-id': this.config.paypal_client_id,
vault: true,
})
} catch (error) {
events.$emit('toaster', {
type: 'danger',
message: this.$t('failed_to_load_paypal'),
})
}
const userId = this.user.data.id
const amount = this.singleChargeAmount
this.paypal.isMethodsLoaded = true
this.paypal.isMethodLoading = false
const app = this
// Initialize paypal buttons for single charge
await paypal
.Buttons({
createOrder: function (data, actions) {
return actions.order.create({
purchase_units: [
{
amount: {
value: amount,
},
custom_id: userId,
},
],
})
},
onApprove: function () {
app.paymentSuccessful()
},
})
.render('#paypal-button-container')
},
paymentSuccessful() {
this.$closePopup()
events.$emit('toaster', {
type: 'success',
message: this.$t('payment_was_successfully_received'),
})
// todo: temporary reload function
setTimeout(() => document.location.reload(), 500)
},
},
created() {
events.$on('popup:close', () => (this.paypal.isMethodsLoaded = false))
},
}
</script>

View File

@@ -1,312 +0,0 @@
<template>
<PopupWrapper name="select-plan-subscription">
<PopupHeader :title="$t('upgrade_your_account')" icon="credit-card" />
<!--Payment Options-->
<div v-if="isPaymentOptionPage">
<PopupContent>
<!--Stripe implementation-->
<PaymentMethod
v-if="config.isStripe"
driver="stripe"
:description="$t(config.stripe_payment_description)"
>
<div v-if="stripe.isGettingCheckoutLink" class="translate-y-3 scale-50 transform">
<Spinner />
</div>
<span
@click="payByStripe"
:class="{ 'opacity-0': stripe.isGettingCheckoutLink }"
class="text-theme cursor-pointer text-sm font-bold"
>
{{ $t('select') }}
</span>
</PaymentMethod>
<!--PayPal implementation-->
<div
v-if="config.isPayPal"
:class="{
'mb-2 rounded-xl bg-light-background px-4 dark:bg-2x-dark-foreground': paypal.isMethodsLoaded,
}"
>
<PaymentMethod
@click.native="payByPayPal"
driver="paypal"
:description="$t(config.paypal_payment_description)"
>
<div v-if="paypal.isMethodLoading" class="translate-y-3 scale-50 transform">
<Spinner />
</div>
<span
v-if="!paypal.isMethodsLoaded"
:class="{ 'opacity-0': paypal.isMethodLoading }"
class="text-theme cursor-pointer text-sm font-bold"
>
{{ $t('select') }}
</span>
</PaymentMethod>
<!--PayPal Buttons-->
<div id="paypal-button-container"></div>
</div>
<!--Paystack implementation-->
<PaymentMethod
v-if="config.isPaystack"
driver="paystack"
:description="$t(config.paystack_payment_description)"
>
<div v-if="paystack.isGettingCheckoutLink" class="translate-y-3 scale-50 transform">
<Spinner />
</div>
<span
@click="payByPaystack()"
:class="{ 'opacity-0': paystack.isGettingCheckoutLink }"
class="text-theme cursor-pointer text-sm font-bold"
>
{{ $t('select') }}
</span>
</PaymentMethod>
</PopupContent>
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary">
{{ $t('cancel_payment') }}
</ButtonBase>
</PopupActions>
</div>
<!--Select Payment Plans-->
<div v-if="!isPaymentOptionPage">
<PopupContent v-if="plans">
<InfoBox v-if="plans.data.length === 0" class="!mb-0">
<p>There isn't any plan yet.</p>
</InfoBox>
<PlanPeriodSwitcher v-if="yearlyPlans.length > 0" v-model="isSelectedYearlyPlans" />
<!--List available plans-->
<div>
<PlanDetail
v-for="(plan, i) in plans.data"
:plan="plan"
:key="plan.data.id"
v-if="plan.data.attributes.interval === intervalPlanType"
:class="{ 'pointer-events-none opacity-50': userSubscribedPlanId === plan.data.id }"
:is-selected="selectedPlan && selectedPlan.data.id === plan.data.id"
@click.native="selectPlan(plan)"
/>
</div>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary"
>{{ $t('cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
v-if="plans && plans.data.length !== 0"
:button-style="buttonStyle"
@click.native="isPaymentOptionPage = true"
>{{ $t('upgrade_account') }}
</ButtonBase>
</PopupActions>
</div>
</PopupWrapper>
</template>
<script>
import PaymentMethod from '../PaymentMethod'
import { loadScript } from '@paypal/paypal-js'
import SwitchInput from '../../Inputs/SwitchInput'
import PopupWrapper from '../../Popups/Components/PopupWrapper'
import PopupActions from '../../Popups/Components/PopupActions'
import PopupContent from '../../Popups/Components/PopupContent'
import PopupHeader from '../../Popups/Components/PopupHeader'
import ButtonBase from '../../UI/Buttons/ButtonBase'
import PlanDetail from '../PlanDetail'
import { mapGetters } from 'vuex'
import { events } from '../../../bus'
import axios from 'axios'
import Spinner from '../../UI/Others/Spinner'
import InfoBox from '../../UI/Others/InfoBox'
import PlanPeriodSwitcher from '../PlanPeriodSwitcher'
export default {
name: 'SubscribeAccountPopup',
components: {
PlanPeriodSwitcher,
InfoBox,
Spinner,
PaymentMethod,
PlanDetail,
SwitchInput,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
},
watch: {
isSelectedYearlyPlans() {
this.selectedPlan = undefined
},
},
computed: {
...mapGetters(['config', 'user']),
intervalPlanType() {
return this.isSelectedYearlyPlans ? 'year' : 'month'
},
buttonStyle() {
return this.selectedPlan ? 'theme' : 'secondary'
},
userSubscribedPlanId() {
return (
this.user &&
this.user.data.relationships.subscription &&
this.user.data.relationships.subscription.data.relationships.plan.data.id
)
},
yearlyPlans() {
return this.plans.data.filter((plan) => plan.data.attributes.interval === 'year')
},
},
data() {
return {
paystack: {
isGettingCheckoutLink: false,
},
stripe: {
isGettingCheckoutLink: false,
},
paypal: {
isMethodsLoaded: false,
isMethodLoading: false,
},
isPaymentOptionPage: false,
isSelectedYearlyPlans: false,
isLoading: false,
selectedPlan: undefined,
plans: undefined,
}
},
methods: {
payByPaystack() {
this.paystack.isGettingCheckoutLink = true
axios
.post('/api/paystack/checkout', {
planCode: this.selectedPlan.data.meta.driver_plan_id.paystack,
})
.then((response) => {
window.location = response.data.data.authorization_url
})
},
async payByPayPal() {
if (this.paypal.isMethodLoading) {
return
}
this.paypal.isMethodLoading = true
let paypal
try {
paypal = await loadScript({
'client-id': this.config.paypal_client_id,
vault: true,
})
} catch (error) {
events.$emit('toaster', {
type: 'danger',
message: this.$t('failed_to_load_paypal'),
})
}
const planId = this.selectedPlan.data.meta.driver_plan_id.paypal
const userId = this.user.data.id
const app = this
this.paypal.isMethodsLoaded = true
this.paypal.isMethodLoading = false
// Initialize paypal buttons for single charge
await paypal
.Buttons({
createSubscription: function (data, actions) {
return actions.subscription.create({
plan_id: planId,
custom_id: userId,
})
},
onApprove: function () {
app.paymentSuccessful()
},
})
.render('#paypal-button-container')
},
payByStripe() {
this.stripe.isGettingCheckoutLink = true
axios
.post('/api/stripe/checkout', {
planCode: this.selectedPlan.data.meta.driver_plan_id.stripe,
})
.then((response) => {
window.location = response.data.url
})
.catch((error) => {
this.$closePopup()
setTimeout(() => {
if (error.response.status === 500 && error.response.data.type) {
events.$emit('alert:open', {
title: error.response.data.title,
message: error.response.data.message,
})
} else {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
}
}, 100)
})
.finally(() => {
this.stripe.isGettingCheckoutLink = false
})
},
selectPlan(plan) {
this.selectedPlan = plan
},
paymentSuccessful() {
this.$closePopup()
events.$emit('toaster', {
type: 'success',
message: this.$t('payment_was_successfully_received'),
})
// todo: temporary reload function
setTimeout(() => document.location.reload(), 1000)
},
},
created() {
// Load available plans
axios.get('/api/subscriptions/plans').then((response) => {
this.plans = response.data
})
// Reset states on popup close
events.$on('popup:close', () => {
this.isSelectedYearlyPlans = false
this.isPaymentOptionPage = false
this.selectedPlan = undefined
this.paypal.isMethodsLoaded = false
})
},
}
</script>

View File

@@ -1,12 +0,0 @@
<template>
<div v-if="$store.getters.isLimitedUser" class="bg-red-500 py-1 text-center">
<router-link :to="{ name: 'Billing' }" class="text-xs font-bold text-white">
{{ $t('restricted_account_warning') }}
</router-link>
</div>
</template>
<script>
export default {
name: 'RestrictionWarningBar',
}
</script>

View File

@@ -1,91 +0,0 @@
<template>
<div v-if="!hasPaymentMethod" class="card shadow-card">
<FormLabel icon="dollar">
{{ $t('balance') }}
</FormLabel>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ user.data.relationships.balance.data.attributes.formatted }}
</b>
<!-- Make payment form -->
<ValidationObserver
ref="fundAccount"
@submit.prevent="makePayment"
v-slot="{ invalid }"
tag="form"
class="mt-6"
>
<ValidationProvider
tag="div"
v-slot="{ errors }"
mode="passive"
name="Amount"
:rules="`required|min_value:${user.data.meta.totalDebt.amount}`"
>
<AppInputText
:description="
$t('amount_increase_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="chargeAmount"
:placeholder="$t('fund_account_balance')"
type="number"
min="1"
max="999999999"
class="focus-border-theme input-dark"
:class="{ '!border-rose-600': errors[0] }"
/>
<ButtonBase type="submit" button-style="theme" class="w-full sm:w-auto">
{{ $t('make_payment') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
</template>
<script>
import { ValidationObserver, ValidationProvider } from 'vee-validate/dist/vee-validate.full'
import ButtonBase from '../UI/Buttons/ButtonBase'
import FormLabel from '../UI/Labels/FormLabel'
import AppInputText from '../Forms/Layouts/AppInputText'
import { mapGetters } from 'vuex'
export default {
name: 'UserBalance',
components: {
ValidationObserver,
ValidationProvider,
AppInputText,
ButtonBase,
FormLabel,
},
computed: {
...mapGetters(['user']),
hasPaymentMethod() {
return this.user.data.relationships.creditCards && this.user.data.relationships.creditCards.data.length > 0
},
},
data() {
return {
chargeAmount: undefined,
}
},
methods: {
async makePayment() {
// Validate fields
const isValid = await this.$refs.fundAccount.validate()
if (!isValid) return
// Show payment methods popup
this.$store.dispatch('callSingleChargeProcess', this.chargeAmount)
},
},
}
</script>

View File

@@ -1,244 +0,0 @@
<template>
<div class="card shadow-card">
<FormLabel icon="bell">
{{ $t('billing_alert') }}
</FormLabel>
<div v-if="user.data.relationships.alert">
<b class="-mt-3 mb-0.5 block flex items-center text-2xl font-extrabold sm:text-3xl">
{{ user.data.relationships.alert.data.attributes.formatted }}
<edit2-icon
v-if="!showUpdateBillingAlertForm"
@click="showUpdateBillingAlertForm = !showUpdateBillingAlertForm"
size="12"
class="vue-feather ml-2 -translate-y-0.5 transform cursor-pointer"
/>
<trash2-icon
v-if="showUpdateBillingAlertForm"
@click="deleteBillingAlert"
size="12"
class="vue-feather ml-2 -translate-y-0.5 transform cursor-pointer"
/>
</b>
<b class="block text-sm dark:text-gray-500 text-gray-400">
{{ $t('billing_alert_description') }}
</b>
</div>
<ValidationObserver
v-if="showUpdateBillingAlertForm"
ref="updatebillingAlertForm"
@submit.prevent="updateBillingAlert"
v-slot="{ invalid }"
tag="form"
class="mt-6"
>
<ValidationProvider tag="div" v-slot="{ errors }" mode="passive" name="Billing Alert" rules="required">
<AppInputText
:description="
$t(
'billing_alert_notes'
)
"
:error="errors[0]"
:is-last="true"
>
<div class="space-y-4 sm:flex sm:space-x-4 sm:space-y-0">
<input
v-model="billingAlertAmount"
:placeholder="$t('alert_amount_')"
type="number"
min="1"
max="999999999"
class="focus-border-theme input-dark"
:class="{ '!border-rose-600': errors[0] }"
/>
<ButtonBase
:loadint="isSendingBillingAlert"
:disabled="isSendingBillingAlert"
type="submit"
button-style="theme"
class="w-full sm:w-auto"
>
{{ $t('update_alert') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
<ValidationObserver
v-if="!user.data.relationships.alert"
ref="billingAlertForm"
@submit.prevent="setBillingAlert"
v-slot="{ invalid }"
tag="form"
class="mt-6"
>
<ValidationProvider tag="div" v-slot="{ errors }" mode="passive" name="Billing Alert" rules="required">
<AppInputText
:description="
$t(
'billing_alert_notes'
)
"
:error="errors[0]"
:is-last="true"
>
<div class="space-y-4 sm:flex sm:space-x-4 sm:space-y-0">
<input
v-model="billingAlertAmount"
:placeholder="$t('alert_amount_')"
type="number"
min="1"
max="999999999"
class="focus-border-theme input-dark"
:class="{ '!border-rose-600': errors[0] }"
/>
<ButtonBase
:loadint="isSendingBillingAlert"
:disabled="isSendingBillingAlert"
type="submit"
button-style="theme"
class="w-full sm:w-auto"
>
{{ $t('set_alert') }}
</ButtonBase>
</div>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
</div>
</template>
<script>
import { ValidationObserver, ValidationProvider } from 'vee-validate/dist/vee-validate.full'
import ButtonBase from '../UI/Buttons/ButtonBase'
import AppInputText from '../Forms/Layouts/AppInputText'
import FormLabel from '../UI/Labels/FormLabel'
import { Edit2Icon, Trash2Icon } from 'vue-feather-icons'
import { events } from '../../bus'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'UserBillingAlerts',
components: {
ValidationObserver,
ValidationProvider,
AppInputText,
ButtonBase,
Trash2Icon,
Edit2Icon,
FormLabel,
},
computed: {
...mapGetters(['user']),
},
data() {
return {
billingAlertAmount: undefined,
isSendingBillingAlert: false,
showUpdateBillingAlertForm: false,
}
},
methods: {
async updateBillingAlert() {
// Validate fields
const isValid = await this.$refs.updatebillingAlertForm.validate()
if (!isValid) return
this.isSendingBillingAlert = true
axios
.patch(`/api/subscriptions/billing-alerts/${this.user.data.relationships.alert.data.id}`, {
amount: this.billingAlertAmount,
})
.then(() => {
this.$store.dispatch('getAppData')
this.showUpdateBillingAlertForm = false
events.$emit('toaster', {
type: 'success',
message: this.$t('alert_updated'),
})
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
.finally(() => {
this.isSendingBillingAlert = false
})
},
async setBillingAlert() {
// Validate fields
const isValid = await this.$refs.billingAlertForm.validate()
if (!isValid) return
this.isSendingBillingAlert = true
axios
.post('/api/subscriptions/billing-alerts', {
amount: this.billingAlertAmount,
})
.then(() => {
this.$store.dispatch('getAppData')
events.$emit('toaster', {
type: 'success',
message: this.$t('alert_set_successfully'),
})
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
.finally(() => {
this.isSendingBillingAlert = false
})
},
deleteBillingAlert() {
events.$emit('confirm:open', {
title: this.$t('want_to_delete_alert'),
message: this.$t(
'want_to_delete_alert_description'
),
action: {
id: this.user.data.relationships.alert.data.id,
operation: 'delete-billing-alert',
},
})
},
},
created() {
events.$on('action:confirmed', (data) => {
if (data.operation === 'delete-billing-alert')
axios
.delete(`/api/subscriptions/billing-alerts/${this.user.data.relationships.alert.data.id}`)
.then(() => {
this.$store.dispatch('getAppData')
this.showUpdateBillingAlertForm = false
this.billingAlertAmount = undefined
events.$emit('toaster', {
type: 'success',
message: this.$t('deleted_alert'),
})
})
.catch(() => this.$isSomethingWrong())
})
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -1,116 +0,0 @@
<template>
<div v-if="hasSubscription" class="card shadow-card">
<FormLabel>
{{ $t('edit_your_subscription') }}
</FormLabel>
<AppInputButton
v-if="subscription.attributes.status !== 'cancelled'"
:title="$t('cancel_subscription')"
:description="
$t(
'cancel_subscription_description'
)
"
>
<ButtonBase
@click.native="cancelSubscriptionConfirmation"
:loading="isCancelling"
class="w-full sm:w-auto"
button-style="secondary"
>
{{ $t('cancel_now') }}
</ButtonBase>
</AppInputButton>
<AppInputButton
:title="$t('upgrade_downgrade_plan')"
:description="$t('upgrade_downgrade_plan_description')"
:is-last="true"
>
<ButtonBase @click.native="$changeSubscriptionOptions" class="w-full sm:w-auto" button-style="secondary">
{{ $t('change_plan') }}
</ButtonBase>
</AppInputButton>
</div>
</template>
<script>
import AppInputButton from '../Forms/Layouts/AppInputButton'
import AppInputText from '../Forms/Layouts/AppInputText'
import AppInputSwitch from '../Forms/Layouts/AppInputSwitch'
import ButtonBase from '../UI/Buttons/ButtonBase'
import FormLabel from '../UI/Labels/FormLabel'
import { events } from '../../bus'
import axios from 'axios'
export default {
name: 'UserEditSubscription',
components: {
AppInputButton,
AppInputSwitch,
AppInputText,
ButtonBase,
FormLabel,
},
computed: {
subscription() {
return this.$store.getters.user.data.relationships.subscription.data
},
hasSubscription() {
return this.$store.getters.user.data.relationships.subscription
},
},
data() {
return {
isCancelling: false,
}
},
methods: {
cancelSubscriptionConfirmation() {
events.$emit('confirm:open', {
title: this.$t('want_cancel_subscription'),
message: this.$t(
"popup_subscription_cancel.message"
),
action: {
operation: 'cancel-subscription',
},
})
},
},
created() {
events.$on('action:confirmed', (data) => {
if (data.operation === 'cancel-subscription') {
// Start deleting spinner button
this.isCancelling = true
// Send post request
axios
.post('/api/subscriptions/cancel')
.then(() => {
// Update user data
this.$store.dispatch('getAppData')
events.$emit('toaster', {
type: 'success',
message: this.$t('popup_subscription_cancel.title'),
})
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
.finally(() => {
this.isCancelling = false
})
}
})
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

View File

@@ -1,45 +0,0 @@
<template>
<div v-if="!hasSubscription" 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 v-if="$store.getters.config.allowed_payments" class="mb-3 mb-8 block text-sm text-gray-400">
{{ $t('upgrade_to_get_more') }}
</b>
<ButtonBase
v-if="$store.getters.config.allowed_payments"
@click.native="$openSubscribeOptions"
type="submit"
button-style="theme"
class="mt-4 w-full"
>
{{ $t('upgrade_your_account') }}
</ButtonBase>
</div>
</template>
<script>
import InfoBox from '../UI/Others/InfoBox'
import FormLabel from '../UI/Labels/FormLabel'
import ButtonBase from '../UI/Buttons/ButtonBase'
export default {
name: 'UserEmptySubscription',
components: {
ButtonBase,
FormLabel,
InfoBox,
},
computed: {
hasSubscription() {
return this.$store.getters.user.data.relationships.subscription
},
},
}
</script>

View File

@@ -1,67 +0,0 @@
<template>
<div
v-if="user.data.relationships.failedPayments && user.data.relationships.failedPayments.data.length > 0"
class="card shadow-card"
>
<FormLabel icon="frown">
{{ $t('failed_payments') }}
</FormLabel>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
-{{ user.data.meta.totalDebt.formatted }}
</b>
<b class="mb-3 mb-5 block text-sm text-gray-400">
{{
$t(
"unable_to_charge"
)
}}
</b>
<!--Failed Payments-->
<div
v-for="payment in user.data.relationships.failedPayments.data"
:key="payment.data.id"
class="flex items-center justify-between border-b border-dashed border-light py-2 dark:border-opacity-5"
>
<div class="w-2/4 leading-none">
<b class="text-sm font-bold leading-none">
{{ payment.data.attributes.note }}
</b>
</div>
<div class="w-1/4 text-left">
<span class="text-gray-560 text-sm font-bold capitalize">
{{ $t(payment.data.attributes.source) }}
</span>
</div>
<div class="w-1/4 text-right">
<span class="text-sm font-bold">
{{ payment.data.attributes.created_at }}
</span>
</div>
<div class="w-1/4 text-right">
<span class="text-red text-sm font-bold">
{{ payment.data.attributes.amount }}
</span>
</div>
</div>
</div>
</template>
<script>
import FormLabel from '../UI/Labels/FormLabel'
import InfoBox from '../UI/Others/InfoBox'
import { mapGetters } from 'vuex'
export default {
name: 'UserFailedPayments',
components: {
FormLabel,
InfoBox,
},
computed: {
...mapGetters(['user']),
},
}
</script>

View File

@@ -1,93 +0,0 @@
<template>
<div v-if="hasSubscription" class="card shadow-card">
<FormLabel>
{{ $t('subscription') }}
</FormLabel>
<b class="-mt-3 mb-0.5 block text-xl font-extrabold sm:text-3xl">
{{ status }}
</b>
<b class="mb-3 mb-8 block text-sm dark:text-gray-500 text-gray-400">
{{ subscription.data.relationships.plan.data.attributes.name }} /
{{ price }}
</b>
<div v-for="(limit, i) in limitations" :key="i" :class="{ 'mb-6': Object.keys(limitations).length - 1 !== i }">
<b class="mb-3 block text-sm dark:text-gray-500 text-gray-400">
{{ limit.message }}
</b>
<ProgressLine v-if="limit.isVisibleBar" :data="limit.distribution" />
</div>
</div>
</template>
<script>
import FormLabel from '../UI/Labels/FormLabel'
import ProgressLine from '../UI/ProgressChart/ProgressLine'
import { mapGetters } from 'vuex'
export default {
name: 'UserFixedSubscriptionDetail',
components: {
ProgressLine,
FormLabel,
},
computed: {
...mapGetters(['user']),
subscription() {
return this.$store.getters.user.data.relationships.subscription
},
hasSubscription() {
return this.$store.getters.user.data.relationships.subscription
},
limitations() {
let limitations = []
Object.entries(this.user.data.meta.limitations).map(([key, item]) => {
let payload = {
color: {
max_storage_amount: 'warning',
max_team_members: 'purple',
},
message: {
max_storage_amount: this.$t('total_x_of_x_used', {use: item.use, total:item.total }),
max_team_members: item.total === -1
? this.$t('max_team_members.unlimited')
: this.$t('total_x_of_x_members', {use: item.use, total:item.total }),
},
title: {
max_storage_amount: this.$t('storage'),
max_team_members: this.$t('team_members'),
},
}
limitations.push({
message: payload.message[key],
isVisibleBar: item.total !== -1,
distribution: [
{
progress: item.percentage,
color: payload.color[key],
title: payload.title[key],
},
],
})
})
return limitations
},
status() {
return {
active: this.$t('active_until', {date: this.subscription.data.attributes.renews_at}),
cancelled: this.$t('ends_at_date', {date: this.subscription.data.attributes.ends_at}),
}[this.subscription.data.attributes.status]
},
price() {
return {
month: this.$t('price_per_month', {price: this.subscription.data.relationships.plan.data.attributes.price}),
year: this.$t('price_per_year', {price: this.subscription.data.relationships.plan.data.attributes.price}),
}[this.subscription.data.relationships.plan.data.attributes.interval]
},
},
}
</script>

View File

@@ -1,216 +0,0 @@
<template>
<div v-if="canShowForMeteredBilling || canShowForFixedBilling" class="card shadow-card">
<FormLabel icon="credit-card">
{{ $t('payment_method') }}
</FormLabel>
<!-- User has registered payment method -->
<div v-if="hasPaymentMethod">
<b
v-if="
config.subscriptionType === 'metered' && user.data.relationships.balance.data.attributes.balance > 0
"
class="mb-3 mb-5 block text-sm"
>
{{
$t('credit_to_auto_withdraw', {
credit: user.data.relationships.balance.data.attributes.formatted,
})
}}
</b>
<!-- Card -->
<PaymentCard v-for="card in user.data.relationships.creditCards.data" :key="card.data.id" :card="card" />
<small class="hidden pt-3 text-xs leading-none dark:text-gray-500 text-gray-500 sm:block">
{{ $t('auto_settled_credit_card') }}
</small>
</div>
<!-- User doesn't have registered payment method -->
<div v-if="!hasPaymentMethod">
<!-- Show credit card form -->
<ButtonBase
@click.native="showStoreCreditCardForm"
v-if="!isCreditCardForm"
:loading="stripe.storingStripePaymentMethod"
type="submit"
button-style="theme"
class="mt-4 w-full"
>
{{ $t('add_payment_method') }}
</ButtonBase>
<!-- Store credit card form -->
<form v-if="isCreditCardForm" @submit.prevent="storeStripePaymentMethod" id="payment-form" class="mt-6">
<div v-if="stripe.isInitialization" class="relative mb-6 h-10">
<Spinner />
</div>
<InfoBox v-if="config.isDemo && !stripe.isInitialization">
<p>For adding test credit card please use <b class="text-theme">4242 4242 4242 4242</b> as a card number, <b class="text-theme">11/22</b>
as the expiration date and <b class="text-theme">123</b> as CVC number and ZIP <b class="text-theme">12345</b> if required.</p>
</InfoBox>
<div id="payment-element">
<!-- Elements will create form elements here -->
</div>
<ButtonBase
:loading="stripe.storingStripePaymentMethod"
type="submit"
button-style="theme"
class="mt-4 w-full"
>
{{ $t('store_my_credit_card') }}
</ButtonBase>
<div id="error-message" class="pt-2 text-xs text-rose-600">
<!-- Display error message to your customers here -->
</div>
</form>
</div>
</div>
</template>
<script>
import ButtonBase from '../UI/Buttons/ButtonBase'
import FormLabel from '../UI/Labels/FormLabel'
import PaymentCard from './PaymentCard'
import Spinner from '../UI/Others/Spinner'
import { mapGetters } from 'vuex'
import { events } from '../../bus'
import { loadStripe } from '@stripe/stripe-js'
import axios from 'axios'
import InfoBox from "../UI/Others/InfoBox";
// Define stripe variables
let stripe,
elements = undefined
export default {
name: 'UserStoredPaymentMethods',
components: {
InfoBox,
ButtonBase,
FormLabel,
PaymentCard,
Spinner,
},
computed: {
...mapGetters(['isDarkMode', 'config', 'user']),
canShowForMeteredBilling() {
return this.config.isStripe && this.config.subscriptionType === 'metered'
},
canShowForFixedBilling() {
return (
this.config.isStripe &&
this.config.subscriptionType === 'fixed' &&
this.$store.getters.user.data.relationships.subscription &&
this.$store.getters.user.data.relationships.subscription.data.attributes.driver === 'stripe'
)
},
hasPaymentMethod() {
return this.user.data.relationships.creditCards && this.user.data.relationships.creditCards.data.length > 0
},
},
data() {
return {
isLoading: false,
isCreditCardForm: false,
stripe: {
isInitialization: true,
storingStripePaymentMethod: false,
},
}
},
methods: {
async storeStripePaymentMethod() {
if (this.config.isDemo && this.user.data.attributes.email === 'ho**@hi5ve.digital') {
events.$emit('toaster', {
type: 'success',
message: this.$t('credit_card_stored'),
})
return
}
this.stripe.storingStripePaymentMethod = true
const { error } = await stripe.confirmSetup({
//`Elements` instance that was used to create the Payment Element
elements,
redirect: 'if_required',
confirmParams: {
return_url: window.location.href,
},
})
if (error) {
// This point will only be reached if there is an immediate error when
// confirming the payment. Show error to your customer (e.g., payment
// details incomplete)
const messageContainer = document.querySelector('#error-message')
messageContainer.textContent = error.message
} else {
// Your customer will be redirected to your `return_url`. For some payment
// methods like iDEAL, your customer will be redirected to an intermediate
// site first to authorize the payment, then redirected to the `return_url`.
events.$emit('toaster', {
type: 'success',
message: this.$t('credit_card_stored'),
})
// TODO: L9 - load credit card after was stored in database
setTimeout(() => document.location.reload(), 500)
}
this.stripe.storingStripePaymentMethod = false
},
async stripeInit() {
// Init stripe js
stripe = await loadStripe(this.config.stripe_public_key)
await axios
.get('/api/stripe/setup-intent')
.then((response) => {
// Set up Stripe.js and Elements to use in checkout form, passing the client secret obtained in step 2
elements = stripe.elements({
clientSecret: response.data.client_secret,
appearance: {
theme: 'stripe',
variables: {
colorPrimary: this.config.app_color,
fontFamily: 'Nunito',
borderRadius: '8px',
colorText: this.isDarkMode ? '#bec6cf' : '#1B2539',
colorBackground: this.isDarkMode ? '#191b1e' : '#fff',
fontWeightNormal: '700',
fontSizeSm: '0.875rem',
colorSuccessText: '#0ABB87',
colorSuccess: '#0ABB87',
colorWarning: '#fd397a',
colorWarningText: '#fd397a',
colorDangerText: '#fd397a',
colorTextSecondary: '#6b7280',
spacingGridRow: '20px',
},
},
})
// Create and mount the Payment Element
const paymentElement = elements.create('payment')
paymentElement.mount('#payment-element')
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
this.stripe.isInitialization = false
},
showStoreCreditCardForm() {
this.isCreditCardForm = !this.isCreditCardForm
this.stripeInit()
},
}
}
</script>

View File

@@ -1,50 +0,0 @@
<template>
<div class="card shadow-card">
<FormLabel icon="file-text">
{{ $t('transactions') }}
</FormLabel>
<DatatableWrapper
class="overflow-x-auto"
api="/api/subscriptions/transactions"
:paginator="true"
:columns="columns"
>
<template slot-scope="{ row }">
<FixedTransactionRow :row="row" />
</template>
<!--Empty page-->
<template v-slot:empty-page>
<InfoBox style="margin-bottom: 0">
<p>{{ $t('user_invoices.empty') }}</p>
</InfoBox>
</template>
</DatatableWrapper>
</div>
</template>
<script>
import InfoBox from '../UI/Others/InfoBox'
import DatatableWrapper from '../UI/Table/DatatableWrapper'
import FixedTransactionRow from './FixedTransactionRow'
import FormLabel from '../UI/Labels/FormLabel'
import ColorLabel from '../UI/Labels/ColorLabel'
export default {
name: 'UserTransactionsForFixedBilling',
components: {
FixedTransactionRow,
DatatableWrapper,
ColorLabel,
FormLabel,
InfoBox,
},
computed: {
columns() {
return this.$store.getters.transactionColumns.filter(
(column) => !['type', 'user_id'].includes(column.field)
)
},
},
}
</script>

View File

@@ -1,68 +0,0 @@
<template>
<div class="card shadow-card">
<FormLabel icon="file-text">
{{ $t('transactions') }}
</FormLabel>
<DatatableWrapper class="overflow-x-auto" api="/api/user/transactions" :paginator="true" :columns="columns">
<template slot-scope="{ row }">
<!--Transaction rows-->
<MeteredTransactionRow :row="row" @showDetail="showTransactionDetail" />
<!--Transaction detail-->
<MeteredTransactionDetailRow
v-if="row.data.attributes.metadata && showedTransactionDetailById === row.data.id"
:row="row"
/>
</template>
<!--Empty page-->
<template v-slot:empty-page>
<InfoBox style="margin-bottom: 0">
<p>{{ $t('user_invoices.empty') }}</p>
</InfoBox>
</template>
</DatatableWrapper>
</div>
</template>
<script>
import { EyeIcon, FileTextIcon } from 'vue-feather-icons'
import ColorLabel from '../UI/Labels/ColorLabel'
import DatatableWrapper from '../UI/Table/DatatableWrapper'
import FormLabel from '../UI/Labels/FormLabel'
import InfoBox from '../UI/Others/InfoBox'
import { mapGetters } from 'vuex'
import MeteredTransactionDetailRow from './MeteredTransactionDetailRow'
import MeteredTransactionRow from './MeteredTransactionRow'
export default {
name: 'UserTransactionsForMeteredBilling',
components: {
MeteredTransactionDetailRow,
MeteredTransactionRow,
DatatableWrapper,
ColorLabel,
FormLabel,
InfoBox,
FileTextIcon,
EyeIcon,
},
computed: {
...mapGetters(['user']),
columns() {
return this.$store.getters.transactionColumns.filter((column) => column.field !== 'user_id')
},
},
data() {
return {
showedTransactionDetailById: undefined,
}
},
methods: {
showTransactionDetail(id) {
if (this.showedTransactionDetailById === id) this.showedTransactionDetailById = undefined
else this.showedTransactionDetailById = id
},
},
}
</script>

View File

@@ -1,80 +0,0 @@
<template>
<div v-if="canShowForSubscription" class="card shadow-card">
<FormLabel>
{{ $t('update_payments') }}
</FormLabel>
<AppInputButton
:title="$t('update_payment_method')"
:description="$t('payment_method_update_redirect_description')"
:is-last="true"
>
<ButtonBase
@click.native="updatePaymentMethod"
:loading="isGeneratedUpdateLink"
class="w-full sm:w-auto"
button-style="theme"
>
{{ $t('update_payments') }}
</ButtonBase>
</AppInputButton>
</div>
</template>
<script>
import AppInputSwitch from '../Forms/Layouts/AppInputSwitch'
import ButtonBase from '../UI/Buttons/ButtonBase'
import FormLabel from '../UI/Labels/FormLabel'
import axios from 'axios'
import { events } from '../../bus'
import AppInputButton from '../Forms/Layouts/AppInputButton'
export default {
name: 'UserUpdatePaymentMethodsExternally',
components: {
AppInputButton,
AppInputSwitch,
ButtonBase,
FormLabel,
},
computed: {
canShowForSubscription() {
return (
this.hasSubscription &&
!this.subscription.attributes.is_cancelled &&
['paystack', 'paypal'].includes(this.subscription.attributes.driver)
)
},
subscription() {
return this.$store.getters.user.data.relationships.subscription.data
},
hasSubscription() {
return this.$store.getters.user.data.relationships.subscription
},
},
data() {
return {
isGeneratedUpdateLink: false,
}
},
methods: {
updatePaymentMethod() {
this.isGeneratedUpdateLink = true
axios
.post(`/api/subscriptions/edit/${this.subscription.id}`)
.then((response) => {
window.location = response.data.url
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
this.isGeneratedUpdateLink = false
})
},
},
}
</script>

View File

@@ -1,59 +0,0 @@
<template>
<div class="card shadow-card">
<FormLabel icon="bar-chart">
{{ $t('usage_estimates') }}
</FormLabel>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ user.data.meta.usages.costEstimate }}
</b>
<b class="mb-3 mb-5 block text-sm dark:text-gray-500 text-gray-400">
{{ user.data.relationships.subscription.data.attributes.updated_at }}
{{ $t('till_now') }}
</b>
<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="hidden pt-2 text-xs leading-none dark:text-gray-500 text-gray-500 sm:block">
{{ $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>
<small class="mt-6 block font-bold">
{{ $t('records_updated_daily_bases') }}
</small>
</div>
</template>
<script>
import FormLabel from '../UI/Labels/FormLabel'
import { mapGetters } from 'vuex'
export default {
name: 'UserUsageEstimates',
components: {
FormLabel,
},
computed: {
...mapGetters(['user']),
},
}
</script>

View File

@@ -1,75 +0,0 @@
<template>
<div @click="togglePermission" class="permission-toggle">
<b class="privilege">{{ $t(teamPermissions[permission]) }}</b>
<refresh-cw-icon size="14" />
</div>
</template>
<script>
import { RefreshCwIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'PermissionToggleButton',
props: ['item'],
computed: {
...mapGetters(['teamPermissions']),
},
components: {
RefreshCwIcon,
},
data() {
return {
permission: undefined,
}
},
methods: {
togglePermission() {
let index = Object.keys(this.teamPermissions)
.map((i) => i)
.indexOf(this.permission)
if (index === Object.keys(this.teamPermissions).length - 1) {
this.permission = Object.keys(this.teamPermissions)[0]
} else {
this.permission = Object.keys(this.teamPermissions)[index + 1]
}
this.$emit('input', this.permission)
},
},
created() {
this.permission = this.item.permission
},
}
</script>
<style lang="scss" scoped>
@import '../../../../sass/vuefilemanager/inapp-forms';
@import '../../../../sass/vuefilemanager/forms';
.permission-toggle {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
.privilege {
white-space: nowrap;
@include font-size(13);
color: $text-muted;
margin-right: 10px;
}
polyline,
path {
color: $light_text;
}
}
.dark {
.permission-toggle .privilege {
color: $dark_mode_text_secondary;
}
}
</style>

View File

@@ -1,31 +0,0 @@
<template>
<div class="py-3 px-5 text-left">
<div class="info">
<b class="title text-sm">
{{ teamFolder.data.attributes.name }}
</b>
<span class="subtitle mb-2 block text-tiny text-gray-600 dark:text-gray-500">
{{ $t('created_at') }} {{ teamFolder.data.attributes.created_at }}
</span>
<TeamMembersPreview :folder="teamFolder" :avatar-size="32" class="members" />
</div>
</div>
</template>
<script>
import TeamMembersPreview from './TeamMembersPreview'
import { mapGetters } from 'vuex'
export default {
name: 'TeamFolderPreview',
components: {
TeamMembersPreview,
},
computed: {
...mapGetters(['currentTeamFolder', 'clipboard']),
teamFolder() {
return this.currentTeamFolder ? this.currentTeamFolder : this.clipboard[0]
},
},
}
</script>

View File

@@ -1,95 +0,0 @@
<template>
<ul>
<li
v-if="Object.values(members).length > 0 && entry.id !== user.data.id"
v-for="(entry, i) in members"
:key="i"
class="flex items-center py-2"
>
<!--Remove Member-->
<div @click="deleteMember(entry)" class="-ml-1.5 cursor-pointer py-2 px-1 leading-none">
<x-icon size="14" class="vue-feather dark:text-gray-600" />
</div>
<!--Member Preview-->
<div class="flex items-center">
<!--Avatar-->
<MemberAvatar class="mr-3 ml-2" :is-border="false" :size="44" :member="$mapIntoMemberResource(entry)" />
<!--Member-->
<div v-if="entry.type === 'member'" class="info">
<b
class="max-w-1 block overflow-hidden text-ellipsis whitespace-nowrap text-sm font-bold"
style="max-width: 155px"
>
{{ entry.name }}
</b>
<span class="block text-xs text-gray-600 dark:text-gray-500">
{{ entry.email }}
</span>
</div>
<!--Invitation-->
<div v-if="entry.type === 'invitation'" class="info">
<b
class="block max-w-xs overflow-hidden text-ellipsis whitespace-nowrap text-sm font-bold"
style="max-width: 155px"
>
{{ entry.email }}
</b>
<span v-if="entry.id" class="block text-xs text-gray-600 dark:text-gray-500">
{{ $t('waiting_for_accept_invitation') }}
</span>
</div>
</div>
<!--Set member permission-->
<div class="ml-auto">
<PermissionToggleButton @input="updateMemberPermission(entry, $event)" :item="entry" />
</div>
</li>
</ul>
</template>
<script>
import PermissionToggleButton from './PermissionToggleButton'
import MemberAvatar from '../../UI/Others/MemberAvatar'
import { XIcon } from 'vue-feather-icons'
import { mapGetters } from 'vuex'
export default {
name: 'TeamList',
props: ['value'],
computed: {
...mapGetters(['user']),
},
components: {
PermissionToggleButton,
MemberAvatar,
XIcon,
},
data() {
return {
members: undefined,
}
},
methods: {
updateMemberPermission(member, value) {
this.members.map((e) => (e === member ? (e.permission = value) : e))
this.emitMembers()
},
deleteMember(member) {
this.members = this.members.filter((m) => m !== member)
this.emitMembers()
},
emitMembers() {
this.$emit('input', this.members)
},
},
created() {
this.members = this.value
},
}
</script>

View File

@@ -1,35 +0,0 @@
<template>
<div class="w-28">
<div v-if="!teamFolder" class="text-right md:text-center">
<span class="mr-3 align-middle text-tiny text-gray-600 dark:text-gray-500 md:mr-0.5">
{{ $t('not_selected') }}
</span>
</div>
<TeamMembersPreview
v-else
:folder="teamFolder"
:limit="true"
:avatar-size="size"
class="justify-end md:justify-center"
/>
</div>
</template>
<script>
import TeamMembersPreview from './TeamMembersPreview'
import { mapGetters } from 'vuex'
export default {
name: 'TeamMembersButton',
components: {
TeamMembersPreview,
},
props: ['size'],
computed: {
...mapGetters(['currentTeamFolder', 'clipboard']),
teamFolder() {
return this.currentTeamFolder ? this.currentTeamFolder : this.clipboard[0]
},
},
}
</script>

View File

@@ -1,98 +0,0 @@
<template>
<div class="team-folder">
<span v-if="limit && membersCount > 3" class="member-count"> +{{ membersCount - 3 }} </span>
<div class="members">
<div
v-for="member in members"
:key="member.data.id"
:title="member.data.attributes.email"
class="member-preview z-10"
>
<MemberAvatar :is-border="true" :size="34" :member="member" />
</div>
</div>
</div>
</template>
<script>
import MemberAvatar from '../../UI/Others/MemberAvatar'
export default {
name: 'TeamMembersPreview',
props: ['folder', 'limit', 'avatarSize'],
components: {
MemberAvatar,
},
computed: {
membersCount() {
return (
this.folder.data.relationships.members.data.length +
this.folder.data.relationships.invitations.data.length
)
},
members() {
let allMembers = this.folder.data.relationships.members.data.concat(
this.folder.data.relationships.invitations.data
)
if (this.limit) {
return allMembers.slice(0, 3)
}
return allMembers
},
},
}
</script>
<style lang="scss" scoped>
@import 'resources/sass/vuefilemanager/_variables';
@import 'resources/sass/vuefilemanager/_mixins';
.team-folder {
display: flex;
align-items: center;
.member-count {
@include font-size(12);
color: $text-muted;
margin-right: 3px;
opacity: 0.7;
min-width: 14px;
text-align: left;
}
.members {
display: flex;
.member-preview {
margin-left: -10px;
&:first-child {
margin-left: 0;
}
}
.member {
width: 32px;
height: 32px;
object-fit: cover;
border-radius: 10px;
border: 2px solid white;
vertical-align: middle;
}
}
}
.dark {
.team-folder {
.member-count {
color: $dark_mode_text_secondary;
}
.members .member {
border-color: $dark_mode_foreground;
}
}
}
</style>

View File

@@ -1,260 +0,0 @@
<template>
<PopupWrapper name="create-team-folder">
<!--Title-->
<PopupHeader :title="popupTitle" icon="user-plus" />
<!--Content-->
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem v-if="!isNewFolderTeamCreation" class="mb-5" :item="item" />
<!--Form to set team folder-->
<ValidationObserver @submit.prevent="createTeamFolder" ref="teamFolderForm" v-slot="{ invalid }" tag="form">
<!--Set folder name-->
<ValidationProvider
v-if="isNewFolderTeamCreation"
tag="div"
mode="passive"
name="Name"
rules="required"
v-slot="{ errors }"
>
<AppInputText :title="$t('popup_create_folder.label')" :error="errors[0]">
<input
v-model="name"
:class="{ '!border-rose-600': errors[0] }"
type="text"
ref="name"
class="focus-border-theme input-dark"
:placeholder="$t('popup_create_folder.placeholder')"
/>
</AppInputText>
</ValidationProvider>
<!--Add Member-->
<ValidationProvider tag="div" mode="passive" name="Email" v-slot="{ errors }">
<AppInputText :title="$t('add_member')" :error="errors[0]">
<div class="relative">
<span
v-if="email"
@click="addMember"
class="button-base theme absolute right-2 top-1/2 -translate-y-1/2 transform cursor-pointer rounded-lg px-3 py-2 text-sm font-bold"
>
{{ $t('add') }}
</span>
<input
@keypress.enter.stop.prevent="addMember"
ref="email"
v-model="email"
:class="{ '!border-rose-600': errors[0] }"
type="email"
class="focus-border-theme input-dark"
:placeholder="$t('type_member_email_')"
/>
</div>
</AppInputText>
</ValidationProvider>
<!--Member list-->
<ValidationProvider tag="div" mode="passive" name="Members" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('your_members')" :error="errors[0]" :is-last="true">
<span v-if="errors[0]" class="error-message" style="margin-top: -5px">
{{ $t('add_at_least_one_member') }}
</span>
<TeamList v-model="invitations" />
<p v-if="Object.values(invitations).length === 0" class="text-xs dark:text-gray-500">
{{ $t('add_at_least_one_member_into_team_folder') }}
</p>
</AppInputText>
</ValidationProvider>
<InfoBox v-if="!isNewFolderTeamCreation" class="mt-2.5 !mb-0">
<p v-html="$t('popup.move_into_team_disclaimer')"></p>
</InfoBox>
</ValidationObserver>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary"
>{{ $t('cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="createTeamFolder"
button-style="theme"
:loading="isLoading"
:disabled="isLoading"
>{{ popupSubmit }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import AppInputText from '../Forms/Layouts/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from '../Popups/Components/PopupWrapper'
import PopupActions from '../Popups/Components/PopupActions'
import PopupContent from '../Popups/Components/PopupContent'
import PopupHeader from '../Popups/Components/PopupHeader'
import ThumbnailItem from '../UI/Entries/ThumbnailItem'
import ButtonBase from '../UI/Buttons/ButtonBase'
import TeamList from './Components/TeamList'
import { required } from 'vee-validate/dist/rules'
import InfoBox from '../UI/Others/InfoBox'
import { events } from '../../bus'
import axios from 'axios'
import { mapGetters } from 'vuex'
export default {
name: 'CreateTeamFolderPopup',
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
TeamList,
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
InfoBox,
},
computed: {
...mapGetters(['user']),
popupTitle() {
return this.item ? this.$t('convert_as_team_folder') : this.$t('create_team_folder')
},
popupSubmit() {
return this.item ? this.$t('move_and_invite_members') : this.$t('create_team_folder')
},
isNewFolderTeamCreation() {
return !this.item
},
},
data() {
return {
invitations: [],
item: undefined,
name: undefined,
email: undefined,
isLoading: false,
}
},
methods: {
async createTeamFolder() {
const isValid = await this.$refs.teamFolderForm.validate()
if (!isValid) return
this.isLoading = true
let route = this.name ? `/api/teams/folders` : `/api/teams/folders/${this.item.data.id}/convert`
let payload = this.name
? {
name: this.name,
invitations: this.invitations,
}
: {
invitations: this.invitations,
}
axios
.post(route, payload)
.then((response) => {
let isTeamFoldersLocation = this.$isThisRoute(this.$route, ['TeamFolders'])
// Redirect into newly created team folder
if (isTeamFoldersLocation && this.$route.params.id) {
this.$router.push({
name: 'TeamFolders',
params: { id: response.data.data.id },
})
// Add created team folder into Team Folder homepage view
} else if (isTeamFoldersLocation && !this.$route.params.id) {
this.$store.commit('ADD_NEW_FOLDER', response.data)
// Redirect to Team Folders after converting simple folder
} else if (!isTeamFoldersLocation) {
this.$router.push({ name: 'TeamFolders' })
}
let toasterMessage = this.isNewFolderTeamCreation
? this.$t('team_was_invited')
: this.$t('team_was_invited_and_folder_moved')
events.$emit('toaster', {
type: 'success',
message: toasterMessage,
})
this.$store.dispatch('getAppData')
})
.catch(() => this.$isSomethingWrong())
.finally(() => {
this.isLoading = false
this.name = undefined
this.invitations = undefined
this.$closePopup()
})
},
addMember() {
if (!this.$isValidEmail(this.email)) {
this.$refs.teamFolderForm.setErrors({
Email: this.$t('type_valid_email'),
})
return
}
if (this.$cantInviteMember(this.email, this.invitations)) {
this.$refs.teamFolderForm.setErrors({
Email: this.$t('upgrade_to_invite_members'),
})
return
}
this.$refs.teamFolderForm.reset()
this.invitations.unshift({
type: 'invitation',
email: this.email,
permission: 'can-edit',
})
this.email = undefined
},
},
created() {
events.$on('popup:open', (args) => {
if (args.name !== 'create-team-folder') return
this.item = args.item
this.$nextTick(() => {
if (this.$isMobile()) return
if (this.item) this.$refs.email.focus()
if (!this.item && this.$refs.name) this.$refs.name.focus()
})
})
events.$on('popup:close', () => {
setTimeout(() => {
this.email = undefined
this.name = undefined
this.item = undefined
this.invitations = []
}, 150)
})
},
}
</script>

View File

@@ -1,239 +0,0 @@
<template>
<PopupWrapper name="update-team-folder">
<!--Title-->
<PopupHeader :title="$t('edit_team_folder')" icon="user-plus" />
<!--Content-->
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem class="mb-5" :item="item" />
<!--Form to set team folder-->
<ValidationObserver @submit.prevent="updateTeamFolder" ref="teamFolderForm" v-slot="{ invalid }" tag="form">
<!--Add Member-->
<ValidationProvider tag="div" mode="passive" name="Email" v-slot="{ errors }">
<AppInputText :title="$t('add_member')" :error="errors[0]">
<div class="relative">
<span
v-if="email"
@click="addMember"
class="button-base theme absolute right-2 top-1/2 -translate-y-1/2 transform cursor-pointer rounded-lg px-3 py-2 text-sm font-bold"
>
{{ $t('add') }}
</span>
<!--TODO: Fix !pr-20 after JIT official release-->
<input
@keypress.enter.stop.prevent="addMember"
ref="email"
v-model="email"
:class="{ '!border-rose-600': errors[0] }"
type="email"
class="focus-border-theme input-dark !pr-20"
:placeholder="$t('type_member_email_')"
/>
</div>
</AppInputText>
</ValidationProvider>
<!--Member list-->
<ValidationProvider tag="div" mode="passive" name="Members" v-slot="{ errors }">
<label class="input-label">{{ $t('your_members') }}:</label>
<span v-if="errors[0]" class="error-message" style="margin-top: -5px">{{
$t('add_at_least_one_member')
}}</span>
<TeamList v-model="members" />
<TeamList v-model="invitations" />
<p
v-if="Object.values(members).length === 0 && Object.values(invitations).length === 0"
class="text-xs dark:text-gray-500"
>
{{ $t('add_at_least_one_member_into_team_folder') }}
</p>
</ValidationProvider>
</ValidationObserver>
</PopupContent>
<!--Actions-->
<PopupActions>
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary"
>{{ $t('cancel') }}
</ButtonBase>
<ButtonBase
class="w-full"
@click.native="updateTeamFolder"
:button-style="isDisabledSubmit ? 'secondary' : 'theme'"
:loading="isLoading"
:disabled="isLoading || isDisabledSubmit"
>{{ $t('update_team_folder') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import AppInputText from '../Forms/Layouts/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PopupWrapper from '../Popups/Components/PopupWrapper'
import PopupActions from '../Popups/Components/PopupActions'
import PopupContent from '../Popups/Components/PopupContent'
import PopupHeader from '../Popups/Components/PopupHeader'
import ThumbnailItem from '../UI/Entries/ThumbnailItem'
import ButtonBase from '../UI/Buttons/ButtonBase'
import TeamList from './Components/TeamList'
import { required } from 'vee-validate/dist/rules'
import InfoBox from '../UI/Others/InfoBox'
import { events } from '../../bus'
import axios from 'axios'
import { mapGetters } from 'vuex'
export default {
name: 'EditTeamFolderPopup',
components: {
ValidationProvider,
ValidationObserver,
AppInputText,
TeamList,
ThumbnailItem,
PopupWrapper,
PopupActions,
PopupContent,
PopupHeader,
ButtonBase,
required,
InfoBox,
},
computed: {
...mapGetters(['user']),
isDisabledSubmit() {
return Object.values(this.members).length === 0 && Object.values(this.invitations).length === 0
},
},
data() {
return {
invitations: [],
members: [],
item: undefined,
name: undefined,
email: undefined,
isLoading: false,
}
},
methods: {
async updateTeamFolder() {
const isValid = await this.$refs.teamFolderForm.validate()
if (!isValid) return
this.isLoading = true
axios
.patch(`/api/teams/folders/${this.item.data.id}`, {
members: this.members,
invitations: this.invitations,
})
.then((response) => {
this.$store.commit('UPDATE_ITEM', response.data)
this.$store.commit('SET_CURRENT_TEAM_FOLDER', response.data)
events.$emit('toaster', {
type: 'success',
message: this.$t('team_folder_updated'),
})
})
.catch(() => {
events.$emit('toaster', {
type: 'danger',
message: this.$t('popup_error.title'),
})
})
.finally(() => {
this.isLoading = false
this.name = undefined
this.invitations = undefined
this.members = undefined
this.$closePopup()
})
},
addMember() {
if (!this.$isValidEmail(this.email)) {
this.$refs.teamFolderForm.setErrors({
Email: this.$t('type_valid_email'),
})
return
}
if (this.$cantInviteMember(this.email, this.invitations)) {
this.$refs.teamFolderForm.setErrors({
Email: this.$t('upgrade_to_invite_members'),
})
return
}
this.$refs.teamFolderForm.reset()
this.invitations.unshift({
type: 'invitation',
email: this.email,
permission: 'can-edit',
})
this.email = undefined
},
},
mounted() {
events.$on('popup:open', (args) => {
if (args.name !== 'update-team-folder') return
this.item = args.item
this.members = args.item.data.relationships.members.data.map((member) => {
return {
type: 'member',
id: member.data.id,
email: member.data.attributes.email,
name: member.data.attributes.name,
avatar: member.data.attributes.avatar,
color: member.data.attributes.color,
permission: member.data.attributes.permission,
}
})
this.invitations = args.item.data.relationships.invitations.data.map((member) => {
return {
id: member.data.id,
type: 'invitation',
email: member.data.attributes.email,
color: member.data.attributes.color,
permission: member.data.attributes.permission,
}
})
this.$nextTick(() => {
if (this.$refs.email && !this.$isMobile()) this.$refs.email.focus()
})
})
events.$on('popup:close', () => {
setTimeout(() => {
this.email = undefined
this.name = undefined
this.item = undefined
this.invitations = []
this.members = []
}, 150)
})
},
}
</script>
<style scoped lang="scss">
@import '../../../sass/vuefilemanager/inapp-forms';
@import '../../../sass/vuefilemanager/forms';
.item-thumbnail {
margin-bottom: 20px;
}
</style>

View File

@@ -1,17 +1,8 @@
<template>
<div
v-if="toasters.length || notifications.length"
v-if="toasters.length"
class="fixed bottom-4 right-4 left-4 z-[55] sm:w-[360px] sm:left-auto lg:bottom-8 lg:right-8"
>
<ToasterWrapper
v-for="notification in notifications"
:key="notification.data.id"
class="mt-4 overflow-hidden rounded-xl dark:bg-2x-dark-foreground bg-white/80 backdrop-blur-2xl shadow-xl"
bar-color="bg-theme"
>
<Notification :notification="notification" class="z-10 !mb-0 !px-4 !pt-4 !pb-5" />
</ToasterWrapper>
<ToasterWrapper
v-for="(toaster, i) in toasters"
:key="i"
@@ -24,7 +15,6 @@
</template>
<script>
import Notification from '../Notifications/Components/Notification'
import ToasterWrapper from './ToasterWrapper'
import {events} from '../../bus'
import Toaster from './Toaster'
@@ -32,12 +22,10 @@ import Toaster from './Toaster'
export default {
components: {
ToasterWrapper,
Notification,
Toaster,
},
data() {
return {
notifications: [],
toasters: [],
}
},
@@ -51,29 +39,6 @@ export default {
},
created() {
events.$on('toaster', (toaster) => this.toasters.push(toaster))
events.$on('notification', (notification) => this.notifications.push(notification))
/*events.$emit('notification', {
data: {
type: 'file-request',
id: 'df954d23-f9d4-4677-85c8-abfd48aaa090',
attributes: {
action: {
type: 'route',
params: {
route: 'Files',
button: 'Show Files',
id: 'ae37b1d8-c147-489a-83ab-2a3c7cb86263',
},
},
created_at: '',
description: "Your file request for 'Multi Level Folder' folder was filled successfully.",
read_at: '',
title: 'File Request Filled',
category: 'file-request',
},
},
})*/
},
}
</script>

View File

@@ -1,41 +0,0 @@
<template>
<div
v-if="config.allowedFacebookLogin || config.allowedGoogleLogin || config.allowedGithubLogin"
class="mb-10 flex items-center justify-center"
>
<div v-if="config.allowedFacebookLogin" class="mx-5 cursor-pointer">
<facebook-icon @click="socialiteRedirect('facebook')" />
</div>
<div v-if="config.allowedGithubLogin" class="mx-5 cursor-pointer">
<github-icon @click="socialiteRedirect('github')" />
</div>
<div v-if="config.allowedGoogleLogin" class="mx-5 cursor-pointer">
<google-icon @click.native="socialiteRedirect('google')" class="vue-feather"/>
</div>
</div>
</template>
<script>
import { FacebookIcon, GithubIcon } from 'vue-feather-icons'
import GoogleIcon from "../../Icons/GoogleIcon"
import { mapGetters } from 'vuex'
export default {
name: 'SocialLoginButtons',
components: {
FacebookIcon,
GoogleIcon,
GithubIcon,
},
computed: {
...mapGetters(['config']),
},
methods: {
socialiteRedirect(provider) {
this.$store.dispatch('socialiteRedirect', provider)
},
},
}
</script>

View File

@@ -11,29 +11,20 @@
</span>
</div>
</div>
<NotificationBell @click.native="openNotificationPopup" />
</div>
</template>
<script>
import MemberAvatar from './MemberAvatar'
import NotificationBell from '../../Notifications/Components/NotificationBell'
import { events } from '../../../bus'
import { mapGetters } from 'vuex'
export default {
name: 'UserHeadline',
components: {
NotificationBell,
MemberAvatar,
},
computed: {
...mapGetters(['user']),
},
methods: {
openNotificationPopup() {
events.$emit('popup:open', { name: 'notifications-mobile' })
},
},
}
</script>

View File

@@ -1,210 +0,0 @@
<template>
<PopupWrapper name="create-file-request">
<!--Title-->
<PopupHeader :title="$t('create_file_request')" icon="upload" />
<!--Content-->
<PopupContent>
<!--Item Thumbnail-->
<ThumbnailItem v-if="pickedItem" class="mb-5" :item="pickedItem" />
<!--Form to set upload request-->
<ValidationObserver
v-if="!generatedUploadRequest"
@submit.prevent="createUploadRequest"
ref="createForm"
v-slot="{ invalid }"
tag="form"
>
<!--Set name-->
<ValidationProvider
tag="div"
mode="passive"
name="Name"
v-slot="{ errors }"
>
<AppInputText :title="$t('folder_name_optional')" :description="$t('folder_name_optional_description')" :error="errors[0]">
<input
v-model="form.name"
:class="{ '!border-rose-600': errors[0] }"
type="text"
ref="input"
class="focus-border-theme input-dark"
:placeholder="$t('type_name_')"
/>
</AppInputText>
</ValidationProvider>
<!--Set note-->
<ValidationProvider tag="div" mode="passive" name="Note" v-slot="{ errors }">
<AppInputText :title="$t('message_optional')" :description="$t('message_optional_description')" :error="errors[0]">
<textarea
v-model="form.notes"
rows="2"
:class="{ '!border-rose-600': errors[0] }"
type="text"
ref="input"
class="focus-border-theme input-dark"
:placeholder="$t('message_for_recipient')"
></textarea>
</AppInputText>
</ValidationProvider>
<!--Send Request by Email-->
<AppInputSwitch
:title="$t('send_request_by_email')"
:description="$t('send_request_by_email_description')"
:is-last="! shareViaEmail"
>
<SwitchInput v-model="shareViaEmail" :state="shareViaEmail" />
</AppInputSwitch>
<!--Set email-->
<ValidationProvider
v-if="shareViaEmail"
tag="div"
mode="passive"
name="Email"
rules="required"
v-slot="{ errors }"
>
<AppInputText :error="errors[0]" class="-mt-2" :is-last="true">
<input
v-model="form.email"
:class="{ '!border-rose-600': errors[0] }"
type="text"
ref="input"
class="focus-border-theme input-dark"
:placeholder="$t('type_email_')"
/>
</AppInputText>
</ValidationProvider>
</ValidationObserver>
<!--Copy generated link-->
<AppInputText v-if="generatedUploadRequest" :title="$t('copy_upload_request_link')" :is-last="true">
<CopyInput :str="generatedUploadRequest.data.attributes.url" />
</AppInputText>
</PopupContent>
<!--Actions-->
<PopupActions v-if="!generatedUploadRequest">
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="secondary"
>{{ $t('cancel') }}
</ButtonBase>
<ButtonBase class="w-full" @click.native="createUploadRequest" :loading="isLoading" button-style="theme"
>{{ $t('create_request') }}
</ButtonBase>
</PopupActions>
<!--Actions-->
<PopupActions v-if="generatedUploadRequest">
<ButtonBase class="w-full" @click.native="$closePopup()" button-style="theme"
>{{ $t('awesome_iam_done') }}
</ButtonBase>
</PopupActions>
</PopupWrapper>
</template>
<script>
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
import AppInputSwitch from '../Forms/Layouts/AppInputSwitch'
import {required} from 'vee-validate/dist/rules'
import ButtonBase from '../UI/Buttons/ButtonBase'
import AppInputText from '../Forms/Layouts/AppInputText'
import PopupWrapper from '../Popups/Components/PopupWrapper'
import PopupActions from '../Popups/Components/PopupActions'
import PopupContent from '../Popups/Components/PopupContent'
import PopupHeader from '../Popups/Components/PopupHeader'
import SwitchInput from '../Inputs/SwitchInput'
import ThumbnailItem from '../UI/Entries/ThumbnailItem'
import CopyInput from '../Inputs/CopyInput'
import {events} from '../../bus'
import axios from 'axios'
export default {
name: 'CreateUploadRequestPopup',
components: {
ValidationProvider,
ValidationObserver,
AppInputSwitch,
ThumbnailItem,
AppInputText,
PopupWrapper,
PopupActions,
PopupContent,
SwitchInput,
PopupHeader,
ButtonBase,
CopyInput,
required,
},
data() {
return {
form: {
email: undefined,
notes: undefined,
folder_id: undefined,
name: undefined,
},
generatedUploadRequest: undefined,
shareViaEmail: false,
pickedItem: undefined,
isLoading: false,
}
},
methods: {
async createUploadRequest() {
// Validate fields
const isValid = await this.$refs.createForm.validate()
if (!isValid) return
this.isLoading = true
// Send request to get share link
axios
.post(`/api/upload-request`, this.form)
.then((response) => {
this.generatedUploadRequest = response.data
})
.catch(() => {
events.$emit('alert:open', {
title: this.$t('popup_error.title'),
message: this.$t('popup_error.message'),
})
})
.finally(() => {
this.isLoading = false
})
},
},
created() {
events.$on('popup:open', (args) => {
if (args.name === 'create-file-request') {
this.pickedItem = args.item
this.form.folder_id = args.item?.data.id
}
})
// Close popup
events.$on('popup:close', () => {
// Restore data
setTimeout(() => {
this.generatedUploadRequest = undefined
this.pickedItem = undefined
this.shareViaEmail = false
this.form = {
name: undefined,
email: undefined,
notes: undefined,
folder_id: undefined,
}
}, 150)
})
},
}
</script>

View File

@@ -1,15 +0,0 @@
const ValidatorHelpers = {
install(Vue) {
Vue.prototype.$generatePaystackReference = function () {
let text = ''
let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
for (let i = 0; i < 10; i++) text += possible.charAt(Math.floor(Math.random() * possible.length))
return text
}
},
}
export default ValidatorHelpers

View File

@@ -1,31 +1,5 @@
import store from '../store/index'
const ValidatorHelpers = {
install(Vue) {
Vue.prototype.$cantInviteMember = function (email, invitations) {
if (['metered', 'none'].includes(store.getters.config.subscriptionType)) {
return false
}
// Get max team members limitations
let limit = store.getters.user.data.meta.limitations.max_team_members
// Unlimited option
if (limit.total === -1) {
return false
}
// Get emails from invitations and currently active members
let newInvitationEmails = invitations.map((item) => item['email'])
let allowedMemberEmails = limit.meta.allowed_emails
// Get unique list of member emails
let totalUniqueEmails = [...new Set(newInvitationEmails.concat(Object.values(allowedMemberEmails)))]
// If there is more unique emails than can be in limit, disable ability to add member
return totalUniqueEmails.length >= limit.total && !totalUniqueEmails.includes(email)
}
Vue.prototype.$isValidEmail = function (email) {
return email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/) !== null
}

View File

@@ -141,14 +141,6 @@ const FunctionHelpers = {
return source ? store.getters.config.host + '/' + source : ''
}
Vue.prototype.$getCreditCardBrand = function (brand) {
return `/assets/icons/${brand}.svg`
}
Vue.prototype.$getInvoiceLink = function (id) {
return '/invoices/' + id
}
Vue.prototype.$uploadFiles = async function (files) {
// Show alert message when upload is disabled
if (store.getters.user && !store.getters.user.data.meta.restrictions.canUpload) {
@@ -289,14 +281,11 @@ const FunctionHelpers = {
return store.getters.currentFolder.data.attributes.name
} else {
return {
RequestUpload: this.$t('home'),
RecentUploads: this.$t('menu.latest'),
MySharedItems: this.$t('publicly_shared'),
Trash: this.$t('trash'),
Public: this.$t('menu.files'),
Files: this.$t('sidebar.home'),
TeamFolders: this.$t('team_folders'),
SharedWithMe: this.$t('shared_with_me'),
}[this.$route.name]
}
}
@@ -308,8 +297,6 @@ const FunctionHelpers = {
Trash: this.$t('trash'),
Public: this.$t('menu.files'),
Files: this.$t('sidebar.home'),
TeamFolders: this.$t('team_folders'),
SharedWithMe: this.$t('shared_with_me'),
}[this.$route.name]
}
@@ -320,89 +307,21 @@ const FunctionHelpers = {
Trash: 'trash2',
Public: 'hard-drive',
Files: 'hard-drive',
TeamFolders: 'users',
SharedWithMe: 'user-check',
}[this.$router.currentRoute.name]
}
Vue.prototype.$getDataByLocation = function () {
let routes = {
RequestUpload: ['getUploadRequestFolder', router.currentRoute.params.id || undefined ],
Public: ['getSharedFolder', router.currentRoute.params.id || undefined],
Files: ['getFolder', router.currentRoute.params.id || undefined],
RecentUploads: ['getRecentUploads'],
MySharedItems: ['getMySharedItems'],
Trash: ['getTrash', router.currentRoute.params.id || undefined],
TeamFolders: ['getTeamFolder', router.currentRoute.params.id || undefined],
SharedWithMe: ['getSharedWithMeFolder', router.currentRoute.params.id || undefined],
}
store.dispatch(...routes[router.currentRoute.name])
}
Vue.prototype.$getPaymentLogo = function (driver) {
return (
{
paypal: store.getters.isDarkMode
? '/assets/payments/paypal-dark.svg'
: '/assets/payments/paypal.svg',
paystack: store.getters.isDarkMode
? '/assets/payments/paystack-dark.svg'
: '/assets/payments/paystack.svg',
stripe: '/assets/payments/stripe.svg',
system: store.getters.isDarkMode
? this.$getImage(store.getters.config.app_logo_horizontal_dark)
: this.$getImage(store.getters.config.app_logo_horizontal),
}[driver] || this.$getImage(store.getters.config.app_logo_horizontal)
)
}
Vue.prototype.$getSocialLogo = function (driver) {
return {
google: '/assets/socials/google.svg',
facebook: '/assets/socials/facebook.svg',
github: store.getters.isDarkMode ? '/assets/socials/github-dark.svg' : '/assets/socials/github.svg',
}[driver]
}
Vue.prototype.$getSubscriptionStatusColor = function (status) {
return {
active: 'green',
cancelled: 'yellow',
completed: 'purple',
}[status]
}
Vue.prototype.$getTransactionStatusColor = function (status) {
return {
completed: 'green',
cancelled: 'yellow',
error: 'red',
}[status]
}
Vue.prototype.$getTransactionTypeColor = function (type) {
return {
credit: 'green',
charge: 'purple',
withdrawal: 'red',
}[type]
}
Vue.prototype.$getTransactionStatusColor = function (type) {
return {
completed: 'green',
error: 'red',
}[type]
}
Vue.prototype.$getPlanStatusColor = function (type) {
return {
active: 'green',
archived: 'red',
}[type]
}
Vue.prototype.$getUserRoleColor = function (role) {
return {
admin: 'purple',
@@ -410,28 +329,9 @@ const FunctionHelpers = {
}[role]
}
Vue.prototype.$getTransactionTypeTextColor = function (type) {
return {
withdrawal: 'text-red',
credit: 'text-green',
charge: '',
}[type]
}
Vue.prototype.$getTransactionMark = function (type) {
return {
withdrawal: '-',
credit: '+',
charge: '',
}[type]
}
Vue.prototype.$goToFileView = function (id) {
let locations = {
RequestUpload: {name: 'RequestUpload', params: { token: this.$route.params.token, id: id }},
Public: {name: 'Public', params: { token: this.$route.params.token, id: id }},
TeamFolders: { name: 'TeamFolders', params: { id: id } },
SharedWithMe: { name: 'SharedWithMe', params: { id: id } },
MySharedItems: { name: 'Files', params: { id: id } },
Trash: { name: 'Trash', params: { id: id } },
Files: { name: 'Files', params: { id: id } },
@@ -524,19 +424,6 @@ const FunctionHelpers = {
})
}
Vue.prototype.$mapIntoMemberResource = function (entry) {
return {
data: {
attributes: {
avatar: entry.avatar,
name: entry.name,
email: entry.email,
color: entry.color,
},
},
}
}
Vue.prototype.$closePopup = function () {
events.$emit('popup:close')
}
@@ -561,18 +448,6 @@ const FunctionHelpers = {
Vue.prototype.$showMobileMenu = function (name) {
events.$emit('mobile-menu:show', name)
}
Vue.prototype.$openSubscribeOptions = function () {
events.$emit('popup:open', { name: 'select-plan-subscription' })
}
Vue.prototype.$changeSubscriptionOptions = function () {
events.$emit('popup:open', { name: 'change-plan-subscription' })
}
Vue.prototype.$openRemoteUploadPopup = function () {
events.$emit('popup:open', {name: 'remote-upload'})
}
},
}

View File

@@ -103,64 +103,6 @@ const itemHelpers = {
}
}
Vue.prototype.$dissolveTeamFolder = function (folder) {
events.$emit('confirm:open', {
title: i18n.t('really_dissolve_team'),
message: i18n.t(
'really_dissolve_team_desc'
),
action: {
id: folder.data.id,
operation: 'dissolve-team-folder',
},
})
}
Vue.prototype.$detachMeFromTeamFolder = function (folder) {
events.$emit('confirm:open', {
title: i18n.t('really_leave_team'),
message: i18n.t(
"really_leave_team_desc"
),
action: {
id: folder.data.id,
operation: 'leave-team-folder',
},
})
}
Vue.prototype.$createTeamFolder = function () {
// Show alert message when create folder is disabled
if (!store.getters.user.data.meta.restrictions.canCreateTeamFolder) {
Vue.prototype.$temporarilyDisabledFolderCreate()
return
}
events.$emit('popup:open', { name: 'create-team-folder' })
}
Vue.prototype.$convertAsTeamFolder = function (entry) {
events.$emit('popup:open', {
name: 'create-team-folder',
item: entry,
})
}
Vue.prototype.$createFileRequest = function (entry = undefined) {
events.$emit('popup:open', {
name: 'create-file-request',
item: entry,
})
}
Vue.prototype.$updateTeamFolder = function (entry) {
events.$emit('popup:open', {
name: 'update-team-folder',
item: entry,
})
}
Vue.prototype.$removeFavourite = function (folder) {
store.dispatch('removeFromFavourites', folder)
}

View File

@@ -7,7 +7,6 @@ import App from './App.vue'
import store from './store'
import { events } from './bus'
import SubscriptionHelpers from './helpers/SubscriptionHelpers'
import ValidatorHelpers from './helpers/ValidatorHelpers'
import functionHelpers from './helpers/functionHelpers'
import AlertHelpers from './helpers/AlertHelpers'
@@ -15,7 +14,6 @@ import itemHelpers from './helpers/itemHelpers'
import { VueReCaptcha } from 'vue-recaptcha-v3'
Vue.use(VueRouter)
Vue.use(SubscriptionHelpers)
Vue.use(ValidatorHelpers)
Vue.use(functionHelpers)
Vue.use(AlertHelpers)

View File

@@ -1,13 +1,10 @@
import routesUploadRequest from './routes/routesUploadRequest'
import routesMaintenance from './routes/routesMaintenance'
import routesShared from './routes/routesShared'
import routesOthers from './routes/routesOthers'
import routesAdmin from './routes/routesAdmin'
import routesIndex from './routes/routesIndex'
import routesAuth from './routes/routesAuth'
import routesUser from './routes/routesUser'
import routesFile from './routes/routesFile'
import routesTeam from './routes/routesTeam'
import store from './store/index'
import Router from 'vue-router'
import Vue from 'vue'
@@ -17,16 +14,13 @@ Vue.use(Router)
const router = new Router({
mode: 'history',
routes: [
...routesUploadRequest,
...routesMaintenance,
...routesShared,
...routesOthers,
...routesAdmin,
...routesIndex,
...routesAuth,
...routesUser,
...routesFile,
...routesTeam,
],
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {

View File

@@ -17,51 +17,6 @@ const routesAdmin = [
title: 'dashboard',
},
},
{
name: 'Invoices',
path: '/admin/invoices',
component: () => import(/* webpackChunkName: "chunks/invoices" */ '../views/Admin/Invoices'),
meta: {
requiresAuth: true,
title: 'transactions',
},
},
{
name: 'Subscriptions',
path: '/admin/subscriptions',
component: () => import(/* webpackChunkName: "chunks/subscriptions" */ '../views/Admin/Subscriptions'),
meta: {
requiresAuth: true,
title: 'subscriptions',
},
},
{
name: 'Pages',
path: '/admin/pages',
component: () => import(/* webpackChunkName: "chunks/pages" */ '../views/Admin/Pages'),
meta: {
requiresAuth: true,
title: 'pages',
},
},
{
name: 'PageEdit',
path: '/admin/pages/:slug',
component: () => import(/* webpackChunkName: "chunks/page-edit" */ '../views/Admin/Pages/PageEdit'),
meta: {
requiresAuth: true,
title: 'edit_page',
},
},
{
name: 'Plans',
path: '/admin/plans',
component: () => import(/* webpackChunkName: "chunks/plans" */ '../views/Admin/Plans'),
meta: {
requiresAuth: true,
title: 'pricing_plans',
},
},
{
name: 'Users',
path: '/admin/users',
@@ -80,30 +35,6 @@ const routesAdmin = [
title: 'create_user',
},
},
{
name: 'CreateFixedPlan',
path: '/admin/plan/create/fixed',
component: () =>
import(
/* webpackChunkName: "chunks/plan-create/fixed" */ '../views/Admin/Plans/Create/CreateFixedPlan'
),
meta: {
requiresAuth: true,
title: 'create_plan',
},
},
{
name: 'CreateMeteredPlan',
path: '/admin/plan/create/metered',
component: () =>
import(
/* webpackChunkName: "chunks/plan-create/metered" */ '../views/Admin/Plans/Create/CreateMeteredPlan'
),
meta: {
requiresAuth: true,
title: 'create_plan',
},
},
{
path: '/admin/user/:id',
component: () => import(/* webpackChunkName: "chunks/user" */ '../views/Admin/Users/User'),
@@ -136,18 +67,6 @@ const routesAdmin = [
title: 'routes_title.users_storage_usage',
},
},
{
name: 'UserSubscription',
path: '/admin/user/:id/subscription',
component: () =>
import(
/* webpackChunkName: "chunks/user-subscription" */ '../views/Admin/Users/UserTabs/UserSubscription'
),
meta: {
requiresAuth: true,
title: 'subscription',
},
},
{
name: 'UserPassword',
path: '/admin/user/:id/password',
@@ -174,132 +93,6 @@ const routesAdmin = [
},
],
},
{
name: 'PlanFixed',
path: '/admin/plan/:id',
component: () => import(/* webpackChunkName: "chunks/plan" */ '../views/Admin/Plans/FixedPlan'),
meta: {
requiresAuth: true,
title: 'plan',
},
children: [
{
name: 'PlanFixedSubscribers',
path: '/admin/plan/:id/fixed/subscribers',
component: () =>
import(
/* webpackChunkName: "chunks/plan-subscribers" */ '../views/Admin/Plans/Tabs/PlanSubscribers'
),
meta: {
requiresAuth: true,
title: 'subscribers',
},
},
{
name: 'PlanFixedSettings',
path: '/admin/plan/:id/fixed/settings',
component: () =>
import(
/* webpackChunkName: "chunks/plan-settings" */ '../views/Admin/Plans/Tabs/PlanFixedSettings'
),
meta: {
requiresAuth: true,
title: 'plan_settings',
},
},
{
name: 'PlanFixedDelete',
path: '/admin/plan/:id/fixed/delete',
component: () =>
import(/* webpackChunkName: "chunks/plan-delete" */ '../views/Admin/Plans/Tabs/PlanDelete'),
meta: {
requiresAuth: true,
title: 'plan_delete',
},
},
],
},
{
name: 'PlanMetered',
path: '/admin/plan/:id',
component: () => import(/* webpackChunkName: "chunks/plan" */ '../views/Admin/Plans/MeteredPlan'),
meta: {
requiresAuth: true,
title: 'plan',
},
children: [
{
name: 'PlanMeteredSubscribers',
path: '/admin/plan/:id/metered/subscribers',
component: () =>
import(
/* webpackChunkName: "chunks/plan-subscribers" */ '../views/Admin/Plans/Tabs/PlanSubscribers'
),
meta: {
requiresAuth: true,
title: 'subscribers',
},
},
{
name: 'PlanMeteredSettings',
path: '/admin/plan/:id/metered/settings',
component: () =>
import(
/* webpackChunkName: "chunks/plan-settings" */ '../views/Admin/Plans/Tabs/PlanMeteredSettings'
),
meta: {
requiresAuth: true,
title: 'plan_settings',
},
},
{
name: 'PlanMeteredDelete',
path: '/admin/plan/:id/metered/delete',
component: () =>
import(/* webpackChunkName: "chunks/plan-delete" */ '../views/Admin/Plans/Tabs/PlanDelete'),
meta: {
requiresAuth: true,
title: 'plan_delete',
},
},
],
},
{
name: 'PaymentSettings',
path: '/admin/payments',
component: () =>
import(/* webpackChunkName: "chunks/payments" */ '../views/Admin/PaymentSettings/PaymentSettings'),
meta: {
requiresAuth: true,
title: 'Payment Settings',
},
children: [
{
name: 'AppBillings',
path: '/admin/payments/billings',
component: () =>
import(
/* webpackChunkName: "chunks/payments/billings" */ '../views/Admin/PaymentSettings/PaymentSettingsTab/Billings'
),
meta: {
requiresAuth: true,
title: 'billings',
},
},
{
name: 'AppPayments',
path: '/admin/payments/payments',
component: () =>
import(
/* webpackChunkName: "chunks/payments/settings" */ '../views/Admin/PaymentSettings/PaymentSettingsTab/Payments'
),
meta: {
requiresAuth: true,
title: 'payments',
},
},
],
},
{
name: 'AppSettings',
path: '/admin/settings',
@@ -322,18 +115,6 @@ const routesAdmin = [
title: 'appearance',
},
},
{
name: 'AppIndex',
path: '/admin/settings/index',
component: () =>
import(
/* webpackChunkName: "chunks/app-index" */ '../views/Admin/Settings/AppSettingsTabs/Index'
),
meta: {
requiresAuth: true,
title: 'Index',
},
},
{
name: 'AppEnvironment',
path: '/admin/settings/environment',
@@ -358,30 +139,6 @@ const routesAdmin = [
title: 'others',
},
},
{
name: 'AppSignInUp',
path: '/admin/settings/sign-in',
component: () =>
import(
/* webpackChunkName: "chunks/app-sign-in-out" */ '../views/Admin/Settings/AppSettingsTabs/SignInUp'
),
meta: {
requiresAuth: true,
title: 'Sign In/Up',
},
},
{
name: 'AppAdsense',
path: '/admin/settings/adsense',
component: () =>
import(
/* webpackChunkName: "chunks/app-adsense" */ '../views/Admin/Settings/AppSettingsTabs/Adsense'
),
meta: {
requiresAuth: true,
title: 'Adsense',
},
},
{
name: 'AppServer',
path: '/admin/settings/server',

View File

@@ -1,13 +1,4 @@
const routesAuth = [
{
name: 'SuccessfullyVerified',
path: '/successfully-verified',
component: () =>
import(/* webpackChunkName: "chunks/successfully-email-verified" */ '../views/Auth/SuccessfullyEmailVerified'),
meta: {
requiresAuth: false,
},
},
{
name: 'SuccessfullySend',
path: '/successfully-send',
@@ -24,14 +15,6 @@ const routesAuth = [
requiresAuth: false,
},
},
{
name: 'SignUp',
path: '/sign-up',
component: () => import(/* webpackChunkName: "chunks/sign-up" */ '../views/Auth/SignUp'),
meta: {
requiresAuth: false,
},
},
{
name: 'ForgottenPassword',
path: '/forgotten-password',

View File

@@ -38,23 +38,6 @@ const routesFile = [
requiresAuth: true,
},
},
{
name: 'TeamFolders',
path: '/platform/team-folders/:id?',
component: () => import(/* webpackChunkName: "chunks/team-folders" */ '../views/FileView/TeamFolders'),
meta: {
requiresAuth: true,
},
},
{
name: 'SharedWithMe',
path: '/platform/shared-with-me/:id?',
component: () =>
import(/* webpackChunkName: "chunks/shared-with-me" */ '../views/FileView/SharedWithMe'),
meta: {
requiresAuth: true,
},
},
],
},
]

View File

@@ -1,28 +0,0 @@
const routesIndex = [
{
name: 'Homepage',
path: '/',
component: () => import(/* webpackChunkName: "chunks/homepage" */ '../views/Frontpage/Homepage'),
meta: {
requiresAuth: false,
},
},
{
name: 'DynamicPage',
path: '/page/:slug',
component: () => import(/* webpackChunkName: "chunks/dynamic-page" */ '../views/Frontpage/DynamicPage'),
meta: {
requiresAuth: false,
},
},
{
name: 'ContactUs',
path: '/contact-us',
component: () => import(/* webpackChunkName: "chunks/contact-us" */ '../views/Frontpage/ContactUs'),
meta: {
requiresAuth: false,
},
},
]
export default routesIndex

View File

@@ -1,12 +0,0 @@
const routesTeam = [
{
name: 'Invitation',
path: '/team-folder-invitation/:id',
component: () => import(/* webpackChunkName: "chunks/invitation" */ '../views/Teams/Invitation'),
meta: {
requiresAuth: false,
},
},
]
export default routesTeam

View File

@@ -1,23 +0,0 @@
const routesShared = [
{
name: 'Request',
path: '/request',
component: () => import(/* webpackChunkName: "chunks/request" */ '../views/UploadRequest'),
meta: {
requiresAuth: false,
},
children: [
{
name: 'RequestUpload',
path: '/request/:token/upload/:id?',
component: () =>
import(/* webpackChunkName: "chunks/request-upload" */ '../views/FileView/UploadRequestFiles'),
meta: {
requiresAuth: false,
},
},
],
},
]
export default routesShared

View File

@@ -34,15 +34,6 @@ const routesUser = [
title: 'storage',
},
},
{
name: 'Billing',
path: '/user/settings/billing',
component: () => import(/* webpackChunkName: "chunks/billing" */ '../views/User/Billing'),
meta: {
requiresAuth: true,
title: 'billing',
},
},
],
},
]

View File

@@ -1,30 +1,22 @@
import Vuex from 'vuex'
import Vue from 'vue'
import uploadRequest from './modules/uploadRequest'
import fileFunctions from './modules/fileFunctions'
import broadcasting from './modules/broadcasting'
import fileBrowser from './modules/fileBrowser'
import payments from './modules/payments'
import userAuth from './modules/userAuth'
import sharing from './modules/sharing'
import lists from './modules/lists'
import teams from './modules/teams'
import app from './modules/app'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
uploadRequest,
fileFunctions,
broadcasting,
fileBrowser,
payments,
userAuth,
sharing,
lists,
teams,
app,
},
})

View File

@@ -5,8 +5,6 @@ import router from '../../router'
const defaultState = {
isVisibleNavigationBars: localStorage.getItem('is_navigation_bars') !== 'false',
isVisibleNotificationCenter: false,
notificationCount: 0,
isDarkMode: false,
isVisibleSidebar: localStorage.getItem('file_info_visibility') === 'true' || false,
itemViewType: localStorage.getItem('preview_type') || 'list',
@@ -126,60 +124,13 @@ const mutations = {
REPLACE_CONFIG_VALUE(state, { key, value }) {
state.config[key] = value
},
SET_SOCIAL_LOGIN_CONFIGURED(state, service) {
if (service === 'facebook') {
state.config.allowedFacebookLogin = true
state.config.isFacebookLoginConfigured = true
}
if (service === 'google') {
state.config.allowedGoogleLogin = true
state.config.isGoogleLoginConfigured = true
}
if (service === 'github') {
state.config.allowedGithubLogin = true
state.config.isGithubLoginConfigured = true
}
if (service === 'recaptcha') {
state.config.allowedRecaptcha = true
state.config.isRecaptchaConfigured = true
}
},
SET_STRIPE_CREDENTIALS(state, data) {
state.config.stripe_public_key = data.key
state.config.isStripe = true
},
SET_PAYSTACK_CREDENTIALS(state, data) {
state.config.paystack_public_key = data.key
state.config.isPaystack = true
},
SET_PAYPAL_CREDENTIALS(state, data) {
state.config.paypal_client_id = data.key
state.config.isPayPal = true
if (data.live)
state.config.isPayPalLive = data.live
},
UPDATE_DARK_MODE_STATUS(state, val) {
state.isDarkMode = val
},
UPDATE_NOTIFICATION_COUNT(state, val) {
state.notificationCount = val
},
TOGGLE_NOTIFICATION_CENTER(state) {
state.isVisibleNotificationCenter = !state.isVisibleNotificationCenter
},
CLOSE_NOTIFICATION_CENTER(state) {
state.isVisibleNotificationCenter = false
},
}
const getters = {
isVisibleNotificationCenter: (state) => state.isVisibleNotificationCenter,
isVisibleNavigationBars: (state) => state.isVisibleNavigationBars,
notificationCount: (state) => state.notificationCount,
isVisibleSidebar: (state) => state.isVisibleSidebar,
itemViewType: (state) => state.itemViewType,
api: (state) => state.config.api,

View File

@@ -1,84 +0,0 @@
import { events } from '../../bus'
import i18n from "../../i18n"
const defaultState = {
remoteUploadQueue: undefined,
isBroadcasting: false,
}
const actions = {
runConnection: ({ commit, getters, dispatch }) => {
commit('SET_RUNNING_COMMUNICATION')
Echo.private(`App.Users.Models.User.${getters.user.data.id}`)
.listen('.RemoteFile.Created', (event) => {
commit('UPDATE_REMOTE_UPLOAD_QUEUE', event.payload)
// If user is located in same directory as remote upload was called, then show the files
if (
event.payload.file &&
(!getters.currentFolder && !event.payload.file.data.attributes.parent_id) ||
(getters.currentFolder && event.payload.file.data.attributes.parent_id === getters.currentFolder.data.id)
) {
// Add received item into view
commit('ADD_NEW_ITEMS', event.payload.file)
}
if (event.payload.progress.total === event.payload.progress.processed) {
events.$emit('toaster', {
type: 'success',
message: i18n.t('remote_download_finished'),
})
}
})
.notification((notification) => {
// Play audio
new Audio('/audio/blop.wav').play()
// Call toaster notification
events.$emit('notification', {
data: {
type: notification.category,
id: notification.id,
attributes: {
action: notification.action,
description: notification.description,
title: notification.title,
category: notification.category,
},
},
})
// Reload user data to update notifications
dispatch('getAppData')
})
},
}
const mutations = {
SET_RUNNING_COMMUNICATION(state) {
state.isBroadcasting = true
},
UPDATE_REMOTE_UPLOAD_QUEUE(state, payload) {
if (payload.progress.total !== payload.progress.processed) {
state.remoteUploadQueue = {
total: payload.progress.total,
processed: payload.progress.processed,
}
} else {
state.remoteUploadQueue = undefined
}
},
}
const getters = {
remoteUploadQueue: (state) => state.remoteUploadQueue,
isBroadcasting: (state) => state.isBroadcasting,
}
export default {
state: defaultState,
getters,
actions,
mutations,
}

View File

@@ -104,7 +104,6 @@ const actions = {
return new Promise((resolve, reject) => {
// Get route
let route = {
RequestUpload: `/api/upload-request/${router.currentRoute.params.token}/navigation`,
Public: `/api/browse/navigation/${router.currentRoute.params.token}`,
}[router.currentRoute.name] || '/api/browse/navigation'

View File

@@ -59,7 +59,6 @@ const actions = {
// Get route
let route = {
RequestUpload: `/api/upload-request/${router.currentRoute.params.token}/move`,
Public: `/api/editor/move/${router.currentRoute.params.token}`,
}[router.currentRoute.name] || '/api/move'
@@ -87,7 +86,6 @@ const actions = {
createFolder: ({ commit, getters, dispatch }, folder) => {
// Get route
let route = {
RequestUpload: `/api/upload-request/${router.currentRoute.params.token}/create-folder`,
Public: `/api/editor/create-folder/${router.currentRoute.params.token}`,
}[router.currentRoute.name] || '/api/create-folder'
@@ -126,7 +124,6 @@ const actions = {
// Get route
let route = {
RequestUpload: `/api/upload-request/${router.currentRoute.params.token}/rename/${data.id}`,
Public: `/api/editor/rename/${data.id}/${router.currentRoute.params.token}`,
}[router.currentRoute.name] || `/api/rename/${data.id}`
@@ -149,7 +146,6 @@ const actions = {
return new Promise((resolve, reject) => {
// Get route
let route = {
RequestUpload: `/api/upload-request/${router.currentRoute.params.token}/upload`,
Public: `/api/editor/upload/${router.currentRoute.params.token}`,
}[router.currentRoute.name] || '/api/upload'
@@ -317,7 +313,6 @@ const actions = {
// Get route
let route = {
RequestUpload: `/api/upload-request/${router.currentRoute.params.token}/remove`,
Public: `/api/editor/remove/${router.currentRoute.params.token}`,
}[router.currentRoute.name] || '/api/remove'

View File

@@ -89,48 +89,6 @@ const defaultState = {
value: 'sa-east-1',
},
],
transactionColumns: [
{
label: 'note',
field: 'note',
sortable: true,
},
{
label: 'user',
field: 'user_id',
sortable: true,
},
{
label: 'status',
field: 'status',
sortable: true,
},
{
label: 'type',
field: 'type',
sortable: true,
},
{
label: 'total',
field: 'amount',
sortable: true,
},
{
label: 'payed_at',
field: 'created_at',
sortable: true,
},
{
label: 'service',
field: 'driver',
sortable: true,
},
{
label: 'actions',
field: 'actions',
sortable: false,
},
],
roles: [
{
label: 'roles.admin',
@@ -141,24 +99,6 @@ const defaultState = {
value: 'user',
},
],
subscriptionTypes: [
{
label: 'Metered',
value: 'metered',
},
{
label: 'Fixed',
value: 'fixed',
},
{
label: 'None',
value: 'none',
},
],
teamPermissions: {
'can-edit': 'can_edit',
'can-view': 'can_view',
},
countries: [
{ label: 'Czech Republic', value: 'CZ' },
{ label: 'Slovakia', value: 'SK' },
@@ -434,558 +374,6 @@ const defaultState = {
value: 168,
},
],
intervalList: [
{
label: 'monthly',
value: 'month',
},
{
label: 'yearly',
value: 'year',
},
],
currencyList: [
{
label: 'USD - United States Dollar',
value: 'USD',
},
{
label: 'EUR - Euro',
value: 'EUR',
},
{
label: 'GBP - British Pound',
value: 'GBP',
},
{
label: 'AFN - Afghan Afghani',
value: 'AFN',
},
{
label: 'ALL - Albanian Lek',
value: 'ALL',
},
{
label: 'DZD - Algerian Dinar',
value: 'DZD',
},
{
label: 'AOA - Angolan Kwanza',
value: 'AOA',
},
{
label: 'ARS - Argentine Peso',
value: 'ARS',
},
{
label: 'AMD - Armenian Dram',
value: 'AMD',
},
{
label: 'AWG - Aruban Florin',
value: 'AWG',
},
{
label: 'AUD - Australian Dollar',
value: 'AUD',
},
{
label: 'AZN - Azerbaijani Manat',
value: 'AZN',
},
{
label: 'BDT - Bangladeshi Taka',
value: 'BDT',
},
{
label: 'BBD - Barbadian Dollar',
value: 'BBD',
},
{
label: 'BZD - Belize Dollar',
value: 'BZD',
},
{
label: 'BMD - Bermudian Dollar',
value: 'BMD',
},
{
label: 'BOB - Bolivian Boliviano',
value: 'BOB',
},
{
label: 'BAM - Bosnia & Herzegovina Convertible Mark',
value: 'BAM',
},
{
label: 'BWP - Botswana Pula',
value: 'BWP',
},
{
label: 'BRL - Brazilian Real',
value: 'BRL',
},
{
label: 'BND - Brunei Dollar',
value: 'BND',
},
{
label: 'BGN - Bulgarian Lev',
value: 'BGN',
},
{
label: 'BIF - Burundian Franc',
value: 'BIF',
},
{
label: 'KHR - Cambodian Riel',
value: 'KHR',
},
{
label: 'CAD - Canadian Dollar',
value: 'CAD',
},
{
label: 'CVE - Cape Verdean Escudo',
value: 'CVE',
},
{
label: 'KYD - Cayman Islands Dollar',
value: 'KYD',
},
{
label: 'XAF - Central African Cfa Franc',
value: 'XAF',
},
{
label: 'XPF - Cfp Franc',
value: 'XPF',
},
{
label: 'CLP - Chilean Peso',
value: 'CLP',
},
{
label: 'CNY - Chinese Renminbi Yuan',
value: 'CNY',
},
{
label: 'COP - Colombian Peso',
value: 'COP',
},
{
label: 'KMF - Comorian Franc',
value: 'KMF',
},
{
label: 'CDF - Congolese Franc',
value: 'CDF',
},
{
label: 'CRC - Costa Rican Colón',
value: 'CRC',
},
{
label: 'HRK - Croatian Kuna',
value: 'HRK',
},
{
label: 'CZK - Czech Koruna',
value: 'CZK',
},
{
label: 'DKK - Danish Krone',
value: 'DKK',
},
{
label: 'DJF - Djiboutian Franc',
value: 'DJF',
},
{
label: 'DOP - Dominican Peso',
value: 'DOP',
},
{
label: 'XCD - East Caribbean Dollar',
value: 'XCD',
},
{
label: 'EGP - Egyptian Pound',
value: 'EGP',
},
{
label: 'ETB - Ethiopian Birr',
value: 'ETB',
},
{
label: 'FKP - Falkland Islands Pound',
value: 'FKP',
},
{
label: 'FJD - Fijian Dollar',
value: 'FJD',
},
{
label: 'GMD - Gambian Dalasi',
value: 'GMD',
},
{
label: 'GEL - Georgian Lari',
value: 'GEL',
},
{
label: 'GIP - Gibraltar Pound',
value: 'GIP',
},
{
label: 'GTQ - Guatemalan Quetzal',
value: 'GTQ',
},
{
label: 'GNF - Guinean Franc',
value: 'GNF',
},
{
label: 'GYD - Guyanese Dollar',
value: 'GYD',
},
{
label: 'HTG - Haitian Gourde',
value: 'HTG',
},
{
label: 'HNL - Honduran Lempira',
value: 'HNL',
},
{
label: 'HKD - Hong Kong Dollar',
value: 'HKD',
},
{
label: 'HUF - Hungarian Forint',
value: 'HUF',
},
{
label: 'ISK - Icelandic Króna',
value: 'ISK',
},
{
label: 'INR - Indian Rupee',
value: 'INR',
},
{
label: 'IDR - Indonesian Rupiah',
value: 'IDR',
},
{
label: 'ILS - Israeli New Sheqel',
value: 'ILS',
},
{
label: 'JMD - Jamaican Dollar',
value: 'JMD',
},
{
label: 'JPY - Japanese Yen',
value: 'JPY',
},
{
label: 'KZT - Kazakhstani Tenge',
value: 'KZT',
},
{
label: 'KES - Kenyan Shilling',
value: 'KES',
},
{
label: 'KGS - Kyrgyzstani Som',
value: 'KGS',
},
{
label: 'LAK - Lao Kip',
value: 'LAK',
},
{
label: 'LBP - Lebanese Pound',
value: 'LBP',
},
{
label: 'LSL - Lesotho Loti',
value: 'LSL',
},
{
label: 'LRD - Liberian Dollar',
value: 'LRD',
},
{
label: 'MOP - Macanese Pataca',
value: 'MOP',
},
{
label: 'MKD - Macedonian Denar',
value: 'MKD',
},
{
label: 'MGA - Malagasy Ariary',
value: 'MGA',
},
{
label: 'MWK - Malawian Kwacha',
value: 'MWK',
},
{
label: 'MYR - Malaysian Ringgit',
value: 'MYR',
},
{
label: 'MVR - Maldivian Rufiyaa',
value: 'MVR',
},
{
label: 'MRO - Mauritanian Ouguiya',
value: 'MRO',
},
{
label: 'MUR - Mauritian Rupee',
value: 'MUR',
},
{
label: 'MXN - Mexican Peso',
value: 'MXN',
},
{
label: 'MDL - Moldovan Leu',
value: 'MDL',
},
{
label: 'MNT - Mongolian Tögrög',
value: 'MNT',
},
{
label: 'MAD - Moroccan Dirham',
value: 'MAD',
},
{
label: 'MZN - Mozambican Metical',
value: 'MZN',
},
{
label: 'MMK - Myanmar Kyat',
value: 'MMK',
},
{
label: 'NAD - Namibian Dollar',
value: 'NAD',
},
{
label: 'NPR - Nepalese Rupee',
value: 'NPR',
},
{
label: 'ANG - Netherlands Antillean Gulden',
value: 'ANG',
},
{
label: 'TWD - New Taiwan Dollar',
value: 'TWD',
},
{
label: 'NZD - New Zealand Dollar',
value: 'NZD',
},
{
label: 'NIO - Nicaraguan Córdoba',
value: 'NIO',
},
{
label: 'NGN - Nigerian Naira',
value: 'NGN',
},
{
label: 'NOK - Norwegian Krone',
value: 'NOK',
},
{
label: 'PKR - Pakistani Rupee',
value: 'PKR',
},
{
label: 'PAB - Panamanian Balboa',
value: 'PAB',
},
{
label: 'PGK - Papua New Guinean Kina',
value: 'PGK',
},
{
label: 'PYG - Paraguayan Guaraní',
value: 'PYG',
},
{
label: 'PEN - Peruvian Nuevo Sol',
value: 'PEN',
},
{
label: 'PHP - Philippine Peso',
value: 'PHP',
},
{
label: 'PLN - Polish Złoty',
value: 'PLN',
},
{
label: 'QAR - Qatari Riyal',
value: 'QAR',
},
{
label: 'RON - Romanian Leu',
value: 'RON',
},
{
label: 'RUB - Russian Ruble',
value: 'RUB',
},
{
label: 'RWF - Rwandan Franc',
value: 'RWF',
},
{
label: 'STD - São Tomé and Príncipe Dobra',
value: 'STD',
},
{
label: 'SHP - Saint Helenian Pound',
value: 'SHP',
},
{
label: 'SVC - Salvadoran Colón',
value: 'SVC',
},
{
label: 'WST - Samoan Tala',
value: 'WST',
},
{
label: 'SAR - Saudi Riyal',
value: 'SAR',
},
{
label: 'RSD - Serbian Dinar',
value: 'RSD',
},
{
label: 'SCR - Seychellois Rupee',
value: 'SCR',
},
{
label: 'SLL - Sierra Leonean Leone',
value: 'SLL',
},
{
label: 'SGD - Singapore Dollar',
value: 'SGD',
},
{
label: 'SBD - Solomon Islands Dollar',
value: 'SBD',
},
{
label: 'SOS - Somali Shilling',
value: 'SOS',
},
{
label: 'ZAR - South African Rand',
value: 'ZAR',
},
{
label: 'KRW - South Korean Won',
value: 'KRW',
},
{
label: 'LKR - Sri Lankan Rupee',
value: 'LKR',
},
{
label: 'SRD - Surinamese Dollar',
value: 'SRD',
},
{
label: 'SZL - Swazi Lilangeni',
value: 'SZL',
},
{
label: 'SEK - Swedish Krona',
value: 'SEK',
},
{
label: 'CHF - Swiss Franc',
value: 'CHF',
},
{
label: 'TJS - Tajikistani Somoni',
value: 'TJS',
},
{
label: 'TZS - Tanzanian Shilling',
value: 'TZS',
},
{
label: 'THB - Thai Baht',
value: 'THB',
},
{
label: 'TOP - Tongan Paʻanga',
value: 'TOP',
},
{
label: 'TTD - Trinidad and Tobago Dollar',
value: 'TTD',
},
{
label: 'TRY - Turkish Lira',
value: 'TRY',
},
{
label: 'UGX - Ugandan Shilling',
value: 'UGX',
},
{
label: 'UAH - Ukrainian Hryvnia',
value: 'UAH',
},
{
label: 'AED - United Arab Emirates Dirham',
value: 'AED',
},
{
label: 'UYU - Uruguayan Peso',
value: 'UYU',
},
{
label: 'UZS - Uzbekistani Som',
value: 'UZS',
},
{
label: 'VUV - Vanuatu Vatu',
value: 'VUV',
},
{
label: 'VND - Vietnamese Đồng',
value: 'VND',
},
{
label: 'XOF - West African Cfa Franc',
value: 'XOF',
},
{
label: 'YER - Yemeni Rial',
value: 'YER',
},
{
label: 'ZMW - Zambian Kwacha',
value: 'ZMW',
},
],
timezones: [
{
value: '-12.0',
@@ -1115,12 +503,7 @@ const defaultState = {
}
const getters = {
transactionColumns: (state) => state.transactionColumns,
subscriptionTypes: (state) => state.subscriptionTypes,
teamPermissions: (state) => state.teamPermissions,
expirationList: (state) => state.expirationList,
currencyList: (state) => state.currencyList,
intervalList: (state) => state.intervalList,
timezones: (state) => state.timezones,
countries: (state) => state.countries,
s3Regions: (state) => state.s3Regions,

View File

@@ -1,32 +0,0 @@
import { events } from '../../bus'
const defaultState = {
singleChargeAmount: undefined,
}
const actions = {
callSingleChargeProcess: ({ commit }, amount) => {
// Open popup with payment methods
events.$emit('popup:open', { name: 'select-payment-method' })
// Store charge amount
commit('SET_SINGLE_CHARGE_AMOUNT', amount)
},
}
const mutations = {
SET_SINGLE_CHARGE_AMOUNT(state, amount) {
state.singleChargeAmount = amount
},
}
const getters = {
singleChargeAmount: (state) => state.singleChargeAmount,
}
export default {
state: defaultState,
getters,
actions,
mutations,
}

View File

@@ -1,129 +0,0 @@
import router from '../../router'
import { events } from '../../bus'
import i18n from '../../i18n'
import axios from 'axios'
import Vue from 'vue'
const defaultState = {
currentTeamFolder: undefined,
}
const actions = {
getTeamFolder: ({ commit, getters }, id) => {
commit('LOADING_STATE', { loading: true, data: [] })
if (typeof id === 'undefined') {
commit('SET_CURRENT_TEAM_FOLDER', null)
}
axios
.get(`${getters.api}/teams/folders/${id}${getters.sorting.URI}`)
.then((response) => {
let folders = response.data.folders.data
let files = response.data.files.data
commit('LOADING_STATE', {
loading: false,
data: folders.concat(files),
})
commit('SET_CURRENT_FOLDER', response.data.root)
if (
!getters.currentTeamFolder ||
getters.currentTeamFolder.data.id !== response.data.teamFolder.data.id
) {
commit('SET_CURRENT_TEAM_FOLDER', response.data.teamFolder)
}
events.$emit('scrollTop')
})
.catch((error) => {
// Redirect if unauthenticated
if ([401, 403].includes(error.response.status)) {
commit('SET_AUTHORIZED', false)
router.push({ name: 'SignIn' })
} else {
// Show error message
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
}
})
},
getSharedWithMeFolder: ({ commit, getters }, id) => {
commit('LOADING_STATE', { loading: true, data: [] })
if (typeof id === 'undefined') {
commit('SET_CURRENT_TEAM_FOLDER', null)
}
axios
.get(`${getters.api}/teams/shared-with-me/${id}${getters.sorting.URI}`)
.then((response) => {
let folders = response.data.folders.data
let files = response.data.files.data
commit('LOADING_STATE', {
loading: false,
data: folders.concat(files),
})
commit('SET_CURRENT_FOLDER', response.data.root)
if (
!getters.currentTeamFolder ||
getters.currentTeamFolder.data.id !== response.data.teamFolder.data.id
) {
commit('SET_CURRENT_TEAM_FOLDER', response.data.teamFolder)
}
events.$emit('scrollTop')
})
.catch((error) => {
// Redirect if unauthenticated
if ([401, 403].includes(error.response.status)) {
commit('SET_AUTHORIZED', false)
router.push({ name: 'SignIn' })
} else {
// Show error message
events.$emit('alert:open', {
title: i18n.t('popup_error.title'),
message: i18n.t('popup_error.message'),
})
}
})
},
getTeamFolderTree: ({ commit, getters }) => {
return new Promise((resolve, reject) => {
axios
.get(`/api/teams/folders/${getters.currentTeamFolder.data.id}/tree${getters.sorting.URI}`)
.then((response) => {
resolve(response)
commit('UPDATE_FOLDER_TREE', response.data)
})
.catch((error) => {
reject(error)
Vue.prototype.$isSomethingWrong()
})
})
},
}
const mutations = {
SET_CURRENT_TEAM_FOLDER(state, payload) {
state.currentTeamFolder = payload
},
}
const getters = {
currentTeamFolder: (state) => state.currentTeamFolder,
}
export default {
state: defaultState,
getters,
actions,
mutations,
}

View File

@@ -1,83 +0,0 @@
import router from '../../router'
import { events } from '../../bus'
import axios from 'axios'
import Vue from 'vue'
const defaultState = {
uploadRequest: undefined,
}
const actions = {
getUploadRequestFolder: ({ commit, getters }, id) => {
commit('LOADING_STATE', { loading: true, data: [] })
return new Promise((resolve, reject) => {
axios
.get(`/api/upload-request/${router.currentRoute.params.token}/browse/${id}${getters.sorting.URI}`)
.then((response) => {
let folders = response.data.folders.data
let files = response.data.files.data
commit('LOADING_STATE', {
loading: false,
data: folders.concat(files),
})
commit('SET_CURRENT_FOLDER', response.data.root)
events.$emit('scrollTop')
resolve(response)
})
.catch((error) => {
Vue.prototype.$isSomethingWrong()
reject(error)
})
})
},
getUploadRequestDetail: ({ commit }) => {
return new Promise((resolve, reject) => {
axios.get(`/api/upload-request/${router.currentRoute.params.token}`)
.then((response) => {
resolve(response)
// Stop loading spinner
if (['active', 'filled', 'expired'].includes(response.data.data.attributes.status) )
commit('LOADING_STATE', { loading: false, data: [] })
commit('SET_UPLOAD_REQUEST', response.data)
// Set current folder if exist
if (! router.currentRoute.params.id) {
commit('SET_CURRENT_FOLDER', response.data.data.relationships.folder)
}
})
})
},
closeUploadRequest: ({ commit }) => {
axios
.delete(`/api/upload-request/${router.currentRoute.params.token}`)
.then((response) => {
commit('LOADING_STATE', { loading: false, data: [] })
commit('SET_UPLOAD_REQUEST', response.data)
})
.catch(() => this.$isSomethingWrong())
},
}
const mutations = {
SET_UPLOAD_REQUEST(state, payload) {
state.uploadRequest = payload
},
}
const getters = {
uploadRequest: (state) => state.uploadRequest,
}
export default {
state: defaultState,
getters,
actions,
mutations,
}

View File

@@ -16,11 +16,6 @@ const actions = {
resolve(response)
commit('RETRIEVE_USER', response.data)
commit('UPDATE_NOTIFICATION_COUNT', response.data.data.relationships.unreadNotifications.data.length)
if (! getters.isBroadcasting && getters.config.broadcasting === 'pusher') {
dispatch('runConnection')
}
})
.catch((error) => {
reject(error)
@@ -53,16 +48,6 @@ const actions = {
router.push({name: 'Homepage'})
})
},
socialiteRedirect: ({ commit }, provider) => {
axios
.get(`/api/socialite/${provider}/redirect`)
.then((response) => {
if (response.data.url) {
window.location.href = response.data.url
}
})
.catch(() => this.$isSomethingWrong())
},
addToFavourites: (context, folder) => {
let addFavourites = []
let items = [folder]
@@ -116,19 +101,6 @@ const actions = {
})
.catch(() => Vue.prototype.$isSomethingWrong())
},
readAllNotifications: ({ commit }) => {
axios.post('/api/user/notifications/read')
.then(() => {
commit('UPDATE_NOTIFICATION_COUNT', 0)
})
},
deleteAllNotifications: ({ commit }) => {
axios.delete('/api/user/notifications')
.then(() => {
commit('FLUSH_NOTIFICATIONS')
})
.catch(() => Vue.prototype.$isSomethingWrong())
},
}
const mutations = {
@@ -178,34 +150,9 @@ const mutations = {
}
})
},
FLUSH_NOTIFICATIONS(state) {
state.user.data.relationships.readNotifications.data = []
state.user.data.relationships.unreadNotifications.data = []
},
CLEAR_NOTIFICATION_ACTION_DATA(state, notificationId) {
if (state.user.data.relationships.readNotifications.data.length) {
state.user.data.relationships.readNotifications.data.map(notification => {
if (notification.data.id === notificationId) {
notification.data.attributes.action = undefined
}
})
}
if (state.user.data.relationships.unreadNotifications.data.length) {
state.user.data.relationships.unreadNotifications.data.map(notification => {
if (notification.data.id === notificationId) {
notification.data.attributes.action = undefined
}
})
}
},
}
const getters = {
isLimitedUser: (state) =>
state.user &&
state.user.data.relationships.failedPayments &&
state.user.data.relationships.failedPayments.data.length === 3,
permission: (state) => state.permission,
user: (state) => state.user,
}

View File

@@ -4,12 +4,6 @@
<FilePreview />
<Spotlight />
<!--Spotlight Addons-->
<CreateUploadRequestPopup />
<CreateTeamFolderPopup />
<NotificationsPopup />
<RemoteUploadPopup />
<!--Mobile Navigation-->
<MobileNavigation />
@@ -18,7 +12,6 @@
<!-- Create language popup -->
<CreateLanguage />
<MobileNavigationToolbar />
<ContentSidebar>
@@ -84,61 +77,13 @@ import {
UsersIcon,
} from 'vue-feather-icons'
import { mapGetters } from 'vuex'
import CreateUploadRequestPopup from "../components/UploadRequest/CreateUploadRequestPopup";
import CreateTeamFolderPopup from "../components/Teams/CreateTeamFolderPopup";
import NotificationsPopup from "../components/Notifications/NotificationsPopup";
import RemoteUploadPopup from "../components/RemoteUpload/RemoteUploadPopup";
export default {
name: 'Admin',
computed: {
...mapGetters(['isVisibleNavigationBars', 'config']),
nav() {
let subscriptionLinks = {
metered: [
{
title: this.$t('payments'),
route: 'PaymentSettings',
icon: 'card',
},
{
title: this.$t('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('plans'),
route: 'Plans',
icon: 'database',
linkActivation: ['plans', 'plan'],
},
{
title: this.$t('transactions'),
route: 'Invoices',
icon: 'file-text',
},
],
}[this.config.subscriptionType]
let sections = [
return [
{
groupCollapsable: false,
groupTitle: this.$t('admin'),
@@ -159,43 +104,17 @@ export default {
route: 'AppSettings',
icon: 'settings',
},
],
},
{
groupCollapsable: false,
groupTitle: this.$t('content'),
groupLinks: [
{
title: this.$t('pages'),
route: 'Pages',
icon: 'monitor',
},
{
title: this.$t('languages'),
route: 'Language',
icon: 'globe',
},
{
title: this.$t('languages'),
route: 'Language',
icon: 'globe',
},
],
},
]
// Push subscription if there is metered or fixed type
if (this.config.subscriptionType !== 'none') {
sections.push({
groupCollapsable: false,
groupTitle: this.$t('subscription'),
groupLinks: subscriptionLinks,
})
}
return sections
},
},
components: {
RemoteUploadPopup,
NotificationsPopup,
CreateTeamFolderPopup,
CreateUploadRequestPopup,
MobileNavigationToolbar,
FilePreview,
Spotlight,

View File

@@ -6,11 +6,6 @@
<AppSpecification v-if="config.isAdminVueFileManagerBar" :data="data" class="hidden lg:flex" />
<AppSpecification v-if="config.isAdminVueFileManagerBar" :data="data" class="card shadow-card lg:hidden" />
<!--Create metered plan alert-->
<AlertBox v-if="config.subscriptionType === 'metered' && config.isEmptyPlans" color="rose">
As you installed app with metered subscription type, you have to <router-link :to="{ name: 'CreateMeteredPlan' }" class="dark:text-rose-500 text-sm font-bold underline">create your plan</router-link> as soon as possible to prevent new user registration without automatically assigned subscription plan.
</AlertBox>
<!--Cron Alert-->
<AlertBox v-if="!data.app.isRunningCron && !config.isDev" color="rose">
We detect your cron jobs probably doesn't work correctly, please check it, you need it for running app correctly. If you set your cron job, please get back one minute later.
@@ -50,22 +45,6 @@
<chevron-right-icon size="16" class="text-theme vue-feather" />
</router-link>
</div>
<div v-if="config.subscriptionType !== 'none'" class="card mb-4 w-full shadow-card md:mb-0">
<FormLabel icon="dollar">
{{ $t('earnings') }}
</FormLabel>
<b class="-mt-3 mb-0.5 block text-2xl font-extrabold sm:text-3xl">
{{ data.app.earnings }}
</b>
<router-link :to="{ name: 'Invoices' }" class="mt-6 flex items-center">
<span class="mr-2 whitespace-nowrap text-xs font-bold">
{{ $t('show_all_transactions') }}
</span>
<chevron-right-icon size="16" class="text-theme vue-feather" />
</router-link>
</div>
</div>
<!--Upload bandwidth widgets-->
@@ -110,18 +89,6 @@
<WidgetLatestRegistrations />
</div>
<!--Latest transactions widgets-->
<div
v-if="['fixed', 'metered'].includes(this.config.subscriptionType)"
class="card mb-4 shadow-card md:mb-6"
>
<FormLabel icon="dollar">
{{ $t('latest_transactions') }}
</FormLabel>
<WidgetLatestTransactions />
</div>
</div>
<div id="loader" v-if="isLoading">
<Spinner />
@@ -137,7 +104,6 @@ import FormLabel from '../../components/UI/Labels/FormLabel'
import BarChart from '../../components/UI/BarChart/BarChart'
import {mapGetters} from 'vuex'
import axios from 'axios'
import WidgetLatestTransactions from '../../components/Dashboard/Widgets/WidgetLatestTransactions'
import AlertBox from "../../components/UI/Others/AlertBox";
import AppSpecification from "../../components/Dashboard/AppSpecification";
@@ -146,7 +112,6 @@ export default {
components: {
AppSpecification,
AlertBox,
WidgetLatestTransactions,
WidgetLatestRegistrations,
ChevronRightIcon,
FormLabel,

View File

@@ -1,90 +0,0 @@
<template>
<div>
<!--Datatable-->
<DatatableWrapper
v-if="!config.isEmptyTransactions"
class="card overflow-x-auto shadow-card"
api="/api/admin/transactions"
:paginator="true"
:columns="columns"
>
<template slot-scope="{ row }">
<!--Transaction rows-->
<MeteredTransactionRow
v-if="config.subscriptionType === 'metered'"
:row="row"
:user="true"
@showDetail="showTransactionDetail"
/>
<FixedTransactionRow v-if="config.subscriptionType === 'fixed'" :row="row" :user="true" />
<!--Transaction detail-->
<MeteredTransactionDetailRow
v-if="row.data.attributes.metadata && showedTransactionDetailById === row.data.id"
:row="row"
/>
</template>
</DatatableWrapper>
<!--Empty State-->
<div v-if="config.isEmptyTransactions" class="flex items-center justify-center fixed top-0 bottom-0 left-0 right-0">
<div class="text-center">
<img
class="mb-6 inline-block w-28"
src="https://twemoji.maxcdn.com/v/13.1.0/svg/1f9ee.svg"
alt="transaction"
/>
<h1 class="mb-1 text-2xl font-bold">
{{ $t('there_is_nothing') }}
</h1>
<p class="text-sm text-gray-600">
{{ $t('transaction_will_be_here') }}
</p>
</div>
</div>
</div>
</template>
<script>
import FixedTransactionRow from '../../components/Subscription/FixedTransactionRow'
import MeteredTransactionDetailRow from '../../components/Subscription/MeteredTransactionDetailRow'
import MeteredTransactionRow from '../../components/Subscription/MeteredTransactionRow'
import MemberAvatar from '../../components/UI/Others/MemberAvatar'
import DatatableWrapper from '../../components/UI/Table/DatatableWrapper'
import ColorLabel from '../../components/UI/Labels/ColorLabel'
import { mapGetters } from 'vuex'
export default {
name: 'Invoices',
components: {
MeteredTransactionDetailRow,
MeteredTransactionRow,
FixedTransactionRow,
DatatableWrapper,
MemberAvatar,
ColorLabel,
},
computed: {
...mapGetters(['config']),
columns() {
if (config.subscriptionType === 'fixed') {
return this.$store.getters.transactionColumns.filter((column) => !['type'].includes(column.field))
}
return this.$store.getters.transactionColumns
},
},
data() {
return {
showedTransactionDetailById: undefined,
}
},
methods: {
showTransactionDetail(id) {
if (this.showedTransactionDetailById === id) this.showedTransactionDetailById = undefined
else this.showedTransactionDetailById = id
},
},
}
</script>

View File

@@ -1,122 +0,0 @@
<template>
<div>
<div class="card shadow-card">
<DatatableWrapper
@init="isLoading = false"
api="/api/admin/pages"
:paginator="false"
:columns="columns"
class="overflow-x-auto"
>
<template slot-scope="{ row }">
<tr class="whitespace-nowrap border-b border-dashed border-light dark:border-opacity-5">
<td class="py-5 pr-3 md:pr-1">
<router-link
:to="{
name: 'PageEdit',
params: { slug: row.data.attributes.slug },
}"
class="cursor-pointer text-sm font-bold"
tag="div"
>
{{ row.data.attributes.title }}
</router-link>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
{{ row.data.attributes.slug }}
</span>
</td>
<td class="px-3 md:px-1">
<span class="text-sm font-bold">
<SwitchInput
@input="
$updateText(
`/admin/pages/${row.data.id}`,
'visibility',
row.data.attributes.visibility
)
"
v-model="row.data.attributes.visibility"
:state="row.data.attributes.visibility"
class="switch"
/>
</span>
</td>
<td class="pl-3 text-right md:pl-1">
<div class="flex w-full justify-end space-x-2">
<router-link
class="flex h-8 w-8 items-center justify-center rounded-md bg-light-background transition-colors hover:bg-green-100 dark:bg-2x-dark-foreground"
:to="{
name: 'PageEdit',
params: {
slug: row.data.attributes.slug,
},
}"
>
<Edit2Icon size="15" class="opacity-75" />
</router-link>
</div>
</td>
</tr>
</template>
</DatatableWrapper>
</div>
</div>
</template>
<script>
import DatatableWrapper from '../../components/UI/Table/DatatableWrapper'
import MobileActionButton from '../../components/UI/Buttons/MobileActionButton'
import EmptyPageContent from '../../components/Others/EmptyPageContent'
import SwitchInput from '../../components/Inputs/SwitchInput'
import MobileHeader from '../../components/Mobile/MobileHeader'
import SectionTitle from '../../components/UI/Labels/SectionTitle'
import ButtonBase from '../../components/UI/Buttons/ButtonBase'
import { Trash2Icon, Edit2Icon } from 'vue-feather-icons'
import ColorLabel from '../../components/UI/Labels/ColorLabel'
import Spinner from '../../components/UI/Others/Spinner'
export default {
name: 'Pages',
components: {
MobileActionButton,
EmptyPageContent,
DatatableWrapper,
SectionTitle,
MobileHeader,
SwitchInput,
Trash2Icon,
ButtonBase,
ColorLabel,
Edit2Icon,
Spinner,
},
data() {
return {
isLoading: true,
columns: [
{
label: this.$t('page'),
field: 'title',
sortable: true,
},
{
label: this.$t('slug'),
field: 'slug',
sortable: true,
},
{
label: this.$t('status'),
field: 'visibility',
sortable: true,
},
{
label: this.$t('action'),
sortable: false,
},
],
}
},
}
</script>

View File

@@ -1,87 +0,0 @@
<template>
<div>
<div v-if="!isLoading && page" class="card shadow-card">
<FormLabel>
{{ page.data.attributes.title }}
</FormLabel>
<AppInputSwitch
:title="$t('visibility')"
:description="$t('admin_pages.form.visibility_help')"
>
<SwitchInput @input="changeStatus" class="switch" :state="page.data.attributes.visibility" />
</AppInputSwitch>
<AppInputText :title="$t('title')">
<input
@input="$updateText('/admin/pages/' + $route.params.slug, 'title', page.data.attributes.title)"
v-model="page.data.attributes.title"
:placeholder="$t('admin_pages.form.title_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('slug')">
<input v-model="page.data.attributes.slug" type="text" class="focus-border-theme input-dark" disabled />
</AppInputText>
<AppInputText :title="$t('content')" :is-last="true">
<textarea
@input="$updateText('/admin/pages/' + $route.params.slug, 'content', page.data.attributes.content)"
v-model="page.data.attributes.content"
:placeholder="$t('admin_pages.form.content_plac')"
class="focus-border-theme input-dark"
rows="18"
></textarea>
</AppInputText>
</div>
<div id="loader" v-if="isLoading">
<Spinner />
</div>
</div>
</template>
<script>
import AppInputSwitch from '../../../components/Forms/Layouts/AppInputSwitch'
import AppInputText from '../../../components/Forms/Layouts/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import FormLabel from '../../../components/UI/Labels/FormLabel'
import { required } from 'vee-validate/dist/rules'
import SwitchInput from '../../../components/Inputs/SwitchInput'
import MobileHeader from '../../../components/Mobile/MobileHeader'
import SectionTitle from '../../../components/UI/Labels/SectionTitle'
import ButtonBase from '../../../components/UI/Buttons/ButtonBase'
import Spinner from '../../../components/UI/Others/Spinner'
import axios from 'axios'
export default {
name: 'PageEdit',
components: {
AppInputSwitch,
AppInputText,
ValidationProvider,
ValidationObserver,
FormLabel,
SectionTitle,
MobileHeader,
SwitchInput,
ButtonBase,
required,
Spinner,
},
data() {
return {
isLoading: true,
page: undefined,
}
},
methods: {
changeStatus(val) {
this.$updateText('/admin/pages/' + this.$route.params.slug, 'visibility', val)
},
},
created() {
axios.get('/api/admin/pages/' + this.$route.params.slug).then((response) => {
this.page = response.data
this.isLoading = false
})
},
}
</script>

View File

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

View File

@@ -1,167 +0,0 @@
<template>
<PageTab :is-loading="isLoading">
<div v-if="billingInformation" class="card shadow-card">
<FormLabel>
{{ $t('admin_settings.billings.section_company') }}
</FormLabel>
<AppInputText :title="$t('admin_settings.billings.company_name')">
<input
@input="$updateText('/admin/settings', 'billing_name', billingInformation.billing_name)"
v-model="billingInformation.billing_name"
:placeholder="$t('admin_settings.billings.company_name_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('admin_settings.billings.vat')" :is-last="true">
<input
@input="$updateText('/admin/settings', 'billing_vat_number', billingInformation.billing_vat_number)"
v-model="billingInformation.billing_vat_number"
:placeholder="$t('admin_settings.billings.vat_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<div v-if="billingInformation" class="card shadow-card">
<FormLabel>
{{ $t('admin_settings.billings.section_billing') }}
</FormLabel>
<AppInputText :title="$t('admin_settings.billings.country')">
<SelectInput
@input="$updateText('/admin/settings', 'billing_country', billingInformation.billing_country)"
v-model="billingInformation.billing_country"
:default="billingInformation.billing_country"
:options="countries"
:placeholder="$t('admin_settings.billings.country_plac')"
/>
</AppInputText>
<AppInputText :title="$t('billing_address')">
<input
@input="$updateText('/admin/settings', 'billing_address', billingInformation.billing_address)"
v-model="billingInformation.billing_address"
:placeholder="$t('admin_settings.billings.address_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<div class="flex space-x-4">
<AppInputText :title="$t('billing_city')" class="w-full">
<input
@input="$updateText('/admin/settings', 'billing_city', billingInformation.billing_city)"
v-model="billingInformation.billing_city"
:placeholder="$t('admin_settings.billings.city_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('admin_settings.billings.postal_code')" class="w-full">
<input
@input="
$updateText(
'/admin/settings',
'billing_postal_code',
billingInformation.billing_postal_code
)
"
v-model="billingInformation.billing_postal_code"
:placeholder="$t('admin_settings.billings.postal_code_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
<AppInputText :title="$t('admin_settings.billings.state')">
<input
@input="$updateText('/admin/settings', 'billing_state', billingInformation.billing_state)"
v-model="billingInformation.billing_state"
:placeholder="$t('admin_settings.billings.state_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputText :title="$t('admin_settings.billings.phone_number')" :is-last="true">
<input
@input="
$updateText('/admin/settings', 'billing_phone_number', billingInformation.billing_phone_number)
"
v-model="billingInformation.billing_phone_number"
:placeholder="$t('admin_settings.billings.phone_number_plac')"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
</div>
</PageTab>
</template>
<script>
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from '../../../../components/Layout/PageTabGroup'
import SelectInput from '../../../../components/Inputs/SelectInput'
import ImageInput from '../../../../components/Inputs/ImageInput'
import FormLabel from '../../../../components/UI/Labels/FormLabel'
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
import PageTab from '../../../../components/Layout/PageTab'
import InfoBox from '../../../../components/UI/Others/InfoBox'
import { required } from 'vee-validate/dist/rules'
import axios from 'axios'
import { mapGetters } from 'vuex'
export default {
name: 'AppAppearance',
components: {
ValidationObserver,
ValidationProvider,
AppInputText,
PageTabGroup,
SelectInput,
ImageInput,
ButtonBase,
FormLabel,
required,
PageTab,
InfoBox,
},
computed: {
...mapGetters(['countries']),
},
data() {
return {
isLoading: true,
billingInformation: undefined,
}
},
mounted() {
axios
.get('/api/admin/settings', {
params: {
column: 'billing_phone_number|billing_postal_code|billing_vat_number|billing_address|billing_country|billing_state|billing_city|billing_name',
},
})
.then((response) => {
this.isLoading = false
this.billingInformation = {
billing_phone_number: response.data.billing_phone_number,
billing_postal_code: response.data.billing_postal_code,
billing_vat_number: response.data.billing_vat_number,
billing_address: response.data.billing_address,
billing_country: response.data.billing_country,
billing_state: response.data.billing_state,
billing_city: response.data.billing_city,
billing_name: response.data.billing_name,
}
})
},
}
</script>

View File

@@ -1,664 +0,0 @@
<template>
<PageTab>
<!--Global payment settings-->
<div class="card shadow-card">
<FormLabel icon="dollar">
{{ $t('subscription_payments') }}
</FormLabel>
<AppInputSwitch
:title="$t('allow_subscription_payments')"
:description="$t('allow_subscription_payments_description')"
:is-last="true"
>
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_payments', allowedPayments)"
v-model="allowedPayments"
:state="allowedPayments"
/>
</AppInputSwitch>
</div>
<!--Metered settings-->
<div v-if="config.subscriptionType === 'metered' && allowedPayments" class="card shadow-card">
<FormLabel icon="bar-chart">
{{ $t('metered_billing_settings') }}
</FormLabel>
<AppInputSwitch
:title="$t('allow_registration_bonus')"
:description="$t('allow_registration_bonus_description')"
>
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_registration_bonus', allowedRegistrationBonus)"
v-model="allowedRegistrationBonus"
:state="allowedRegistrationBonus"
/>
</AppInputSwitch>
<AppInputText
v-if="allowedRegistrationBonus"
:title="$t('registration_bonus_amount')"
:description="$t('registration_bonus_amount_description')"
>
<input
@input="$updateText('/admin/settings', 'registration_bonus_amount', registrationBonusAmount)"
v-model="registrationBonusAmount"
:placeholder="$t('registration_bonus_amount_')"
type="number"
class="focus-border-theme input-dark"
/>
</AppInputText>
<AppInputButton
:title="$t('metered_plan')"
:description="$t('metered_plan_description')"
:is-last="true"
>
<router-link
v-if="config.isCreatedMeteredPlan"
:to="{
name: 'PlanMeteredSettings',
params: { id: config.meteredPlanId },
}"
>
<ButtonBase v-if="config.isCreatedMeteredPlan" class="w-full sm:w-auto" button-style="theme">
{{ $t('plan_details') }}
</ButtonBase>
</router-link>
<router-link v-if="!config.isCreatedMeteredPlan" :to="{ name: 'CreateMeteredPlan' }">
<ButtonBase class="w-full sm:w-auto" button-style="theme-solid">
{{ $t('create_plan') }}
</ButtonBase>
</router-link>
</AppInputButton>
</div>
<!--Stripe method configuration-->
<div v-if="allowedPayments" class="card shadow-card">
<img :src="$getPaymentLogo('stripe')" alt="Stripe" class="mb-8 h-8" />
<AppInputSwitch
:title="$t('Allow Stripe Service')"
:description="$t('allow_pay_by_card')"
:is-last="!stripe.allowedService"
>
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_stripe', stripe.allowedService)"
v-model="stripe.allowedService"
:state="stripe.allowedService"
/>
</AppInputSwitch>
<!--Stripe credentials are set up-->
<div v-if="stripe.allowedService">
<AppInputText
:title="$t('webhook_url')"
:description="$t('copy_webhook_note')"
>
<CopyInput size="small" :str="getWebhookEndpoint('stripe')" />
</AppInputText>
<div v-if="stripe.isConfigured">
<AppInputText
@input="$updateText('/admin/settings', 'stripe_payment_description', stripe.paymentDescription)"
:title="$t('payment_description')"
:description="$t('payment_description_note')"
>
<textarea
rows="2"
@input="
$updateText(
'/admin/settings',
'stripe_payment_description',
stripe.paymentDescription,
true
)
"
v-model="stripe.paymentDescription"
:placeholder="
$t('additional_info_about_payment_method_')
"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<div
@click="stripe.isVisibleCredentialsForm = !stripe.isVisibleCredentialsForm"
class="flex cursor-pointer items-center"
:class="{ 'mb-4': stripe.isVisibleCredentialsForm }"
>
<edit2-icon size="14" class="vue-feather text-theme mr-2.5" />
<b class="text-sm">{{ $t('update_your_credentials') }}</b>
</div>
</div>
<!--Set up Stripe credentials-->
<ValidationObserver
v-if="!stripe.isConfigured || stripe.isVisibleCredentialsForm"
@submit.prevent="storeCredentials('stripe')"
ref="stripe"
v-slot="{ invalid }"
tag="form"
class="rounded-xl p-5 shadow-lg"
>
<FormLabel v-if="!stripe.isConfigured" icon="shield">
{{ $t('configure_your_credentials') }}
</FormLabel>
<ValidationProvider
tag="div"
mode="passive"
name="Publishable Key"
rules="required"
v-slot="{ errors }"
>
<AppInputText :title="$t('admin_settings.payments.stripe_pub_key')" :error="errors[0]">
<input
v-model="stripe.credentials.key"
:placeholder="$t('admin_settings.payments.stripe_pub_key_plac')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Secret Key" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.payments.stripe_sec_key')" :error="errors[0]">
<input
v-model="stripe.credentials.secret"
:placeholder="$t('admin_settings.payments.stripe_sec_key_plac')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider
tag="div"
mode="passive"
name="Webhook Secret"
rules="required"
v-slot="{ errors }"
>
<AppInputText :title="$t('Webhook Secret')" :error="errors[0]">
<input
v-model="stripe.credentials.webhook"
:placeholder="$t('paste_webhook_secret')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ButtonBase
:disabled="isLoading"
:loading="isLoading"
button-style="theme"
type="submit"
class="w-full"
>
{{ $t('store_credentials') }}
</ButtonBase>
</ValidationObserver>
</div>
</div>
<!--Paystack method configuration-->
<div v-if="allowedPayments" class="card shadow-card">
<img :src="$getPaymentLogo('paystack')" alt="Paystack" class="mb-8 h-7" />
<AppInputSwitch
:title="$t('Allow Paystack Service')"
:description="$t('allow_pay_by_card')"
:is-last="!paystack.allowedService"
>
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_paystack', paystack.allowedService)"
v-model="paystack.allowedService"
:state="paystack.allowedService"
/>
</AppInputSwitch>
<!--Paystack credentials are set up-->
<div v-if="paystack.allowedService">
<AppInputText
:title="$t('webhook_url')"
:description="$t('copy_webhook_note')"
>
<CopyInput size="small" :str="getWebhookEndpoint('paystack')" />
</AppInputText>
<div v-if="paystack.isConfigured">
<AppInputText
@input="
$updateText('/admin/settings', 'paystack_payment_description', paystack.paymentDescription)
"
:title="$t('payment_description')"
:description="$t('payment_description_note')"
>
<textarea
rows="2"
@input="
$updateText(
'/admin/settings',
'paystack_payment_description',
paystack.paymentDescription,
true
)
"
v-model="paystack.paymentDescription"
:placeholder="
$t('additional_info_about_payment_method_')
"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<div
@click="paystack.isVisibleCredentialsForm = !paystack.isVisibleCredentialsForm"
class="flex cursor-pointer items-center"
:class="{ 'mb-4': paystack.isVisibleCredentialsForm }"
>
<edit2-icon size="14" class="vue-feather text-theme mr-2.5" />
<b class="text-sm">{{ $t('update_your_credentials') }}</b>
</div>
</div>
<!--Set up Paystack credentials-->
<ValidationObserver
v-if="!paystack.isConfigured || paystack.isVisibleCredentialsForm"
@submit.prevent="storeCredentials('paystack')"
ref="paystack"
v-slot="{ invalid }"
tag="form"
class="rounded-xl p-5 shadow-lg"
>
<FormLabel v-if="!paystack.isConfigured" icon="shield">
{{ $t('configure_your_credentials') }}
</FormLabel>
<ValidationProvider
tag="div"
mode="passive"
name="Publishable Key"
rules="required"
v-slot="{ errors }"
>
<AppInputText :title="$t('admin_settings.payments.stripe_pub_key')" :error="errors[0]">
<input
v-model="paystack.credentials.key"
:placeholder="$t('admin_settings.payments.stripe_pub_key_plac')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Secret Key" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.payments.stripe_sec_key')" :error="errors[0]">
<input
v-model="paystack.credentials.secret"
:placeholder="$t('admin_settings.payments.stripe_sec_key_plac')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ButtonBase
:disabled="isLoading"
:loading="isLoading"
button-style="theme"
type="submit"
class="w-full"
>
{{ $t('store_credentials') }}
</ButtonBase>
</ValidationObserver>
</div>
</div>
<!--PayPal method configuration-->
<div v-if="allowedPayments" class="card shadow-card">
<img :src="$getPaymentLogo('paypal')" alt="PayPal" class="mb-8 h-8" />
<AppInputSwitch
:title="$t('Allow PayPal Service')"
:description="$t('allow_pay_by_card')"
:is-last="!paypal.allowedService"
>
<SwitchInput
@input="$updateText('/admin/settings', 'allowed_paypal', paypal.allowedService)"
v-model="paypal.allowedService"
:state="paypal.allowedService"
/>
</AppInputSwitch>
<!--Stripe credentials are set up-->
<div v-if="paypal.allowedService">
<AppInputText
:title="$t('webhook_url')"
:description="$t('copy_webhook_note')"
>
<CopyInput size="small" :str="getWebhookEndpoint('paypal')" />
</AppInputText>
<div v-if="paypal.isConfigured">
<AppInputSwitch :title="$t('Live Mode')" :description="$t('Toggle between live and sandbox mode')">
<SwitchInput
@input="$updateText('/admin/settings', 'paypal_live', config.isPayPalLive)"
v-model="config.isPayPalLive"
:state="config.isPayPalLive"
/>
</AppInputSwitch>
<AppInputText
@input="$updateText('/admin/settings', 'paypal_payment_description', paypal.paymentDescription)"
:title="$t('payment_description')"
:description="$t('payment_description_note')"
>
<textarea
rows="2"
@input="
$updateText(
'/admin/settings',
'paypal_payment_description',
paypal.paymentDescription,
true
)
"
v-model="paypal.paymentDescription"
:placeholder="
$t('additional_info_about_payment_method_')
"
type="text"
class="focus-border-theme input-dark"
/>
</AppInputText>
<div
@click="paypal.isVisibleCredentialsForm = !paypal.isVisibleCredentialsForm"
class="flex cursor-pointer items-center"
:class="{ 'mb-4': paypal.isVisibleCredentialsForm }"
>
<edit2-icon size="14" class="vue-feather text-theme mr-2.5" />
<b class="text-sm">{{ $t('update_your_credentials') }}</b>
</div>
</div>
<!--Set up Paypal credentials-->
<ValidationObserver
v-if="!paypal.isConfigured || paypal.isVisibleCredentialsForm"
@submit.prevent="storeCredentials('paypal')"
ref="paypal"
v-slot="{ invalid }"
tag="form"
class="rounded-xl p-5 shadow-lg"
>
<FormLabel v-if="!paypal.isConfigured" icon="shield">
{{ $t('configure_your_credentials') }}
</FormLabel>
<ValidationProvider>
<AppInputSwitch v-if="! paypal.isConfigured" :title="$t('Live Mode')" :description="$t('Toggle between live and sandbox mode')">
<SwitchInput v-model="paypal.credentials.live" :state="paypal.credentials.live" />
</AppInputSwitch>
</ValidationProvider>
<ValidationProvider
tag="div"
mode="passive"
name="Publishable Key"
rules="required"
v-slot="{ errors }"
>
<AppInputText :title="$t('admin_settings.payments.stripe_pub_key')" :error="errors[0]">
<input
v-model="paypal.credentials.key"
:placeholder="$t('admin_settings.payments.stripe_pub_key_plac')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Secret Key" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('admin_settings.payments.stripe_sec_key')" :error="errors[0]">
<input
v-model="paypal.credentials.secret"
:placeholder="$t('admin_settings.payments.stripe_sec_key_plac')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ValidationProvider tag="div" mode="passive" name="Webhook ID" rules="required" v-slot="{ errors }">
<AppInputText :title="$t('Webhook ID')" :error="errors[0]">
<input
v-model="paypal.credentials.webhook"
:placeholder="$t('Paste your webhook id')"
type="text"
:class="{ '!border-rose-600': errors[0] }"
class="focus-border-theme input-dark"
/>
</AppInputText>
</ValidationProvider>
<ButtonBase
:disabled="isLoading"
:loading="isLoading"
button-style="theme"
type="submit"
class="w-full"
>
{{ $t('store_credentials') }}
</ButtonBase>
</ValidationObserver>
</div>
</div>
</PageTab>
</template>
<script>
import { Edit2Icon, Trash2Icon } from 'vue-feather-icons'
import AppInputButton from '../../../../components/Forms/Layouts/AppInputButton'
import DatatableWrapper from '../../../../components/UI/Table/DatatableWrapper'
import { ValidationProvider, ValidationObserver } from 'vee-validate/dist/vee-validate.full'
import PageTabGroup from '../../../../components/Layout/PageTabGroup'
import SelectInput from '../../../../components/Inputs/SelectInput'
import SwitchInput from '../../../../components/Inputs/SwitchInput'
import ImageInput from '../../../../components/Inputs/ImageInput'
import AppInputSwitch from '../../../../components/Forms/Layouts/AppInputSwitch'
import FormLabel from '../../../../components/UI/Labels/FormLabel'
import ButtonBase from '../../../../components/UI/Buttons/ButtonBase'
import CopyInput from '../../../../components/Inputs/CopyInput'
import AppInputText from '../../../../components/Forms/Layouts/AppInputText'
import PageTab from '../../../../components/Layout/PageTab'
import InfoBox from '../../../../components/UI/Others/InfoBox'
import { required } from 'vee-validate/dist/rules'
import { events } from '../../../../bus'
import { mapGetters } from 'vuex'
import axios from 'axios'
export default {
name: 'AppPayments',
components: {
AppInputButton,
DatatableWrapper,
ValidationObserver,
ValidationProvider,
AppInputSwitch,
AppInputText,
PageTabGroup,
SwitchInput,
SelectInput,
ImageInput,
ButtonBase,
CopyInput,
FormLabel,
Trash2Icon,
Edit2Icon,
required,
PageTab,
InfoBox,
},
computed: {
...mapGetters(['config']),
},
data() {
return {
allowedRegistrationBonus: true,
registrationBonusAmount: undefined,
allowedPayments: false,
isLoading: false,
isError: false,
errorMessage: '',
stripe: {
allowedService: true,
isConfigured: false,
isVisibleCredentialsForm: false,
paymentDescription: undefined,
credentials: {
key: undefined,
secret: undefined,
webhook: undefined,
},
},
paystack: {
allowedService: true,
isConfigured: false,
isVisibleCredentialsForm: false,
paymentDescription: undefined,
credentials: {
key: undefined,
secret: undefined,
},
},
paypal: {
allowedService: true,
isConfigured: false,
isVisibleCredentialsForm: false,
paymentDescription: undefined,
credentials: {
key: undefined,
secret: undefined,
webhook: undefined,
live: undefined,
},
},
columns: [
{
label: this.$t('name'),
field: 'name',
sortable: true,
},
{
label: this.$t('currency'),
field: 'currency',
sortable: true,
},
{
label: this.$t('interval'),
field: 'interval',
sortable: true,
},
{
label: this.$t('subscribers'),
sortable: false,
},
{
label: this.$t('action'),
sortable: false,
},
],
}
},
methods: {
async storeCredentials(service) {
// Validate fields
const isValid = await this.$refs[service].validate()
if (!isValid) return
// Start loading
this.isLoading = true
// Send request to get verify account
axios
.post('/api/admin/settings/payment-service', {
service: service,
key: this[service].credentials.key,
secret: this[service].credentials.secret,
webhook: this[service].credentials.webhook || undefined,
live: this[service].credentials.live,
})
.then(() => {
// Update Credentials
let commitKey = {
stripe: 'SET_STRIPE_CREDENTIALS',
paystack: 'SET_PAYSTACK_CREDENTIALS',
paypal: 'SET_PAYPAL_CREDENTIALS',
}[service]
// Commit credentials
this.$store.commit(commitKey, this[service].credentials)
this[service].allowedService = true
this[service].isConfigured = true
this[service].isVisibleCredentialsForm = false
// Show toaster
events.$emit('toaster', {
type: 'success',
message: this.$t('toaster.credentials_set', {
service: service,
}),
})
})
.catch((error) => {
if (error.response.status === 500) {
this.isError = true
this.errorMessage = error.response.data.message
}
})
.finally(() => (this.isLoading = false))
},
getWebhookEndpoint(service) {
return `${this.config.host}/api/subscriptions/${service}/webhooks`
},
},
created() {
// Set payment description
this.stripe.paymentDescription = this.config.stripe_payment_description
this.paystack.paymentDescription = this.config.paystack_payment_description
this.paypal.paymentDescription = this.config.paypal_payment_description
// Set if service is allowed
this.stripe.allowedService = this.config.isStripe
this.paystack.allowedService = this.config.isPaystack
this.paypal.allowedService = this.config.isPayPal
if (this.config.stripe_public_key) this.stripe.isConfigured = true
if (this.config.paystack_public_key) this.paystack.isConfigured = true
if (this.config.paypal_client_id) this.paypal.isConfigured = true
this.allowedPayments = this.config.allowed_payments
// Set metered
this.allowedRegistrationBonus = this.config.allowed_registration_bonus
this.registrationBonusAmount = this.config.registration_bonus_amount
},
destroyed() {
events.$off('action:confirmed')
},
}
</script>

Some files were not shown because too many files have changed in this diff Show More