mirror of
https://github.com/VueFileManager/vuefilemanager.git
synced 2026-04-05 18:23:48 +00:00
Subscription UI refactoring
This commit is contained in:
@@ -13,19 +13,19 @@
|
||||
"/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~1bec6fe4.js": "/chunks/admin~chunks/files~chunks/my-shared-items~chunks/platform~chunks/recent-uploads~chunks/shared~1bec6fe4.js?id=510e6c1b1017a73a40a6",
|
||||
"/chunks/admin~chunks/platform.js": "/chunks/admin~chunks/platform.js?id=a8e78a3b0ce4dba71a6e",
|
||||
"/chunks/admin~chunks/platform~chunks/settings.js": "/chunks/admin~chunks/platform~chunks/settings.js?id=3121d1c093031fba08ec",
|
||||
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js?id=d99d78cf99eec0ed3a81",
|
||||
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.js?id=9e90103c70ecef1771d1",
|
||||
"/chunks/admin~chunks/platform~chunks/shared.js": "/chunks/admin~chunks/platform~chunks/shared.js?id=afeba4ebd13af7e995be",
|
||||
"/chunks/app-appearance.js": "/chunks/app-appearance.js?id=5e7505fe2f64858109fa",
|
||||
"/chunks/app-appearance.js": "/chunks/app-appearance.js?id=e4bd3e51dbb74e894be3",
|
||||
"/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~5acee76d.js": "/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~5acee76d.js?id=9dd101339ae6d85c6175",
|
||||
"/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~fa9d08c1.js": "/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~fa9d08c1.js?id=741acf06489eac45774e",
|
||||
"/chunks/app-billings.js": "/chunks/app-billings.js?id=6f363efa7ba2f45c9d24",
|
||||
"/chunks/app-email.js": "/chunks/app-email.js?id=01efc9cc57d65ec7039a",
|
||||
"/chunks/app-index.js": "/chunks/app-index.js?id=31141d91afa8ebc381f4",
|
||||
"/chunks/app-language.js": "/chunks/app-language.js?id=076c03dcb960a4edb672",
|
||||
"/chunks/app-index.js": "/chunks/app-index.js?id=fee803b8bdc0032fb1dd",
|
||||
"/chunks/app-language.js": "/chunks/app-language.js?id=3787049bbcb0b5a4e140",
|
||||
"/chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-items~chunks/page~7dbb6a42.js": "/chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-items~chunks/page~7dbb6a42.js?id=3cbb19b80e635b3524e5",
|
||||
"/chunks/app-language~chunks/dashboard~chunks/invoices~chunks/page-edit~chunks/pages~chunks/plan-creat~d6690165.js": "/chunks/app-language~chunks/dashboard~chunks/invoices~chunks/page-edit~chunks/pages~chunks/plan-creat~d6690165.js?id=2f03fbbe27b922063d16",
|
||||
"/chunks/app-others.js": "/chunks/app-others.js?id=d48001de73029fe3a5ce",
|
||||
"/chunks/app-payments.js": "/chunks/app-payments.js?id=f7ba69541b8d8d10926f",
|
||||
"/chunks/app-others.js": "/chunks/app-others.js?id=34d85e85534feb7fc649",
|
||||
"/chunks/app-payments.js": "/chunks/app-payments.js?id=0b851be1fcc6e1374007",
|
||||
"/chunks/app-settings.js": "/chunks/app-settings.js?id=2a96295eef59bd6c8921",
|
||||
"/chunks/app-setup.js": "/chunks/app-setup.js?id=64f4753ffef5f731dc96",
|
||||
"/chunks/app-setup~chunks/billings-detail~chunks/create-new-password~chunks/database~chunks/email-veri~5d0bfc57.js": "/chunks/app-setup~chunks/billings-detail~chunks/create-new-password~chunks/database~chunks/email-veri~5d0bfc57.js?id=107a78323eef0c1d4c81",
|
||||
@@ -52,29 +52,29 @@
|
||||
"/chunks/invoices.js": "/chunks/invoices.js?id=2032e4f8e4f5ed58b8b4",
|
||||
"/chunks/my-shared-items.js": "/chunks/my-shared-items.js?id=4eb4c11f0e574303af7f",
|
||||
"/chunks/not-found.js": "/chunks/not-found.js?id=034287ee0ecb036320d3",
|
||||
"/chunks/page-edit.js": "/chunks/page-edit.js?id=4bc9ddd14ff472d455e6",
|
||||
"/chunks/page-edit.js": "/chunks/page-edit.js?id=1b8c9a0eee76fcb52f12",
|
||||
"/chunks/pages.js": "/chunks/pages.js?id=8c6772e3224fadca2bf2",
|
||||
"/chunks/plan.js": "/chunks/plan.js?id=f62a5bd64fb706b2f0e2",
|
||||
"/chunks/plan-create/fixed.js": "/chunks/plan-create/fixed.js?id=84617542ab68f42c2ab8",
|
||||
"/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=f865f6dafff99c0a356b",
|
||||
"/chunks/plan-create/metered.js": "/chunks/plan-create/metered.js?id=4aeec95a76351205dab0",
|
||||
"/chunks/plan-delete.js": "/chunks/plan-delete.js?id=c97fde9a7b6395a7dd45",
|
||||
"/chunks/plan-settings.js": "/chunks/plan-settings.js?id=f04752a32b8bf397599d",
|
||||
"/chunks/plan-settings.js": "/chunks/plan-settings.js?id=8a959e196d628d0efc4b",
|
||||
"/chunks/plan-subscribers.js": "/chunks/plan-subscribers.js?id=7eff560fea52770a642f",
|
||||
"/chunks/plans.js": "/chunks/plans.js?id=854c232c29d88c5b541a",
|
||||
"/chunks/platform.js": "/chunks/platform.js?id=56c6a261168c708d8284",
|
||||
"/chunks/platform.js": "/chunks/platform.js?id=079849949b8872ce74d0",
|
||||
"/chunks/platform~chunks/shared.js": "/chunks/platform~chunks/shared.js?id=06f4d5fe4dc931026a02",
|
||||
"/chunks/platform~chunks/shared~chunks/shared-with-me~chunks/team-folders.js": "/chunks/platform~chunks/shared~chunks/shared-with-me~chunks/team-folders.js?id=7d983dfdc91de607d737",
|
||||
"/chunks/profile.js": "/chunks/profile.js?id=8688d9c7ff850e6989e6",
|
||||
"/chunks/profile~chunks/settings-password.js": "/chunks/profile~chunks/settings-password.js?id=dfa4128d68360d5e1b3b",
|
||||
"/chunks/purchase-code.js": "/chunks/purchase-code.js?id=19ed542c413992bb052e",
|
||||
"/chunks/recent-uploads.js": "/chunks/recent-uploads.js?id=2716127ee2f945a90c0e",
|
||||
"/chunks/settings.js": "/chunks/settings.js?id=3feadf596e576e256454",
|
||||
"/chunks/settings-create-payment-methods.js": "/chunks/settings-create-payment-methods.js?id=44f5a03f79d2ebd29a79",
|
||||
"/chunks/settings-password.js": "/chunks/settings-password.js?id=049c3383e310392f966d",
|
||||
"/chunks/settings.js": "/chunks/settings.js?id=d40f8c73d3c8baa29600",
|
||||
"/chunks/settings-create-payment-methods.js": "/chunks/settings-create-payment-methods.js?id=92b82ac5385e90f4bf77",
|
||||
"/chunks/settings-password.js": "/chunks/settings-password.js?id=5ff6ab3f66a1ea9b41f3",
|
||||
"/chunks/settings-payment-methods.js": "/chunks/settings-payment-methods.js?id=5a0971b7e97d67546f9d",
|
||||
"/chunks/settings-storage.js": "/chunks/settings-storage.js?id=76b45c336e8e12b23e81",
|
||||
"/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.js": "/chunks/settings-storage~chunks/settings-subscription~chunks/user-storage~chunks/user-subscription.js?id=3e16c9ff52b4447b2c90",
|
||||
"/chunks/settings-subscription.js": "/chunks/settings-subscription.js?id=d0f15dad141528e80d8f",
|
||||
"/chunks/settings-subscription.js": "/chunks/settings-subscription.js?id=2d87aa10c9b10a8c2379",
|
||||
"/chunks/settings~chunks/settings-password.js": "/chunks/settings~chunks/settings-password.js?id=aafc9cd6aa47b01bc25a",
|
||||
"/chunks/setup-wizard.js": "/chunks/setup-wizard.js?id=651d5accf401908724c5",
|
||||
"/chunks/shared.js": "/chunks/shared.js?id=6230d050545cd1bd9b87",
|
||||
@@ -204,5 +204,224 @@
|
||||
"/chunks/settings.26fb9ae87225d86923f8.hot-update.js": "/chunks/settings.26fb9ae87225d86923f8.hot-update.js",
|
||||
"/chunks/settings.6da9a15f8348f4457bcf.hot-update.js": "/chunks/settings.6da9a15f8348f4457bcf.hot-update.js",
|
||||
"/chunks/settings.72b5f647cc14fc3c96d7.hot-update.js": "/chunks/settings.72b5f647cc14fc3c96d7.hot-update.js",
|
||||
"/chunks/settings.21d7007ef6c9337390e5.hot-update.js": "/chunks/settings.21d7007ef6c9337390e5.hot-update.js"
|
||||
"/chunks/settings.21d7007ef6c9337390e5.hot-update.js": "/chunks/settings.21d7007ef6c9337390e5.hot-update.js",
|
||||
"/chunks/app-appearance.7cf496fee8a83761062f.hot-update.js": "/chunks/app-appearance.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/app-index.7cf496fee8a83761062f.hot-update.js": "/chunks/app-index.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/app-language.7cf496fee8a83761062f.hot-update.js": "/chunks/app-language.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/app-others.7cf496fee8a83761062f.hot-update.js": "/chunks/app-others.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/app-payments.7cf496fee8a83761062f.hot-update.js": "/chunks/app-payments.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/page-edit.7cf496fee8a83761062f.hot-update.js": "/chunks/page-edit.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/plan-create/metered.7cf496fee8a83761062f.hot-update.js": "/chunks/plan-create/metered.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/plan-settings.7cf496fee8a83761062f.hot-update.js": "/chunks/plan-settings.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/platform.7cf496fee8a83761062f.hot-update.js": "/chunks/platform.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/settings-password.7cf496fee8a83761062f.hot-update.js": "/chunks/settings-password.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/settings-subscription.7cf496fee8a83761062f.hot-update.js": "/chunks/settings-subscription.7cf496fee8a83761062f.hot-update.js",
|
||||
"/chunks/app-appearance.1536c804c943e0ab59c7.hot-update.js": "/chunks/app-appearance.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/app-index.1536c804c943e0ab59c7.hot-update.js": "/chunks/app-index.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/app-language.1536c804c943e0ab59c7.hot-update.js": "/chunks/app-language.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/app-others.1536c804c943e0ab59c7.hot-update.js": "/chunks/app-others.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/app-payments.1536c804c943e0ab59c7.hot-update.js": "/chunks/app-payments.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/page-edit.1536c804c943e0ab59c7.hot-update.js": "/chunks/page-edit.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/plan-create/metered.1536c804c943e0ab59c7.hot-update.js": "/chunks/plan-create/metered.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/plan-settings.1536c804c943e0ab59c7.hot-update.js": "/chunks/plan-settings.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/platform.1536c804c943e0ab59c7.hot-update.js": "/chunks/platform.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/settings-password.1536c804c943e0ab59c7.hot-update.js": "/chunks/settings-password.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/settings-subscription.1536c804c943e0ab59c7.hot-update.js": "/chunks/settings-subscription.1536c804c943e0ab59c7.hot-update.js",
|
||||
"/chunks/app-appearance.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/app-appearance.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/app-index.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/app-index.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/app-language.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/app-language.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/app-others.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/app-others.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/app-payments.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/app-payments.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/page-edit.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/page-edit.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/plan-create/metered.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/plan-create/metered.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/plan-settings.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/plan-settings.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/platform.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/platform.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/settings-password.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/settings-password.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/settings-subscription.d239d34a32c5a4c40e3a.hot-update.js": "/chunks/settings-subscription.d239d34a32c5a4c40e3a.hot-update.js",
|
||||
"/chunks/app-appearance.0226e139d7160b2507e4.hot-update.js": "/chunks/app-appearance.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/app-index.0226e139d7160b2507e4.hot-update.js": "/chunks/app-index.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/app-language.0226e139d7160b2507e4.hot-update.js": "/chunks/app-language.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/app-others.0226e139d7160b2507e4.hot-update.js": "/chunks/app-others.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/app-payments.0226e139d7160b2507e4.hot-update.js": "/chunks/app-payments.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/page-edit.0226e139d7160b2507e4.hot-update.js": "/chunks/page-edit.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/plan-create/metered.0226e139d7160b2507e4.hot-update.js": "/chunks/plan-create/metered.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/plan-settings.0226e139d7160b2507e4.hot-update.js": "/chunks/plan-settings.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/platform.0226e139d7160b2507e4.hot-update.js": "/chunks/platform.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/settings-password.0226e139d7160b2507e4.hot-update.js": "/chunks/settings-password.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/settings-subscription.0226e139d7160b2507e4.hot-update.js": "/chunks/settings-subscription.0226e139d7160b2507e4.hot-update.js",
|
||||
"/chunks/app-appearance.d3a32202061b43267178.hot-update.js": "/chunks/app-appearance.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/app-index.d3a32202061b43267178.hot-update.js": "/chunks/app-index.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/app-language.d3a32202061b43267178.hot-update.js": "/chunks/app-language.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/app-others.d3a32202061b43267178.hot-update.js": "/chunks/app-others.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/app-payments.d3a32202061b43267178.hot-update.js": "/chunks/app-payments.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/page-edit.d3a32202061b43267178.hot-update.js": "/chunks/page-edit.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/plan-create/metered.d3a32202061b43267178.hot-update.js": "/chunks/plan-create/metered.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/plan-settings.d3a32202061b43267178.hot-update.js": "/chunks/plan-settings.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/platform.d3a32202061b43267178.hot-update.js": "/chunks/platform.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/settings-password.d3a32202061b43267178.hot-update.js": "/chunks/settings-password.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/settings-subscription.d3a32202061b43267178.hot-update.js": "/chunks/settings-subscription.d3a32202061b43267178.hot-update.js",
|
||||
"/chunks/settings-subscription.9264decb141164440877.hot-update.js": "/chunks/settings-subscription.9264decb141164440877.hot-update.js",
|
||||
"/chunks/settings-subscription.78271456cf0dca580c1d.hot-update.js": "/chunks/settings-subscription.78271456cf0dca580c1d.hot-update.js",
|
||||
"/chunks/settings.3ae7b6652e9c2be66abe.hot-update.js": "/chunks/settings.3ae7b6652e9c2be66abe.hot-update.js",
|
||||
"/chunks/settings.ffc77f6ec277ec32f78e.hot-update.js": "/chunks/settings.ffc77f6ec277ec32f78e.hot-update.js",
|
||||
"/chunks/settings.d111185e4d2723b0760c.hot-update.js": "/chunks/settings.d111185e4d2723b0760c.hot-update.js",
|
||||
"/chunks/settings.9f82ae84f4887451a97f.hot-update.js": "/chunks/settings.9f82ae84f4887451a97f.hot-update.js",
|
||||
"/chunks/settings.c9333942718332eec760.hot-update.js": "/chunks/settings.c9333942718332eec760.hot-update.js",
|
||||
"/chunks/settings.eeae2c5e05db896f2f83.hot-update.js": "/chunks/settings.eeae2c5e05db896f2f83.hot-update.js",
|
||||
"/chunks/settings.56c61d09bb366b7a0333.hot-update.js": "/chunks/settings.56c61d09bb366b7a0333.hot-update.js",
|
||||
"/chunks/settings.d8b83eff1cead1d459af.hot-update.js": "/chunks/settings.d8b83eff1cead1d459af.hot-update.js",
|
||||
"/chunks/settings.b18a6a6f2b0727700a3a.hot-update.js": "/chunks/settings.b18a6a6f2b0727700a3a.hot-update.js",
|
||||
"/chunks/settings.1049ac020a0503278ad9.hot-update.js": "/chunks/settings.1049ac020a0503278ad9.hot-update.js",
|
||||
"/chunks/settings.975c25aa927fd3a33664.hot-update.js": "/chunks/settings.975c25aa927fd3a33664.hot-update.js",
|
||||
"/chunks/settings.b678632674f1da8b8e74.hot-update.js": "/chunks/settings.b678632674f1da8b8e74.hot-update.js",
|
||||
"/chunks/settings.48a44e4cd8c1e8d0495c.hot-update.js": "/chunks/settings.48a44e4cd8c1e8d0495c.hot-update.js",
|
||||
"/chunks/settings.5317a595e48dafd4cb62.hot-update.js": "/chunks/settings.5317a595e48dafd4cb62.hot-update.js",
|
||||
"/chunks/settings.60fd77fbbaaa18372559.hot-update.js": "/chunks/settings.60fd77fbbaaa18372559.hot-update.js",
|
||||
"/chunks/settings.9bb865ea0a5999931b59.hot-update.js": "/chunks/settings.9bb865ea0a5999931b59.hot-update.js",
|
||||
"/chunks/settings.bf2c26418608c3e0c1d8.hot-update.js": "/chunks/settings.bf2c26418608c3e0c1d8.hot-update.js",
|
||||
"/chunks/settings.6ed2bc4add224286baef.hot-update.js": "/chunks/settings.6ed2bc4add224286baef.hot-update.js",
|
||||
"/chunks/settings.24e218796955bcd2163d.hot-update.js": "/chunks/settings.24e218796955bcd2163d.hot-update.js",
|
||||
"/chunks/settings.c77458bd996e4744211e.hot-update.js": "/chunks/settings.c77458bd996e4744211e.hot-update.js",
|
||||
"/chunks/settings.0122f387d1bf035c4cfc.hot-update.js": "/chunks/settings.0122f387d1bf035c4cfc.hot-update.js",
|
||||
"/chunks/settings.560813bd5713a5c9c43d.hot-update.js": "/chunks/settings.560813bd5713a5c9c43d.hot-update.js",
|
||||
"/chunks/settings.f4da0410509cf30d157b.hot-update.js": "/chunks/settings.f4da0410509cf30d157b.hot-update.js",
|
||||
"/chunks/settings.bff3ea2f44a270e517ab.hot-update.js": "/chunks/settings.bff3ea2f44a270e517ab.hot-update.js",
|
||||
"/chunks/settings.29a18f21671d48739ef8.hot-update.js": "/chunks/settings.29a18f21671d48739ef8.hot-update.js",
|
||||
"/chunks/settings.0555c4f8c4cea575dfc1.hot-update.js": "/chunks/settings.0555c4f8c4cea575dfc1.hot-update.js",
|
||||
"/chunks/settings.742bfde63d49c70506e0.hot-update.js": "/chunks/settings.742bfde63d49c70506e0.hot-update.js",
|
||||
"/chunks/settings.9cc5bb664d361a1b28dc.hot-update.js": "/chunks/settings.9cc5bb664d361a1b28dc.hot-update.js",
|
||||
"/chunks/settings.985cd6723c91011e5eff.hot-update.js": "/chunks/settings.985cd6723c91011e5eff.hot-update.js",
|
||||
"/chunks/settings-subscription.21815a5a0af024010061.hot-update.js": "/chunks/settings-subscription.21815a5a0af024010061.hot-update.js",
|
||||
"/chunks/settings-subscription.c64f7ab58467ac1b807f.hot-update.js": "/chunks/settings-subscription.c64f7ab58467ac1b807f.hot-update.js",
|
||||
"/chunks/settings-subscription.af28420657fdc3df179a.hot-update.js": "/chunks/settings-subscription.af28420657fdc3df179a.hot-update.js",
|
||||
"/chunks/settings-subscription.813a0b9acb187c0ff473.hot-update.js": "/chunks/settings-subscription.813a0b9acb187c0ff473.hot-update.js",
|
||||
"/chunks/settings-subscription.8d13a248f4da32d65eec.hot-update.js": "/chunks/settings-subscription.8d13a248f4da32d65eec.hot-update.js",
|
||||
"/chunks/settings-subscription.cd5f4fde34e88e766724.hot-update.js": "/chunks/settings-subscription.cd5f4fde34e88e766724.hot-update.js",
|
||||
"/chunks/settings-subscription.65c3a6c2664c78278563.hot-update.js": "/chunks/settings-subscription.65c3a6c2664c78278563.hot-update.js",
|
||||
"/chunks/settings-subscription.026cbdceda141d9b2245.hot-update.js": "/chunks/settings-subscription.026cbdceda141d9b2245.hot-update.js",
|
||||
"/chunks/settings-subscription.17cf6234cf89cacf3c11.hot-update.js": "/chunks/settings-subscription.17cf6234cf89cacf3c11.hot-update.js",
|
||||
"/chunks/settings-subscription.e486c5b8045be69f6f42.hot-update.js": "/chunks/settings-subscription.e486c5b8045be69f6f42.hot-update.js",
|
||||
"/chunks/settings-subscription.dab138155bae02a07442.hot-update.js": "/chunks/settings-subscription.dab138155bae02a07442.hot-update.js",
|
||||
"/chunks/settings-subscription.05d12821e29cc8ad3327.hot-update.js": "/chunks/settings-subscription.05d12821e29cc8ad3327.hot-update.js",
|
||||
"/chunks/settings.e96984dc00f369b921be.hot-update.js": "/chunks/settings.e96984dc00f369b921be.hot-update.js",
|
||||
"/chunks/settings-subscription.c7e15be580d5df854371.hot-update.js": "/chunks/settings-subscription.c7e15be580d5df854371.hot-update.js",
|
||||
"/js/main.e0d1278223f2b1218a69.hot-update.js": "/js/main.e0d1278223f2b1218a69.hot-update.js",
|
||||
"/chunks/settings-storage~chunks/user-storage~chunks/user-subscription.js": "/chunks/settings-storage~chunks/user-storage~chunks/user-subscription.js?id=8e604e2d3de21807f411",
|
||||
"/chunks/settings-subscription.e0d1278223f2b1218a69.hot-update.js": "/chunks/settings-subscription.e0d1278223f2b1218a69.hot-update.js",
|
||||
"/js/main.94d29532d02191fff30a.hot-update.js": "/js/main.94d29532d02191fff30a.hot-update.js",
|
||||
"/chunks/settings-subscription.94d29532d02191fff30a.hot-update.js": "/chunks/settings-subscription.94d29532d02191fff30a.hot-update.js",
|
||||
"/chunks/settings-subscription.b27df0d1c2a29354a0e1.hot-update.js": "/chunks/settings-subscription.b27df0d1c2a29354a0e1.hot-update.js",
|
||||
"/chunks/settings-subscription.2801fc466654a71cd74b.hot-update.js": "/chunks/settings-subscription.2801fc466654a71cd74b.hot-update.js",
|
||||
"/chunks/settings-subscription.de6689d25cda8fac02a1.hot-update.js": "/chunks/settings-subscription.de6689d25cda8fac02a1.hot-update.js",
|
||||
"/chunks/settings-subscription.95fe74dbdc544d18a9e6.hot-update.js": "/chunks/settings-subscription.95fe74dbdc544d18a9e6.hot-update.js",
|
||||
"/chunks/settings-subscription.3eee9af51b3e86eefd56.hot-update.js": "/chunks/settings-subscription.3eee9af51b3e86eefd56.hot-update.js",
|
||||
"/chunks/settings-subscription.83f45e3d70d4156666b1.hot-update.js": "/chunks/settings-subscription.83f45e3d70d4156666b1.hot-update.js",
|
||||
"/chunks/settings-subscription.bbaee001688f6984937a.hot-update.js": "/chunks/settings-subscription.bbaee001688f6984937a.hot-update.js",
|
||||
"/chunks/settings-subscription.b54654c49f6a313fc0f7.hot-update.js": "/chunks/settings-subscription.b54654c49f6a313fc0f7.hot-update.js",
|
||||
"/chunks/settings-subscription.6b747655d1469a1fdfb9.hot-update.js": "/chunks/settings-subscription.6b747655d1469a1fdfb9.hot-update.js",
|
||||
"/chunks/settings-subscription.758bb312d12c94586c16.hot-update.js": "/chunks/settings-subscription.758bb312d12c94586c16.hot-update.js",
|
||||
"/chunks/settings-subscription.e9b938bc9f0b1a8aa512.hot-update.js": "/chunks/settings-subscription.e9b938bc9f0b1a8aa512.hot-update.js",
|
||||
"/chunks/settings-subscription.92d10833b93f8d8e8654.hot-update.js": "/chunks/settings-subscription.92d10833b93f8d8e8654.hot-update.js",
|
||||
"/chunks/settings-subscription.abc31ac6d150af7854fe.hot-update.js": "/chunks/settings-subscription.abc31ac6d150af7854fe.hot-update.js",
|
||||
"/chunks/settings-subscription.6301b365d421894c758f.hot-update.js": "/chunks/settings-subscription.6301b365d421894c758f.hot-update.js",
|
||||
"/chunks/settings-subscription.fda9651142d719ea5de1.hot-update.js": "/chunks/settings-subscription.fda9651142d719ea5de1.hot-update.js",
|
||||
"/chunks/settings-subscription.bd5402ef92e7600787c8.hot-update.js": "/chunks/settings-subscription.bd5402ef92e7600787c8.hot-update.js",
|
||||
"/chunks/settings-subscription.84e4ea1bb5840d78db33.hot-update.js": "/chunks/settings-subscription.84e4ea1bb5840d78db33.hot-update.js",
|
||||
"/chunks/settings-subscription.80399b9ba8ad4e93e9fd.hot-update.js": "/chunks/settings-subscription.80399b9ba8ad4e93e9fd.hot-update.js",
|
||||
"/chunks/settings-subscription.6af3c8ede63ea46c5a63.hot-update.js": "/chunks/settings-subscription.6af3c8ede63ea46c5a63.hot-update.js",
|
||||
"/chunks/settings-subscription.ebd7175b334d9b3e7d45.hot-update.js": "/chunks/settings-subscription.ebd7175b334d9b3e7d45.hot-update.js",
|
||||
"/chunks/settings-subscription.bed5f903f0e9c031acca.hot-update.js": "/chunks/settings-subscription.bed5f903f0e9c031acca.hot-update.js",
|
||||
"/chunks/settings-subscription.da1f88586e55926297a8.hot-update.js": "/chunks/settings-subscription.da1f88586e55926297a8.hot-update.js",
|
||||
"/chunks/settings-subscription.9d8626d3a44a55e2a2fc.hot-update.js": "/chunks/settings-subscription.9d8626d3a44a55e2a2fc.hot-update.js",
|
||||
"/chunks/settings-subscription.130258d7b204f0ad2d08.hot-update.js": "/chunks/settings-subscription.130258d7b204f0ad2d08.hot-update.js",
|
||||
"/chunks/settings-subscription.a2124fb988816eab5551.hot-update.js": "/chunks/settings-subscription.a2124fb988816eab5551.hot-update.js",
|
||||
"/chunks/settings-subscription.e94c2790ce524e8553fa.hot-update.js": "/chunks/settings-subscription.e94c2790ce524e8553fa.hot-update.js",
|
||||
"/chunks/settings-subscription.c072d148d313aec82253.hot-update.js": "/chunks/settings-subscription.c072d148d313aec82253.hot-update.js",
|
||||
"/chunks/settings-subscription.6a54cdd550afffd7a451.hot-update.js": "/chunks/settings-subscription.6a54cdd550afffd7a451.hot-update.js",
|
||||
"/chunks/settings-subscription.bce79ac8a4d22932fecc.hot-update.js": "/chunks/settings-subscription.bce79ac8a4d22932fecc.hot-update.js",
|
||||
"/chunks/settings-subscription.b537a69762ca9d61eacf.hot-update.js": "/chunks/settings-subscription.b537a69762ca9d61eacf.hot-update.js",
|
||||
"/chunks/settings-subscription.b75123cb1859deef7453.hot-update.js": "/chunks/settings-subscription.b75123cb1859deef7453.hot-update.js",
|
||||
"/chunks/settings-subscription.ee3f8fd4ac9bf6a41925.hot-update.js": "/chunks/settings-subscription.ee3f8fd4ac9bf6a41925.hot-update.js",
|
||||
"/chunks/settings-subscription.43676700043777805465.hot-update.js": "/chunks/settings-subscription.43676700043777805465.hot-update.js",
|
||||
"/chunks/settings-subscription.2b954e5d777d0fc26a79.hot-update.js": "/chunks/settings-subscription.2b954e5d777d0fc26a79.hot-update.js",
|
||||
"/chunks/settings-subscription.52017f0f1612dae7531f.hot-update.js": "/chunks/settings-subscription.52017f0f1612dae7531f.hot-update.js",
|
||||
"/chunks/settings-subscription.5a5a2d6139bb6c872e68.hot-update.js": "/chunks/settings-subscription.5a5a2d6139bb6c872e68.hot-update.js",
|
||||
"/chunks/settings-subscription.92af7b9ab14d7061db8c.hot-update.js": "/chunks/settings-subscription.92af7b9ab14d7061db8c.hot-update.js",
|
||||
"/chunks/settings-subscription.02fded6119bf3ff81a62.hot-update.js": "/chunks/settings-subscription.02fded6119bf3ff81a62.hot-update.js",
|
||||
"/chunks/settings-subscription.41873ee1e29346fdb9d6.hot-update.js": "/chunks/settings-subscription.41873ee1e29346fdb9d6.hot-update.js",
|
||||
"/chunks/settings-subscription.f257c1f7275a4023a5e5.hot-update.js": "/chunks/settings-subscription.f257c1f7275a4023a5e5.hot-update.js",
|
||||
"/chunks/settings-subscription.a479dfe7779e980c598a.hot-update.js": "/chunks/settings-subscription.a479dfe7779e980c598a.hot-update.js",
|
||||
"/chunks/settings-subscription.bd3fac38b34981307256.hot-update.js": "/chunks/settings-subscription.bd3fac38b34981307256.hot-update.js",
|
||||
"/chunks/settings-subscription.d4b3af0c7e415166267a.hot-update.js": "/chunks/settings-subscription.d4b3af0c7e415166267a.hot-update.js",
|
||||
"/chunks/settings-subscription.2f6a9e00212e8a65eb84.hot-update.js": "/chunks/settings-subscription.2f6a9e00212e8a65eb84.hot-update.js",
|
||||
"/chunks/settings-subscription.1c7d9124308f62476eba.hot-update.js": "/chunks/settings-subscription.1c7d9124308f62476eba.hot-update.js",
|
||||
"/chunks/settings-subscription.9355ca6c6767cf49c647.hot-update.js": "/chunks/settings-subscription.9355ca6c6767cf49c647.hot-update.js",
|
||||
"/chunks/settings-subscription.17cbe4f253b19faa9925.hot-update.js": "/chunks/settings-subscription.17cbe4f253b19faa9925.hot-update.js",
|
||||
"/chunks/settings-subscription.ad1cd7e7a867d20c34bc.hot-update.js": "/chunks/settings-subscription.ad1cd7e7a867d20c34bc.hot-update.js",
|
||||
"/chunks/settings-subscription.2acd6a144aab40274f41.hot-update.js": "/chunks/settings-subscription.2acd6a144aab40274f41.hot-update.js",
|
||||
"/chunks/settings-subscription.5da79ada0097426cc0ef.hot-update.js": "/chunks/settings-subscription.5da79ada0097426cc0ef.hot-update.js",
|
||||
"/chunks/settings-subscription.ca4b3eb159d8fa68a0ea.hot-update.js": "/chunks/settings-subscription.ca4b3eb159d8fa68a0ea.hot-update.js",
|
||||
"/chunks/settings-subscription.8644733dab5a11edf836.hot-update.js": "/chunks/settings-subscription.8644733dab5a11edf836.hot-update.js",
|
||||
"/chunks/settings-subscription.3e0c1d6746d8817c2b4d.hot-update.js": "/chunks/settings-subscription.3e0c1d6746d8817c2b4d.hot-update.js",
|
||||
"/chunks/settings-subscription.2126b3cec0a3cdd2f621.hot-update.js": "/chunks/settings-subscription.2126b3cec0a3cdd2f621.hot-update.js",
|
||||
"/chunks/settings-subscription.62eff09d38e9df1b8c50.hot-update.js": "/chunks/settings-subscription.62eff09d38e9df1b8c50.hot-update.js",
|
||||
"/chunks/settings-subscription.0d71478d74726b77ef9c.hot-update.js": "/chunks/settings-subscription.0d71478d74726b77ef9c.hot-update.js",
|
||||
"/chunks/settings-subscription.4187247505559d55837c.hot-update.js": "/chunks/settings-subscription.4187247505559d55837c.hot-update.js",
|
||||
"/chunks/settings-subscription.dd1ad905edd70bbe4da3.hot-update.js": "/chunks/settings-subscription.dd1ad905edd70bbe4da3.hot-update.js",
|
||||
"/chunks/settings-subscription.7952f2b152b1eed82dfe.hot-update.js": "/chunks/settings-subscription.7952f2b152b1eed82dfe.hot-update.js",
|
||||
"/chunks/settings-subscription.486e95f60340414adae0.hot-update.js": "/chunks/settings-subscription.486e95f60340414adae0.hot-update.js",
|
||||
"/chunks/settings-subscription.5e694026aa1c749d5fdf.hot-update.js": "/chunks/settings-subscription.5e694026aa1c749d5fdf.hot-update.js",
|
||||
"/chunks/settings-subscription.aca0b237b57c89cd5645.hot-update.js": "/chunks/settings-subscription.aca0b237b57c89cd5645.hot-update.js",
|
||||
"/chunks/settings-subscription.4c10e05bbead440cdb3f.hot-update.js": "/chunks/settings-subscription.4c10e05bbead440cdb3f.hot-update.js",
|
||||
"/chunks/settings-subscription.dd3eb723866fd9fce277.hot-update.js": "/chunks/settings-subscription.dd3eb723866fd9fce277.hot-update.js",
|
||||
"/chunks/settings-subscription.5a9a453b574117e78b07.hot-update.js": "/chunks/settings-subscription.5a9a453b574117e78b07.hot-update.js",
|
||||
"/chunks/settings-subscription.5a1526f8e4bae1f1606b.hot-update.js": "/chunks/settings-subscription.5a1526f8e4bae1f1606b.hot-update.js",
|
||||
"/chunks/settings-subscription.e1d0145d0632614cff28.hot-update.js": "/chunks/settings-subscription.e1d0145d0632614cff28.hot-update.js",
|
||||
"/chunks/settings-subscription.a5c2769ab36a0a7e4858.hot-update.js": "/chunks/settings-subscription.a5c2769ab36a0a7e4858.hot-update.js",
|
||||
"/chunks/settings-subscription.eb14b8d7a25673ce577c.hot-update.js": "/chunks/settings-subscription.eb14b8d7a25673ce577c.hot-update.js",
|
||||
"/chunks/settings-subscription.f85d6f6076d464817e47.hot-update.js": "/chunks/settings-subscription.f85d6f6076d464817e47.hot-update.js",
|
||||
"/chunks/settings-subscription.e5f89d670880092b50bc.hot-update.js": "/chunks/settings-subscription.e5f89d670880092b50bc.hot-update.js",
|
||||
"/js/main.987bb389470e6ba108d4.hot-update.js": "/js/main.987bb389470e6ba108d4.hot-update.js",
|
||||
"/chunks/dashboard~chunks/invoices~chunks/pages~chunks/plan-subscribers~chunks/plans~chunks/settings-p~106e7cc8.js": "/chunks/dashboard~chunks/invoices~chunks/pages~chunks/plan-subscribers~chunks/plans~chunks/settings-p~106e7cc8.js?id=40162f04656017a75f9d",
|
||||
"/chunks/settings-subscription.987bb389470e6ba108d4.hot-update.js": "/chunks/settings-subscription.987bb389470e6ba108d4.hot-update.js",
|
||||
"/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~b810e987.js": "/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~b810e987.js?id=f993f201235dfd5afa22",
|
||||
"/js/main.c3d197911eb089ad4037.hot-update.js": "/js/main.c3d197911eb089ad4037.hot-update.js",
|
||||
"/chunks/settings-subscription.c3d197911eb089ad4037.hot-update.js": "/chunks/settings-subscription.c3d197911eb089ad4037.hot-update.js",
|
||||
"/chunks/settings-subscription.32fb08521f72921a5288.hot-update.js": "/chunks/settings-subscription.32fb08521f72921a5288.hot-update.js",
|
||||
"/chunks/settings-subscription.7588579a4b91d06f520c.hot-update.js": "/chunks/settings-subscription.7588579a4b91d06f520c.hot-update.js",
|
||||
"/chunks/settings-subscription.a04f868ebf28587f2253.hot-update.js": "/chunks/settings-subscription.a04f868ebf28587f2253.hot-update.js",
|
||||
"/chunks/settings-subscription.e0e89120a76142fc9d68.hot-update.js": "/chunks/settings-subscription.e0e89120a76142fc9d68.hot-update.js",
|
||||
"/chunks/settings-subscription.ac039928ef7a58eb93d5.hot-update.js": "/chunks/settings-subscription.ac039928ef7a58eb93d5.hot-update.js",
|
||||
"/chunks/settings-subscription.f67a0de1acead88abdb0.hot-update.js": "/chunks/settings-subscription.f67a0de1acead88abdb0.hot-update.js",
|
||||
"/chunks/settings.728b8825ad6f4a1070f8.hot-update.js": "/chunks/settings.728b8825ad6f4a1070f8.hot-update.js",
|
||||
"/chunks/settings-subscription.ccb544093500d934198a.hot-update.js": "/chunks/settings-subscription.ccb544093500d934198a.hot-update.js",
|
||||
"/js/main.7872a1b28e6af5b603f5.hot-update.js": "/js/main.7872a1b28e6af5b603f5.hot-update.js",
|
||||
"/chunks/settings-subscription.7872a1b28e6af5b603f5.hot-update.js": "/chunks/settings-subscription.7872a1b28e6af5b603f5.hot-update.js",
|
||||
"/js/main.99210e06aba2e80e12c7.hot-update.js": "/js/main.99210e06aba2e80e12c7.hot-update.js",
|
||||
"/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~d00129eb.js": "/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~d00129eb.js?id=fed76264f26694c9db37",
|
||||
"/chunks/settings-create-payment-methods.99210e06aba2e80e12c7.hot-update.js": "/chunks/settings-create-payment-methods.99210e06aba2e80e12c7.hot-update.js",
|
||||
"/js/main.fac1b13bd1efc2f42f34.hot-update.js": "/js/main.fac1b13bd1efc2f42f34.hot-update.js",
|
||||
"/chunks/admin~chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-item~3b21240c.js": "/chunks/admin~chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-item~3b21240c.js?id=1e02bcb6e880e4c679a1",
|
||||
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.fac1b13bd1efc2f42f34.hot-update.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.fac1b13bd1efc2f42f34.hot-update.js",
|
||||
"/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~217928f6.js": "/chunks/app-appearance~chunks/app-billings~chunks/app-email~chunks/app-index~chunks/app-others~chunks~217928f6.js?id=9c4d17d768dc543a0e4b",
|
||||
"/chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-items~chunks/page~8729b0a3.js": "/chunks/app-language~chunks/dashboard~chunks/files~chunks/invoices~chunks/my-shared-items~chunks/page~8729b0a3.js?id=50f7067283a882b798bd",
|
||||
"/chunks/billing.js": "/chunks/billing.js?id=8a75c805a916b03597d5",
|
||||
"/chunks/billing~chunks/dashboard~chunks/invoices~chunks/pages~chunks/plan-subscribers~chunks/plans~ch~5d1c5841.js": "/chunks/billing~chunks/dashboard~chunks/invoices~chunks/pages~chunks/plan-subscribers~chunks/plans~ch~5d1c5841.js?id=f8f80b9b2ff56cb089fb",
|
||||
"/chunks/billing~chunks/settings-storage~chunks/user-storage~chunks/user-subscription.js": "/chunks/billing~chunks/settings-storage~chunks/user-storage~chunks/user-subscription.js?id=9bb4b7b4c8f9442fc828",
|
||||
"/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~81844718.js": "/vendors~chunks/admin~chunks/admin-account~chunks/app-appearance~chunks/app-billings~chunks/app-email~81844718.js?id=21edd02ba7123e444147",
|
||||
"/js/main.2701b65e37c8b8796a85.hot-update.js": "/js/main.2701b65e37c8b8796a85.hot-update.js",
|
||||
"/chunks/billing.580dad5d484c9e080cc1.hot-update.js": "/chunks/billing.580dad5d484c9e080cc1.hot-update.js",
|
||||
"/chunks/admin~chunks/platform~chunks/settings~chunks/shared.643f2961de5c2d459f87.hot-update.js": "/chunks/admin~chunks/platform~chunks/settings~chunks/shared.643f2961de5c2d459f87.hot-update.js",
|
||||
"/chunks/settings.3da09fb5fd6633f80e4f.hot-update.js": "/chunks/settings.3da09fb5fd6633f80e4f.hot-update.js",
|
||||
"/chunks/settings-password.3da09fb5fd6633f80e4f.hot-update.js": "/chunks/settings-password.3da09fb5fd6633f80e4f.hot-update.js",
|
||||
"/chunks/settings.c119a078f32d91bd411c.hot-update.js": "/chunks/settings.c119a078f32d91bd411c.hot-update.js",
|
||||
"/chunks/settings.52081832b31602c240a6.hot-update.js": "/chunks/settings.52081832b31602c240a6.hot-update.js",
|
||||
"/chunks/settings.2088d8ae67f8cd6d39d2.hot-update.js": "/chunks/settings.2088d8ae67f8cd6d39d2.hot-update.js",
|
||||
"/chunks/settings.aacd1d7d23d640557259.hot-update.js": "/chunks/settings.aacd1d7d23d640557259.hot-update.js",
|
||||
"/chunks/settings~chunks/settings-password.aacd1d7d23d640557259.hot-update.js": "/chunks/settings~chunks/settings-password.aacd1d7d23d640557259.hot-update.js",
|
||||
"/chunks/settings.5f0086508ea24618153f.hot-update.js": "/chunks/settings.5f0086508ea24618153f.hot-update.js",
|
||||
"/chunks/settings.74bc0ec39a2763776f82.hot-update.js": "/chunks/settings.74bc0ec39a2763776f82.hot-update.js",
|
||||
"/chunks/settings.c3d61ef320de2a2e416c.hot-update.js": "/chunks/settings.c3d61ef320de2a2e416c.hot-update.js",
|
||||
"/chunks/settings.ea354159b254df466a33.hot-update.js": "/chunks/settings.ea354159b254df466a33.hot-update.js",
|
||||
"/chunks/settings.6743a7421e351e032520.hot-update.js": "/chunks/settings.6743a7421e351e032520.hot-update.js"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="{'mb-7': !isLast}" class="flex justify-between space-x-8 w-full">
|
||||
<div :class="{'mb-7': !isLast}" class="sm:flex justify-between sm:space-x-8 sm:space-y-0 space-y-4 w-full">
|
||||
|
||||
<!--Label for input-->
|
||||
<div class="leading-5">
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'PaymentMethod',
|
||||
props: [
|
||||
'description',
|
||||
'driver',
|
||||
]
|
||||
}
|
||||
export default {
|
||||
name: 'PaymentMethod',
|
||||
props: [
|
||||
'description',
|
||||
'driver',
|
||||
]
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
<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', 'Subscription', 'MeteredSubscription'].includes(result.action.value)" 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" class="vue-feather text-theme"/>
|
||||
@@ -408,12 +408,10 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: this.$t('Show my Subscription'),
|
||||
title: this.$t('Show my Billing'),
|
||||
action: {
|
||||
type: 'route',
|
||||
value: this.config.subscriptionType === 'fixed'
|
||||
? 'Subscription'
|
||||
: 'MeteredSubscription',
|
||||
value: 'Billing',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
56
resources/js/components/Subscription/PaymentCard.vue
Normal file
56
resources/js/components/Subscription/PaymentCard.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-between py-3 px-4 input-dark">
|
||||
<div class="flex items-center">
|
||||
<img :src="`/assets/gateways/${card.data.attributes.brand}.svg`" alt="" class="h-5 mr-3 rounded">
|
||||
<b class="text-sm font-bold leading-none capitalize">
|
||||
{{ 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('Are you sure you want to delete your credit card?'),
|
||||
message: this.$t('We will no longer settle your payments automatically and you will have to fund your account for the next payments.'),
|
||||
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('Your credit card was deleted.'),
|
||||
})
|
||||
})
|
||||
.catch(() => this.$isSomethingWrong())
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -5,22 +5,45 @@
|
||||
|
||||
<!--Payment Options-->
|
||||
<div v-if="isPaymentOptionPage">
|
||||
|
||||
<PopupContent class="px-4">
|
||||
<b class="text-center block mb-3 mt-8">
|
||||
Stripe
|
||||
</b>
|
||||
<ButtonBase @click.native="payByStripe" class="block w-full mb-6" button-style="theme" type="button">
|
||||
<span class="text-theme">
|
||||
Pay With Stripe
|
||||
</span>
|
||||
</ButtonBase>
|
||||
|
||||
<b class="text-center block mb-3 mt-8">
|
||||
PayStack
|
||||
</b>
|
||||
<ButtonBase class="block w-full mb-6" button-style="theme" type="button">
|
||||
<!--Stripe implementation-->
|
||||
<PaymentMethod
|
||||
v-if="config.isStripe"
|
||||
driver="stripe"
|
||||
:description="$t('Pay by your credit card or Apple Pay')"
|
||||
>
|
||||
<span v-if="! paypalMethodsLoaded" @click="payByStripe" class="text-sm text-theme font-bold cursor-pointer">
|
||||
{{ $t('Select') }}
|
||||
</span>
|
||||
</PaymentMethod>
|
||||
|
||||
<!--PayPal implementation-->
|
||||
<div v-if="config.isPayPal" :class="{'dark:bg-2x-dark-foreground bg-light-background rounded-xl px-4 mb-2': paypalMethodsLoaded}">
|
||||
<PaymentMethod
|
||||
@click.native="pickedPaymentMethod('paypal')"
|
||||
driver="paypal"
|
||||
:description="$t('Available PayPal Credit, Debit or Credit Card.')"
|
||||
>
|
||||
<span v-if="! paypalMethodsLoaded" class="text-sm text-theme font-bold cursor-pointer">
|
||||
{{ $t('Select') }}
|
||||
</span>
|
||||
</PaymentMethod>
|
||||
|
||||
<!--PayPal Buttons-->
|
||||
<div v-if="paypalMethodsLoaded">
|
||||
<div id="paypal-button-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Paystack implementation-->
|
||||
<PaymentMethod
|
||||
v-if="config.isPaystack"
|
||||
driver="paystack"
|
||||
:description="$t('Available Bank Account, USSD, Mobile Money, Apple Pay')"
|
||||
>
|
||||
<paystack
|
||||
v-if="user && config"
|
||||
:channels="['bank', 'ussd', 'qr', 'mobile_money', 'bank_transfer']"
|
||||
class="font-bold"
|
||||
currency="ZAR"
|
||||
@@ -32,18 +55,22 @@
|
||||
:callback="paymentSuccessful"
|
||||
:close="paystackClosed"
|
||||
>
|
||||
<span class="text-theme">
|
||||
Pay With PayStack
|
||||
<span class="text-sm text-theme font-bold cursor-pointer">
|
||||
{{ $t('Select') }}
|
||||
</span>
|
||||
</paystack>
|
||||
</ButtonBase>
|
||||
|
||||
<b class="text-center block mb-3">
|
||||
PayPal
|
||||
</b>
|
||||
<!--PayPal Button-->
|
||||
<div id="paypal-button-container"></div>
|
||||
</PaymentMethod>
|
||||
</PopupContent>
|
||||
|
||||
<PopupActions>
|
||||
<ButtonBase
|
||||
class="popup-button"
|
||||
@click.native="$closePopup()"
|
||||
button-style="secondary"
|
||||
>
|
||||
{{ $t('Cancel Payment') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
</div>
|
||||
|
||||
<!--Select Payment Plans-->
|
||||
@@ -55,8 +82,8 @@
|
||||
<label :class="{'text-gray-400': !isYearlyPlans}" class="font-bold cursor-pointer text-xs">
|
||||
{{ $t('Billed Annually') }}
|
||||
</label>
|
||||
<div class="relative inline-block w-14 align-middle select-none">
|
||||
<SwitchInput class="transform scale-90" v-model="isYearlyPlans" :state="isYearlyPlans" />
|
||||
<div class="relative inline-block w-12 align-middle select-none">
|
||||
<SwitchInput class="transform scale-75" v-model="isYearlyPlans" :state="isYearlyPlans" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,7 +112,7 @@
|
||||
<ButtonBase
|
||||
class="popup-button"
|
||||
:button-style="buttonStyle"
|
||||
@click.native="showPaymentOptions"
|
||||
@click.native="isPaymentOptionPage = true"
|
||||
>{{ $t('Upgrade Account') }}
|
||||
</ButtonBase>
|
||||
</PopupActions>
|
||||
@@ -94,6 +121,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PaymentMethod from "../Others/PaymentMethod";
|
||||
import { loadScript } from "@paypal/paypal-js";
|
||||
import SwitchInput from '/resources/js/components/Others/Forms/SwitchInput'
|
||||
import PopupWrapper from '/resources/js/components/Others/Popup/PopupWrapper'
|
||||
@@ -110,6 +138,7 @@
|
||||
export default {
|
||||
name: 'SelectPlanSubscriptionPopup',
|
||||
components: {
|
||||
PaymentMethod,
|
||||
paystack,
|
||||
PlanDetail,
|
||||
SwitchInput,
|
||||
@@ -152,6 +181,8 @@
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
paypalMethodsLoaded: false,
|
||||
|
||||
isPaymentOptionPage: false,
|
||||
isYearlyPlans: false,
|
||||
isLoading: false,
|
||||
@@ -160,19 +191,17 @@
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
payByStripe() {
|
||||
axios.post('/api/subscriptions/stripe/checkout', {
|
||||
planCode: this.selectedPlan.data.meta.driver_plan_id.stripe
|
||||
})
|
||||
.then(response => {
|
||||
window.location = response.data.url
|
||||
})
|
||||
pickedPaymentMethod(driver) {
|
||||
if (driver === 'paystack') {
|
||||
this.$closePopup()
|
||||
}
|
||||
if (driver === 'paypal' && !this.paypalMethodsLoaded) {
|
||||
this.PayPalInitialization()
|
||||
}
|
||||
},
|
||||
async showPaymentOptions() {
|
||||
// Show payment buttons page
|
||||
this.isPaymentOptionPage = true
|
||||
async PayPalInitialization() {
|
||||
this.paypalMethodsLoaded = true
|
||||
|
||||
// PayPal
|
||||
let paypal;
|
||||
|
||||
try {
|
||||
@@ -183,7 +212,7 @@
|
||||
} catch (error) {
|
||||
events.$emit('toaster', {
|
||||
type: 'danger',
|
||||
message: this.$t('failed to load the PayPal components'),
|
||||
message: this.$t('Failed to load the PayPal service'),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -191,8 +220,8 @@
|
||||
const userId = this.user.data.id
|
||||
const app = this
|
||||
|
||||
// Initialize paypal buttons for subscription
|
||||
/*await paypal.Buttons({
|
||||
// Initialize paypal buttons for single charge
|
||||
await paypal.Buttons({
|
||||
createSubscription: function(data, actions) {
|
||||
return actions.subscription.create({
|
||||
plan_id: planId,
|
||||
@@ -202,22 +231,16 @@
|
||||
onApprove: function(data, actions) {
|
||||
app.paymentSuccessful()
|
||||
}
|
||||
}).render('#paypal-button-container');*/
|
||||
|
||||
// Initialize paypal buttons for single charge
|
||||
await paypal.Buttons({
|
||||
createOrder: function(data, actions) {
|
||||
return actions.order.create({
|
||||
purchase_units: [{
|
||||
amount: {
|
||||
value: '10.49',
|
||||
},
|
||||
custom_id: userId
|
||||
}]
|
||||
});
|
||||
}
|
||||
}).render('#paypal-button-container');
|
||||
},
|
||||
payByStripe() {
|
||||
axios.post('/api/stripe/checkout', {
|
||||
planCode: this.selectedPlan.data.meta.driver_plan_id.stripe
|
||||
})
|
||||
.then(response => {
|
||||
window.location = response.data.url
|
||||
})
|
||||
},
|
||||
selectPlan(plan) {
|
||||
this.selectedPlan = plan
|
||||
},
|
||||
|
||||
74
resources/js/components/Subscription/UserBalance.vue
Normal file
74
resources/js/components/Subscription/UserBalance.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div v-if="! hasPaymentMethod" class="card shadow-card">
|
||||
<FormLabel icon="dollar">
|
||||
{{ $t('Balance') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ 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="Capacity" rules="required">
|
||||
<AppInputText :description="$t('The amount will be increased as soon as we register your charge from payment gateway.')" :error="errors[0]" :is-last="true">
|
||||
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
|
||||
<input v-model="chargeAmount"
|
||||
:placeholder="$t('Fund Your Account Balance...')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{'border-red-700': errors[0]}"
|
||||
/>
|
||||
<ButtonBase type="submit" button-style="theme" class="sm:w-auto w-full">
|
||||
{{ $t('Make a Payment') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {ValidationObserver, ValidationProvider} from 'vee-validate/dist/vee-validate.full'
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import AppInputText from "../Admin/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>
|
||||
189
resources/js/components/Subscription/UserBillingAlerts.vue
Normal file
189
resources/js/components/Subscription/UserBillingAlerts.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="bell">
|
||||
{{ $t('Billing Alert') }}
|
||||
</FormLabel>
|
||||
|
||||
<div v-if="user.data.relationships.alert">
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5 flex items-center">
|
||||
{{ user.data.relationships.alert.data.attributes.formatted }}
|
||||
<edit2-icon v-if="! showUpdateBillingAlertForm" @click="showUpdateBillingAlertForm = ! showUpdateBillingAlertForm" size="12" class="vue-feather cursor-pointer ml-2 transform -translate-y-0.5" />
|
||||
<trash2-icon v-if="showUpdateBillingAlertForm" @click="deleteBillingAlert" size="12" class="vue-feather cursor-pointer ml-2 transform -translate-y-0.5" />
|
||||
</b>
|
||||
|
||||
<b class="block text-sm text-gray-400">
|
||||
{{ $t('Alert will be triggered after you reach the value above.') }}
|
||||
</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('You will receive an email whenever your monthly balance reaches the specified amount above.')" :error="errors[0]" :is-last="true">
|
||||
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
|
||||
<input v-model="billingAlertAmount"
|
||||
:placeholder="$t('Alert Amount...')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{'border-red-700': errors[0]}"
|
||||
/>
|
||||
<ButtonBase :loadint="isSendingBillingAlert" :disabled="isSendingBillingAlert" type="submit" button-style="theme" class="sm:w-auto w-full">
|
||||
{{ $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('You will receive an email whenever your monthly balance reaches the specified amount above.')" :error="errors[0]" :is-last="true">
|
||||
<div class="flex space-x-4">
|
||||
<input v-model="billingAlertAmount"
|
||||
:placeholder="$t('Alert Amount...')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{'border-red-700': errors[0]}"
|
||||
/>
|
||||
<ButtonBase :loadint="isSendingBillingAlert" :disabled="isSendingBillingAlert" type="submit" button-style="theme" class="submit-button">
|
||||
{{ $t('Set Alert') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {ValidationObserver, ValidationProvider} from 'vee-validate/dist/vee-validate.full'
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import AppInputText from "../Admin/AppInputText"
|
||||
import FormLabel from "../Others/Forms/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('Your billing alert was updated successfully'),
|
||||
})
|
||||
})
|
||||
.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('Your billing alert was 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('Are you sure you want to delete your alert?'),
|
||||
message: this.$t('You will no longer receive any notifications that your billing limit has been exceeded.'),
|
||||
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('Your billing alert was deleted.'),
|
||||
})
|
||||
})
|
||||
.catch(() => this.$isSomethingWrong())
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Edit your Subscription') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch :title="$t('Cancel Subscription')" :description="$t('You can cancel your subscription now. You\'ll continue to have access to the features you\'ve paid for until the end of your billing cycle.')">
|
||||
<ButtonBase @click.native="cancelSubscriptionConfirmation" :loading="isCancelling" class="sm:w-auto w-full" button-style="secondary">
|
||||
{{ $t('Cancel Now') }}
|
||||
</ButtonBase>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputSwitch :title="$t('Upgrade Plan')" :description="$t('You can upgrade your plan at any time you want.')" :is-last="true">
|
||||
<ButtonBase @click.native="$openUpgradeOptions" class="sm:w-auto w-full" button-style="secondary">
|
||||
{{ $t('Upgrade Now') }}
|
||||
</ButtonBase>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppInputSwitch from "../Admin/AppInputSwitch"
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import {events} from "../../bus";
|
||||
import axios from "axios";
|
||||
|
||||
export default {
|
||||
name: 'UserEditSubscription',
|
||||
components: {
|
||||
AppInputSwitch,
|
||||
ButtonBase,
|
||||
FormLabel
|
||||
},
|
||||
computed: {
|
||||
subscription() {
|
||||
return this.$store.getters.user.data.relationships.subscription.data
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isCancelling: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cancelSubscriptionConfirmation() {
|
||||
events.$emit('confirm:open', {
|
||||
title: this.$t('Are you sure you want to cancel subscription?'),
|
||||
message: this.$t("You'll continue to have access to the features you've paid for until the end of your billing cycle."),
|
||||
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').then(() => {
|
||||
this.fetchSubscriptionDetail()
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div v-if="! hasSubscription" class="card shadow-card">
|
||||
<InfoBox style="margin-bottom: 0">
|
||||
<p>{{ $t("You don't have any subscription") }}</p>
|
||||
</InfoBox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
|
||||
export default {
|
||||
name: 'UserEmptySubscription',
|
||||
components: {
|
||||
InfoBox
|
||||
},
|
||||
computed: {
|
||||
hasSubscription() {
|
||||
return this.$store.getters.user.data.relationships.subscription
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
62
resources/js/components/Subscription/UserFailedPayments.vue
Normal file
62
resources/js/components/Subscription/UserFailedPayments.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<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="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
-{{ user.data.meta.totalDebt }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ $t("We are unable to charge your usage. Please register new credit card or fund your account with sufficient amount and we'll give it another try!") }}
|
||||
</b>
|
||||
|
||||
<!--Failed Payments-->
|
||||
<div
|
||||
v-for="payment in user.data.relationships.failedPayments.data"
|
||||
:key="payment.data.id"
|
||||
class="flex items-center justify-between py-2 border-b dark:border-opacity-5 border-light border-dashed"
|
||||
>
|
||||
<div class="w-2/4 leading-none">
|
||||
<b class="text-sm font-bold leading-none">
|
||||
{{ payment.data.attributes.note }}
|
||||
</b>
|
||||
</div>
|
||||
<div class="text-left w-1/4">
|
||||
<span class="text-sm font-bold text-gray-560 capitalize">
|
||||
{{ $t(payment.data.attributes.source) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right w-1/4">
|
||||
<span class="text-sm font-bold">
|
||||
{{ payment.data.attributes.created_at }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right w-1/4">
|
||||
<span class="text-sm font-bold text-red">
|
||||
{{ payment.data.attributes.amount }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
import {mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'UserFailedPayments',
|
||||
components: {
|
||||
FormLabel,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'user',
|
||||
])
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Subscription') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ status }}
|
||||
</b>
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-8">
|
||||
{{ 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 text-gray-400">
|
||||
{{ limit.message }}
|
||||
</b>
|
||||
<ProgressLine :data="limit.distribution" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import ProgressLine from "../Admin/ProgressLine"
|
||||
import {mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'UserFixedSubscriptionDetail',
|
||||
components: {
|
||||
ProgressLine,
|
||||
FormLabel,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'user',
|
||||
]),
|
||||
subscription() {
|
||||
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': `Total ${item.use} of ${item.total} Used`,
|
||||
'max_team_members': `Total ${item.use} of ${item.total} Members`,
|
||||
},
|
||||
title: {
|
||||
'max_storage_amount': `Storage`,
|
||||
'max_team_members': `Team Members`,
|
||||
}
|
||||
}
|
||||
|
||||
limitations.push({
|
||||
message: payload.message[key],
|
||||
distribution: [
|
||||
{
|
||||
progress: item.percentage,
|
||||
color: payload.color[key],
|
||||
title: payload.title[key],
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
return limitations
|
||||
},
|
||||
status() {
|
||||
return {
|
||||
'active': `Active until ${this.subscription.data.attributes.renews_at}`,
|
||||
'cancelled': `Ends at ${this.subscription.data.attributes.ends_at}`,
|
||||
}[this.subscription.data.attributes.status]
|
||||
},
|
||||
price() {
|
||||
return {
|
||||
'month': `${this.subscription.data.relationships.plan.data.attributes.price} Per Month`,
|
||||
'year': `${this.subscription.data.relationships.plan.data.attributes.price} Per Year`,
|
||||
}[this.subscription.data.relationships.plan.data.attributes.interval]
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div v-if="config.isStripe" 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 block text-sm mb-5">
|
||||
{{ $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="text-xs text-gray-500 pt-3 leading-none sm:block hidden">
|
||||
{{ $t('We are settling your payment automatically via your saved 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="stripeData.storingStripePaymentMethod" type="submit" button-style="theme" class="w-full mt-4">
|
||||
{{ $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="stripeData.isInitialization" class="h-10 relative mb-6">
|
||||
<Spinner />
|
||||
</div>
|
||||
<div id="payment-element">
|
||||
<!-- Elements will create form elements here -->
|
||||
</div>
|
||||
<ButtonBase :loading="stripeData.storingStripePaymentMethod" type="submit" button-style="theme" class="w-full mt-4">
|
||||
{{ $t('Store My Credit Card') }}
|
||||
</ButtonBase>
|
||||
<div id="error-message">
|
||||
<!-- Display error message to your customers here -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import PaymentCard from "./PaymentCard"
|
||||
import Spinner from "../FilesView/Spinner"
|
||||
import {mapGetters} from "vuex";
|
||||
import {events} from "../../bus";
|
||||
import {loadStripe} from "@stripe/stripe-js";
|
||||
import axios from "axios";
|
||||
|
||||
// Define stripe variables
|
||||
let stripe, elements = undefined
|
||||
|
||||
export default {
|
||||
name: 'UserStoredPaymentMethods',
|
||||
components: {
|
||||
ButtonBase,
|
||||
FormLabel,
|
||||
PaymentCard,
|
||||
Spinner
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'isDarkMode',
|
||||
'config',
|
||||
'user',
|
||||
]),
|
||||
hasPaymentMethod() {
|
||||
return this.user.data.relationships.creditCards && this.user.data.relationships.creditCards.data.length > 0
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
|
||||
isCreditCardForm: false,
|
||||
|
||||
stripeData: {
|
||||
isInitialization: true,
|
||||
storingStripePaymentMethod: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async storeStripePaymentMethod() {
|
||||
|
||||
this.stripeData.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('Your credit card was stored successfully'),
|
||||
})
|
||||
|
||||
// TODO: L9 - load credit card after was stored in database
|
||||
}
|
||||
|
||||
this.stripeData.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.stripeData.isInitialization = false
|
||||
},
|
||||
showStoreCreditCardForm() {
|
||||
this.isCreditCardForm = !this.isCreditCardForm
|
||||
this.stripeInit()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="file-text">
|
||||
{{ $t('Transactions') }}
|
||||
</FormLabel>
|
||||
|
||||
<DatatableWrapper
|
||||
api="/api/subscriptions/transactions"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<tr class="border-b dark:border-opacity-5 border-light border-dashed">
|
||||
<td class="py-4">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.note }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<ColorLabel :color="$getTransactionStatusColor(row.data.attributes.status)">
|
||||
{{ row.data.attributes.status }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold" :class="$getTransactionTypeTextColor(row.data.attributes.type)">
|
||||
{{ $getTransactionMark(row.data.attributes.type) + row.data.attributes.price }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.created_at }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<img class="inline-block max-h-5" :src="$getPaymentLogo(row.data.attributes.driver)" :alt="row.data.attributes.driver">
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!--Empty page-->
|
||||
<template v-slot:empty-page>
|
||||
<InfoBox>
|
||||
<p>{{ $t('admin_page_user.invoices.empty') }}</p>
|
||||
</InfoBox>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ColorLabel from "../Others/ColorLabel"
|
||||
import DatatableWrapper from "../Others/Tables/DatatableWrapper"
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
|
||||
export default {
|
||||
name: 'UserTransactionsForFixedBilling',
|
||||
components: {ColorLabel, DatatableWrapper, FormLabel, InfoBox},
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('Note'),
|
||||
field: 'note',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Status'),
|
||||
field: 'status',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_invoices.table.total'),
|
||||
field: 'amount',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Payed At'),
|
||||
field: 'created_at',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Service'),
|
||||
field: 'driver',
|
||||
sortable: true
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="file-text">
|
||||
{{ $t('Transactions') }}
|
||||
</FormLabel>
|
||||
|
||||
<DatatableWrapper
|
||||
api="/api/subscriptions/transactions"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<tr class="border-b dark:border-opacity-5 border-light border-dashed">
|
||||
<td class="py-4">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.note }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<ColorLabel class="capitalize" :color="$getTransactionStatusColor(row.data.attributes.status)">
|
||||
{{ row.data.attributes.status }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="py-4">
|
||||
<span class="text-sm font-bold capitalize">
|
||||
{{ $t(row.data.attributes.type) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold" :class="$getTransactionTypeTextColor(row.data.attributes.type)">
|
||||
{{ $getTransactionMark(row.data.attributes.type) + row.data.attributes.price }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.created_at }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<img class="inline-block max-h-5" :src="$getPaymentLogo(row.data.attributes.driver)" :alt="row.data.attributes.driver">
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!--Empty page-->
|
||||
<template v-slot:empty-page>
|
||||
<InfoBox>
|
||||
<p>{{ $t('admin_page_user.invoices.empty') }}</p>
|
||||
</InfoBox>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ColorLabel from "../Others/ColorLabel"
|
||||
import DatatableWrapper from "../Others/Tables/DatatableWrapper"
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
|
||||
export default {
|
||||
name: 'UserTransactionsForMeteredBilling',
|
||||
components: {
|
||||
ColorLabel,
|
||||
DatatableWrapper,
|
||||
FormLabel,
|
||||
InfoBox
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('Note'),
|
||||
field: 'note',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Status'),
|
||||
field: 'status',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Type'),
|
||||
field: 'status',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_invoices.table.total'),
|
||||
field: 'amount',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Payed At'),
|
||||
field: 'created_at',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Service'),
|
||||
field: 'driver',
|
||||
sortable: true
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div v-if="['paystack', 'paypal'].includes(subscription.attributes.driver)" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Update Payments') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch :title="$t('Update your Payment Method')" :description="$t('You will be redirected to your payment provider to edit your payment method.')" :is-last="true">
|
||||
<ButtonBase @click.native="updatePaymentMethod" :loading="isGeneratedUpdateLink" class="sm:w-auto w-full" button-style="theme">
|
||||
{{ $t('Update Payments') }}
|
||||
</ButtonBase>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AppInputSwitch from "../Admin/AppInputSwitch"
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import axios from "axios";
|
||||
import {events} from "../../bus";
|
||||
|
||||
export default {
|
||||
name: 'UserUpdatePaymentMethodsExternally',
|
||||
components: {
|
||||
AppInputSwitch,
|
||||
ButtonBase,
|
||||
FormLabel
|
||||
},
|
||||
computed: {
|
||||
subscription() {
|
||||
return this.$store.getters.user.data.relationships.subscription.data
|
||||
}
|
||||
},
|
||||
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>
|
||||
54
resources/js/components/Subscription/UserUsageEstimates.vue
Normal file
54
resources/js/components/Subscription/UserUsageEstimates.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="bar-chart">
|
||||
{{ $t('Usage Estimates') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ user.data.meta.usages.costEstimate }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ user.data.relationships.subscription.data.attributes.updated_at }} {{ $t('till now') }}
|
||||
</b>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between py-2 border-b dark:border-opacity-5 border-light border-dashed" v-for="(usage, i) in user.data.meta.usages.featureEstimates" :key="i">
|
||||
<div class="w-2/4 leading-none">
|
||||
<b class="text-sm font-bold leading-none">
|
||||
{{ $t(usage.feature) }}
|
||||
</b>
|
||||
<small class="text-xs text-gray-500 pt-2 leading-none sm:block hidden">
|
||||
{{ $t(`feature_usage_desc_${usage.feature}`) }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="text-left w-1/4">
|
||||
<span class="text-sm font-bold text-gray-560">
|
||||
{{ usage.usage }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right w-1/4">
|
||||
<span class="text-sm font-bold text-theme">
|
||||
{{ usage.cost }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FormLabel from "../Others/Forms/FormLabel"
|
||||
import {mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'UserUsageEstimates',
|
||||
components: {
|
||||
FormLabel
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'user',
|
||||
])
|
||||
},
|
||||
}
|
||||
</script>
|
||||
34
resources/js/routes/routesUser.js
vendored
34
resources/js/routes/routesUser.js
vendored
@@ -39,45 +39,15 @@ const routesUser = [
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Subscription',
|
||||
path: '/user/settings/subscription',
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "chunks/settings-subscription" */ '../views/User/Subscription'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: 'routes_title.subscription'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'MeteredSubscription',
|
||||
name: 'Billing',
|
||||
path: '/user/settings/billing',
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "chunks/settings-subscription" */ '../views/User/MeteredSubscription'),
|
||||
import(/* webpackChunkName: "chunks/billing" */ '../views/User/Billing'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: 'routes_title.subscription'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'PaymentMethods',
|
||||
path: '/user/settings/payment-methods',
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "chunks/settings-payment-methods" */ '../views/User/PaymentMethods'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: 'routes_title.payment_methods'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'CreatePaymentMethod',
|
||||
path: '/user/settings/create-payment-method',
|
||||
component: () =>
|
||||
import(/* webpackChunkName: "chunks/settings-create-payment-methods" */ '../views/User/CreatePaymentMethod'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
title: 'Create Payment Method'
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -138,26 +138,7 @@
|
||||
return this.user.data.attributes.incomplete_payment
|
||||
},
|
||||
pages() {
|
||||
let fixedSubscription = [
|
||||
{
|
||||
title: this.$t('menu.profile'),
|
||||
route: 'Profile',
|
||||
},
|
||||
{
|
||||
title: this.$t('menu.password'),
|
||||
route: 'Password',
|
||||
},
|
||||
{
|
||||
title: this.$t('menu.storage'),
|
||||
route: 'Storage',
|
||||
},
|
||||
{
|
||||
title: this.$t('menu.subscription'),
|
||||
route: 'Subscription',
|
||||
},
|
||||
]
|
||||
|
||||
let meteredSubscription = [
|
||||
return [
|
||||
{
|
||||
title: this.$t('menu.profile'),
|
||||
route: 'Profile',
|
||||
@@ -172,14 +153,9 @@
|
||||
},
|
||||
{
|
||||
title: this.$t('Billing'),
|
||||
route: 'MeteredSubscription',
|
||||
route: 'Billing',
|
||||
},
|
||||
]
|
||||
|
||||
return {
|
||||
fixed: fixedSubscription,
|
||||
metered: meteredSubscription,
|
||||
}[this.config.subscriptionType]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -188,6 +164,9 @@
|
||||
isLoading: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// setTimeout(() => this.$openUpgradeOptions(), 300)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
86
resources/js/views/User/Billing.vue
Normal file
86
resources/js/views/User/Billing.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<PageTab>
|
||||
<!-- Metered subscription components -->
|
||||
<div v-if="config.subscriptionType === 'metered'">
|
||||
<!--Failed Payments-->
|
||||
<UserFailedPayments />
|
||||
|
||||
<!--Balance-->
|
||||
<UserBalance/>
|
||||
|
||||
<!--Usage Estimates-->
|
||||
<UserUsageEstimates />
|
||||
|
||||
<!--Billing Alert-->
|
||||
<UserBillingAlerts />
|
||||
|
||||
<!-- Payment method for automatically handle payments - only for Stripe -->
|
||||
<UserStoredPaymentMethods />
|
||||
|
||||
<!--Transactions-->
|
||||
<UserTransactionsForMeteredBilling />
|
||||
</div>
|
||||
|
||||
<!-- Fixed subscription components -->
|
||||
<div v-if="config.subscriptionType === 'fixed'">
|
||||
|
||||
<!-- Subscription Detail -->
|
||||
<UserFixedSubscriptionDetail />
|
||||
|
||||
<!-- Payment method for automatically handle payments - only for Stripe -->
|
||||
<UserStoredPaymentMethods />
|
||||
|
||||
<!-- Update payment in external source -->
|
||||
<UserUpdatePaymentMethodsExternally />
|
||||
|
||||
<!-- Edit subscription -->
|
||||
<UserEditSubscription />
|
||||
|
||||
<!-- Transactions -->
|
||||
<UserTransactionsForFixedBilling />
|
||||
|
||||
<!-- Empty subscription -->
|
||||
<UserEmptySubscription />
|
||||
</div>
|
||||
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserUpdatePaymentMethodsExternally from "../../components/Subscription/UserUpdatePaymentMethodsExternally"
|
||||
import UserTransactionsForMeteredBilling from "../../components/Subscription/UserTransactionsForMeteredBilling"
|
||||
import UserTransactionsForFixedBilling from "../../components/Subscription/UserTransactionsForFixedBilling"
|
||||
import UserFixedSubscriptionDetail from "../../components/Subscription/UserFixedSubscriptionDetail"
|
||||
import UserStoredPaymentMethods from "../../components/Subscription/UserStoredPaymentMethods"
|
||||
import UserEmptySubscription from "../../components/Subscription/UserEmptySubscription"
|
||||
import UserEditSubscription from "../../components/Subscription/UserEditSubscription"
|
||||
import UserFailedPayments from "../../components/Subscription/UserFailedPayments"
|
||||
import UserUsageEstimates from "../../components/Subscription/UserUsageEstimates"
|
||||
import UserBillingAlerts from "../../components/Subscription/UserBillingAlerts"
|
||||
import PageTab from '/resources/js/components/Others/Layout/PageTab'
|
||||
import UserBalance from "../../components/Subscription/UserBalance"
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Billing',
|
||||
components: {
|
||||
UserUpdatePaymentMethodsExternally,
|
||||
UserTransactionsForMeteredBilling,
|
||||
UserTransactionsForFixedBilling,
|
||||
UserFixedSubscriptionDetail,
|
||||
UserStoredPaymentMethods,
|
||||
UserEmptySubscription,
|
||||
UserEditSubscription,
|
||||
UserFailedPayments,
|
||||
UserUsageEstimates,
|
||||
UserBillingAlerts,
|
||||
UserBalance,
|
||||
PageTab,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'config',
|
||||
]),
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,247 +0,0 @@
|
||||
<template>
|
||||
<PageTab>
|
||||
<PageTabGroup>
|
||||
<div class="form block-form">
|
||||
<FormLabel>{{ $t('user_payments.add_card') }}</FormLabel>
|
||||
|
||||
<!-- Register new credit card -->
|
||||
<div class="register-card">
|
||||
<InfoBox v-if="config.isDemo || config.isDev">
|
||||
<p>For test your payment please use <b class="text-theme">4242 4242 4242 4242</b> or <b class="text-theme">5555 5555 5555 4444</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>.</p>
|
||||
</InfoBox>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<label>{{ $t('user_payments.card_field_title') }}:</label>
|
||||
<div ref="stripeCard" class="stripe-card" :class="{'is-error': isError }">
|
||||
<span class="loading">
|
||||
{{ $t('user_payments.field_loading') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-error-message" v-if="isError">
|
||||
<span>{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-wrapper">
|
||||
<div class="input-wrapper">
|
||||
<div class="inline-wrapper">
|
||||
<div class="switch-label">
|
||||
<label class="input-label">{{ $t('user_add_card.default_title') }}:</label>
|
||||
<small class="input-help">{{ $t('user_add_card.default_description') }}</small>
|
||||
</div>
|
||||
<SwitchInput v-model="defaultPaymentMethod" class="switch" :state="defaultPaymentMethod"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ButtonBase @click.native="registerCard" :loading="isSubmitted" :button-style="isDisabledSubmit ? 'secondary' : 'theme'" type="submit">
|
||||
{{ $t('user_payments.store_card') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</PageTabGroup>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageTabGroup from '/resources/js/components/Others/Layout/PageTabGroup'
|
||||
import SwitchInput from '/resources/js/components/Others/Forms/SwitchInput'
|
||||
import FormLabel from '/resources/js/components/Others/Forms/FormLabel'
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import PageTab from '/resources/js/components/Others/Layout/PageTab'
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
import {mapGetters} from 'vuex'
|
||||
import {events} from '/resources/js/bus'
|
||||
import axios from 'axios'
|
||||
|
||||
let [stripe, card] = [undefined, undefined];
|
||||
|
||||
export default {
|
||||
name: 'CreatePaymentMethod',
|
||||
components: {
|
||||
PageTabGroup,
|
||||
SwitchInput,
|
||||
ButtonBase,
|
||||
FormLabel,
|
||||
PageTab,
|
||||
InfoBox,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['config']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: undefined,
|
||||
isError: false,
|
||||
|
||||
stripeOptions: {
|
||||
hidePostalCode: false
|
||||
},
|
||||
|
||||
isSubmitted: false,
|
||||
isDisabledSubmit: true,
|
||||
|
||||
defaultPaymentMethod: true,
|
||||
clientSecret: undefined,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async registerCard() {
|
||||
|
||||
// Prevent empty submit
|
||||
if (! stripe && !card && ! this.$refs.stripeCard.classList.contains('StripeElement')) return
|
||||
|
||||
// Start loading
|
||||
this.isSubmitted = true
|
||||
|
||||
const {setupIntent, error} = await stripe.confirmCardSetup(this.clientSecret, {
|
||||
payment_method: {
|
||||
card: card,
|
||||
}
|
||||
})
|
||||
|
||||
if (setupIntent) {
|
||||
|
||||
axios
|
||||
.post('/api/user/subscription/payment-cards', {
|
||||
token: setupIntent.payment_method,
|
||||
default: this.defaultPaymentMethod,
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.card_new_add'),
|
||||
})
|
||||
|
||||
// Go to payment methods page
|
||||
this.$router.push({name: 'PaymentMethods'})
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSubmitted = false
|
||||
})
|
||||
}
|
||||
|
||||
if (error) {
|
||||
|
||||
// Set error on
|
||||
this.isError = true
|
||||
|
||||
// End button spinner
|
||||
this.isSubmitted = false
|
||||
|
||||
// Show error message
|
||||
this.errorMessage = error.message
|
||||
}
|
||||
},
|
||||
initStripe() {
|
||||
stripe = Stripe(this.config.stripe_public_key)
|
||||
|
||||
let elements = stripe.elements();
|
||||
|
||||
card = elements.create('card');
|
||||
|
||||
card.mount(this.$refs.stripeCard);
|
||||
|
||||
this.isDisabledSubmit = false
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
let StripeElementsScript = document.createElement('script')
|
||||
|
||||
StripeElementsScript.setAttribute('src', 'https://js.stripe.com/v3/')
|
||||
document.head.appendChild(StripeElementsScript)
|
||||
|
||||
// Get setup intent for stripe
|
||||
axios.get('/api/user/subscription/setup-intent')
|
||||
.then(response => {
|
||||
this.clientSecret = response.data.client_secret
|
||||
|
||||
this.initStripe()
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '/resources/sass/vuefilemanager/_variables';
|
||||
@import '/resources/sass/vuefilemanager/_mixins';
|
||||
@import '/resources/sass/vuefilemanager/_forms';
|
||||
|
||||
.register-card {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.stripe-card {
|
||||
box-sizing: border-box;
|
||||
padding: 13px 20px;
|
||||
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
|
||||
box-shadow: 0 1px 3px 0 #e6ebf1;
|
||||
-webkit-transition: box-shadow 150ms ease;
|
||||
transition: box-shadow 150ms ease;
|
||||
|
||||
|
||||
&:not(.StripeElement) {
|
||||
background: $light_background;
|
||||
padding: 14px 20px;
|
||||
|
||||
.loading {
|
||||
@include font-size(14);
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-error {
|
||||
box-shadow: 0 0 7px rgba($danger, 0.3);
|
||||
border: 2px solid $danger;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&.StripeElement--focus {
|
||||
box-shadow: 0 1px 3px 0 #cfd7df;
|
||||
}
|
||||
|
||||
&.StripeElement--invalid {
|
||||
border-color: #fa755a;
|
||||
}
|
||||
|
||||
&.StripeElement--webkit-autofill {
|
||||
background-color: #fefde5 !important;
|
||||
}
|
||||
|
||||
iframe .InputContainer .InputElement {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.card-error-message {
|
||||
padding-top: 10px;
|
||||
|
||||
span, a {
|
||||
@include font-size(14);
|
||||
font-weight: 600;
|
||||
color: $danger;
|
||||
}
|
||||
|
||||
.link, a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,611 +0,0 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
|
||||
<!--Failed Payments-->
|
||||
<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="text-3xl text-red font-extrabold -mt-3 block mb-0.5">
|
||||
-{{ user.data.meta.totalDebt }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ $t('We are unable to charge your usage for items below') }}
|
||||
</b>
|
||||
|
||||
<!--Failed Payments-->
|
||||
<div
|
||||
v-for="payment in user.data.relationships.failedPayments.data"
|
||||
:key="payment.data.id"
|
||||
class="flex items-center justify-between py-2 border-b dark:border-opacity-5 border-light border-dashed"
|
||||
>
|
||||
<div class="w-2/4 leading-none">
|
||||
<b class="text-sm font-bold leading-none">
|
||||
{{ payment.data.attributes.note }}
|
||||
</b>
|
||||
</div>
|
||||
<div class="text-left w-1/4">
|
||||
<span class="text-sm font-bold text-gray-560 capitalize">
|
||||
{{ $t(payment.data.attributes.source) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right w-1/4">
|
||||
<span class="text-sm font-bold">
|
||||
{{ payment.data.attributes.created_at }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right w-1/4">
|
||||
<span class="text-sm font-bold text-theme">
|
||||
{{ payment.data.attributes.amount }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InfoBox type="error" class="mt-7" style="margin-bottom: 0">
|
||||
<p v-html="$t('Uh-oh! We are unable to charge your usage. Please register new credit card or fund your account with sufficient amount and we\'ll give it another try!')"></p>
|
||||
</InfoBox>
|
||||
</div>
|
||||
|
||||
<!-- Balance -->
|
||||
<div v-if="! hasPaymentMethod" class="card shadow-card">
|
||||
<FormLabel icon="dollar">
|
||||
{{ $t('Balance') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ 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="Capacity" rules="required">
|
||||
<AppInputText :description="$t('The amount will be increased as soon as we register your charge from payment gateway.')" :error="errors[0]" :is-last="true">
|
||||
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
|
||||
<input v-model="chargeAmount"
|
||||
:placeholder="$t('Fund Your Account Balance...')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{'border-red-700': errors[0]}"
|
||||
/>
|
||||
<ButtonBase type="submit" button-style="theme" class="sm:w-auto w-full">
|
||||
{{ $t('Make a Payment') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
|
||||
<!--Usage Estimates-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="bar-chart">
|
||||
{{ $t('Usage Estimates') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ user.data.meta.usages.costEstimate }}
|
||||
</b>
|
||||
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-5">
|
||||
{{ user.data.relationships.subscription.data.attributes.updated_at }} {{ $t('till now') }}
|
||||
</b>
|
||||
|
||||
<div>
|
||||
<div class="flex items-center justify-between py-2 border-b dark:border-opacity-5 border-light border-dashed" v-for="(usage, i) in user.data.meta.usages.featureEstimates" :key="i">
|
||||
<div class="w-2/4 leading-none">
|
||||
<b class="text-sm font-bold leading-none">
|
||||
{{ $t(usage.feature) }}
|
||||
</b>
|
||||
<small class="text-xs text-gray-500 pt-2 leading-none sm:block hidden">
|
||||
{{ $t(`feature_usage_desc_${usage.feature}`) }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="text-left w-1/4">
|
||||
<span class="text-sm font-bold text-gray-560">
|
||||
{{ usage.usage }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right w-1/4">
|
||||
<span class="text-sm font-bold text-theme">
|
||||
{{ usage.cost }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Billing Alert-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="bell">
|
||||
{{ $t('Billing Alert') }}
|
||||
</FormLabel>
|
||||
|
||||
<div v-if="user.data.relationships.alert">
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5 flex items-center">
|
||||
{{ user.data.relationships.alert.data.attributes.formatted }}
|
||||
<edit2-icon v-if="! showUpdateBillingAlertForm" @click="showUpdateBillingAlertForm = ! showUpdateBillingAlertForm" size="12" class="vue-feather cursor-pointer ml-2 transform -translate-y-0.5" />
|
||||
<trash2-icon v-if="showUpdateBillingAlertForm" @click="deleteBillingAlert" size="12" class="vue-feather cursor-pointer ml-2 transform -translate-y-0.5" />
|
||||
</b>
|
||||
|
||||
<b class="block text-sm text-gray-400">
|
||||
{{ $t('Alert will be triggered after you reach the value above.') }}
|
||||
</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('You will receive an email whenever your monthly balance reaches the specified amount above.')" :error="errors[0]" :is-last="true">
|
||||
<div class="sm:flex sm:space-x-4 sm:space-y-0 space-y-4">
|
||||
<input v-model="billingAlertAmount"
|
||||
:placeholder="$t('Alert Amount...')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{'border-red-700': errors[0]}"
|
||||
/>
|
||||
<ButtonBase :loadint="isSendingBillingAlert" :disabled="isSendingBillingAlert" type="submit" button-style="theme" class="sm:w-auto w-full">
|
||||
{{ $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('You will receive an email whenever your monthly balance reaches the specified amount above.')" :error="errors[0]" :is-last="true">
|
||||
<div class="flex space-x-4">
|
||||
<input v-model="billingAlertAmount"
|
||||
:placeholder="$t('Alert Amount...')"
|
||||
type="number"
|
||||
min="1"
|
||||
max="999999999"
|
||||
class="focus-border-theme input-dark"
|
||||
:class="{'border-red-700': errors[0]}"
|
||||
/>
|
||||
<ButtonBase :loadint="isSendingBillingAlert" :disabled="isSendingBillingAlert" type="submit" button-style="theme" class="submit-button">
|
||||
{{ $t('Set Alert') }}
|
||||
</ButtonBase>
|
||||
</div>
|
||||
</AppInputText>
|
||||
</ValidationProvider>
|
||||
</ValidationObserver>
|
||||
</div>
|
||||
|
||||
<!-- Payment method for automatically handle payments - only for Stripe -->
|
||||
<div v-if="config.isStripe" class="card shadow-card">
|
||||
<FormLabel icon="credit-card">
|
||||
{{ $t('Payment Method') }}
|
||||
</FormLabel>
|
||||
|
||||
<!-- User doesn't have registered payment method -->
|
||||
<div v-if="! hasPaymentMethod">
|
||||
<!-- Show credit card form -->
|
||||
<ButtonBase @click.native="showStoreCreditCardForm" v-if="! isCreditCardForm" :loading="stripeData.storingStripePaymentMethod" type="submit" button-style="theme" class="w-full mt-4">
|
||||
{{ $t('Add Auto Payment Method') }}
|
||||
</ButtonBase>
|
||||
|
||||
<!-- Store credit card form -->
|
||||
<form v-if="isCreditCardForm" @submit.prevent="storeStripePaymentMethod" id="payment-form" class="mt-6">
|
||||
<div v-if="stripeData.isInitialization" class="h-10 relative mb-6">
|
||||
<Spinner />
|
||||
</div>
|
||||
<div id="payment-element">
|
||||
<!-- Elements will create form elements here -->
|
||||
</div>
|
||||
<ButtonBase :loading="stripeData.storingStripePaymentMethod" type="submit" button-style="theme" class="w-full mt-4">
|
||||
{{ $t('Store My Credit Card') }}
|
||||
</ButtonBase>
|
||||
<div id="error-message">
|
||||
<!-- Display error message to your customers here -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- User has registered payment method -->
|
||||
<div v-if="hasPaymentMethod">
|
||||
<b v-if="user.data.relationships.balance.data.attributes.balance > 0" class="mb-3 block text-sm mb-5">
|
||||
{{ $t('credit_to_auto_withdraw', {credit: user.data.relationships.balance.data.attributes.formatted}) }}
|
||||
</b>
|
||||
|
||||
<!-- Card -->
|
||||
<div
|
||||
v-for="card in user.data.relationships.creditCards.data"
|
||||
:key="card.data.id"
|
||||
class="flex items-center justify-between py-3 px-4 input-dark"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<img :src="`/assets/gateways/${card.data.attributes.brand}.svg`" alt="" class="h-5 mr-3 rounded">
|
||||
<b class="text-sm font-bold leading-none capitalize">
|
||||
{{ 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>
|
||||
|
||||
<small class="text-xs text-gray-500 pt-3 leading-none sm:block hidden">
|
||||
{{ $t('We are settling your payment automatically via your saved credit card.') }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Transactions-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="file-text">
|
||||
{{ $t('Transactions') }}
|
||||
</FormLabel>
|
||||
|
||||
<DatatableWrapper
|
||||
@init="isLoading = false"
|
||||
api="/api/subscriptions/transactions"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<tr class="border-b dark:border-opacity-5 border-light border-dashed">
|
||||
<td class="py-4">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.note }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<ColorLabel class="capitalize" :color="$getTransactionStatusColor(row.data.attributes.status)">
|
||||
{{ row.data.attributes.status }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td class="py-4">
|
||||
<span class="text-sm font-bold capitalize">
|
||||
{{ $t(row.data.attributes.type) }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold" :class="$getTransactionTypeTextColor(row.data.attributes.type)">
|
||||
{{ $getTransactionMark(row.data.attributes.type) + row.data.attributes.price }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.created_at }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<img class="inline-block max-h-5" :src="$getPaymentLogo(row.data.attributes.driver)" :alt="row.data.attributes.driver">
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!--Empty page-->
|
||||
<template v-slot:empty-page>
|
||||
<InfoBox>
|
||||
<p>{{ $t('admin_page_user.invoices.empty') }}</p>
|
||||
</InfoBox>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
CreditCardIcon,
|
||||
XIcon,
|
||||
Trash2Icon,
|
||||
Edit2Icon,
|
||||
} from "vue-feather-icons";
|
||||
import Spinner from "../../components/FilesView/Spinner";
|
||||
import ColorLabel from "../../components/Others/ColorLabel";
|
||||
import DatatableWrapper from "../../components/Others/Tables/DatatableWrapper";
|
||||
import AppInputText from "../../components/Admin/AppInputText";
|
||||
import {ValidationProvider, ValidationObserver} from 'vee-validate/dist/vee-validate.full'
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import PageTab from '/resources/js/components/Others/Layout/PageTab'
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
import AppInputSwitch from "../../components/Admin/AppInputSwitch"
|
||||
import FormLabel from "../../components/Others/Forms/FormLabel"
|
||||
import ProgressLine from "../../components/Admin/ProgressLine"
|
||||
import {mapGetters} from 'vuex'
|
||||
import axios from 'axios'
|
||||
import {events} from "../../bus"
|
||||
import {loadStripe} from '@stripe/stripe-js'
|
||||
|
||||
// Define stripe variables
|
||||
let stripe, elements = undefined
|
||||
|
||||
export default {
|
||||
name: 'MeteredSubscription',
|
||||
components: {
|
||||
CreditCardIcon,
|
||||
DatatableWrapper,
|
||||
ValidationProvider,
|
||||
ValidationObserver,
|
||||
AppInputSwitch,
|
||||
AppInputText,
|
||||
ProgressLine,
|
||||
ButtonBase,
|
||||
ColorLabel,
|
||||
Trash2Icon,
|
||||
Edit2Icon,
|
||||
XIcon,
|
||||
FormLabel,
|
||||
InfoBox,
|
||||
PageTab,
|
||||
Spinner,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'isDarkMode',
|
||||
'config',
|
||||
'user',
|
||||
]),
|
||||
hasPaymentMethod() {
|
||||
return this.user.data.relationships.creditCards && this.user.data.relationships.creditCards.data.length > 0
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isLoading: false,
|
||||
|
||||
isSendingBillingAlert: false,
|
||||
billingAlertAmount: undefined,
|
||||
showUpdateBillingAlertForm: false,
|
||||
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('Note'),
|
||||
field: 'note',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Status'),
|
||||
field: 'status',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Type'),
|
||||
field: 'status',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_invoices.table.total'),
|
||||
field: 'amount',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Payed At'),
|
||||
field: 'created_at',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Service'),
|
||||
field: 'driver',
|
||||
sortable: true
|
||||
},
|
||||
],
|
||||
|
||||
chargeAmount: undefined,
|
||||
isCreditCardForm: false,
|
||||
|
||||
stripeData: {
|
||||
isInitialization: true,
|
||||
storingStripePaymentMethod: 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('Your billing alert was updated successfully'),
|
||||
})
|
||||
})
|
||||
.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('Your billing alert was set successfully'),
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('toaster', {
|
||||
type: 'danger',
|
||||
message: this.$t('popup_error.title'),
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.isSendingBillingAlert = false
|
||||
})
|
||||
},
|
||||
async makePayment() {
|
||||
// Validate fields
|
||||
const isValid = await this.$refs.fundAccount.validate();
|
||||
|
||||
if (!isValid) return;
|
||||
|
||||
// Show payment methods popup
|
||||
this.$store.dispatch('callSingleChargeProcess', this.chargeAmount)
|
||||
},
|
||||
async storeStripePaymentMethod() {
|
||||
|
||||
this.stripeData.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('Your credit card was stored successfully'),
|
||||
})
|
||||
|
||||
// TODO: L9 - load credit card after was stored in database
|
||||
}
|
||||
|
||||
this.stripeData.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.stripeData.isInitialization = false
|
||||
},
|
||||
deleteBillingAlert() {
|
||||
events.$emit('confirm:open', {
|
||||
title: this.$t('Are you sure you want to delete your alert?'),
|
||||
message: this.$t('You will no longer receive any notifications that your billing limit has been exceeded.'),
|
||||
action: {
|
||||
id: this.user.data.relationships.alert.data.id,
|
||||
operation: 'delete-billing-alert',
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteCreditCard(id) {
|
||||
events.$emit('confirm:open', {
|
||||
title: this.$t('Are you sure you want to delete your credit card?'),
|
||||
message: this.$t('We will no longer settle your payments automatically and you will have to fund your account for the next payments.'),
|
||||
action: {
|
||||
id: id,
|
||||
operation: 'delete-credit-card',
|
||||
}
|
||||
})
|
||||
},
|
||||
showStoreCreditCardForm() {
|
||||
this.isCreditCardForm = !this.isCreditCardForm
|
||||
this.stripeInit()
|
||||
}
|
||||
},
|
||||
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('Your billing alert was deleted.'),
|
||||
})
|
||||
})
|
||||
.catch(() => this.$isSomethingWrong())
|
||||
|
||||
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('Your credit card was deleted.'),
|
||||
})
|
||||
})
|
||||
.catch(() => this.$isSomethingWrong())
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,276 +0,0 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<PageTabGroup>
|
||||
|
||||
<!--Page title-->
|
||||
<FormLabel>{{ $t('user_payments.title') }}</FormLabel>
|
||||
|
||||
<!--Add payment method button-->
|
||||
<div class="page-actions" v-if="PaymentMethods && PaymentMethods.length > 0">
|
||||
<router-link :to="{name: 'CreatePaymentMethod'}">
|
||||
<MobileActionButton icon="credit-card">
|
||||
{{ $t('user_payments.add_card') }}
|
||||
</MobileActionButton>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!--Payment methods table-->
|
||||
<DatatableWrapper v-if="PaymentMethods" :table-data="{data: PaymentMethods}" :paginator="false" :columns="columns" class="table">
|
||||
|
||||
<!--Table data content-->
|
||||
<template slot-scope="{ row }">
|
||||
<tr :class="{'is-deleting': row.data.attributes.card_id === deletingID}">
|
||||
<td style="width: 300px">
|
||||
<span class="cell-item">
|
||||
<div class="credit-card">
|
||||
<img class="credit-card-icon" :src="$getCreditCardBrand(row.data.attributes.brand)"
|
||||
:alt="row.data.attributes.brand">
|
||||
<div class="credit-card-numbers">
|
||||
•••• {{ row.data.attributes.last4 }}
|
||||
</div>
|
||||
<ColorLabel v-if="row.data.id === defaultPaymentCard.data.id" color="purple">{{ $t('global.default') }}</ColorLabel>
|
||||
</div>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="cell-item">
|
||||
{{ row.data.attributes.exp_month }} / {{ row.data.attributes.exp_year }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-icons">
|
||||
<label class="icon-wrapper" :title="$t('user_payments.set_as_default')">
|
||||
<credit-card-icon size="15" class="icon icon-card" @click="setDefaultCard(row.data.attributes)" v-if="row.data.id !== defaultPaymentCard.data.id"></credit-card-icon>
|
||||
</label>
|
||||
<label class="icon-wrapper" :title="$t('user_payments.delete_card')">
|
||||
<trash2-icon size="15" class="icon icon-trash" @click="deleteCard(row.data.attributes)"></trash2-icon>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!--Empty page-->
|
||||
<template v-slot:empty-page>
|
||||
<InfoBox>
|
||||
<p>{{ $t('user_payments.empty') }} <router-link v-if="user.data.attributes.stripe_customer" :to="{name: 'CreatePaymentMethod'}" class="text-theme">Add new payment method.</router-link> </p>
|
||||
</InfoBox>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</PageTabGroup>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MobileActionButton from '/resources/js/components/FilesView/MobileActionButton'
|
||||
import DatatableWrapper from '/resources/js/components/Others/Tables/DatatableWrapper'
|
||||
import PageTabGroup from '/resources/js/components/Others/Layout/PageTabGroup'
|
||||
import {CreditCardIcon, Trash2Icon} from "vue-feather-icons"
|
||||
import FormLabel from '/resources/js/components/Others/Forms/FormLabel'
|
||||
import PageTab from '/resources/js/components/Others/Layout/PageTab'
|
||||
import ColorLabel from '/resources/js/components/Others/ColorLabel'
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
import { mapGetters } from 'vuex'
|
||||
import {events} from '/resources/js/bus'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'UserPaymentMethods',
|
||||
components: {
|
||||
MobileActionButton,
|
||||
DatatableWrapper,
|
||||
CreditCardIcon,
|
||||
PageTabGroup,
|
||||
Trash2Icon,
|
||||
ColorLabel,
|
||||
FormLabel,
|
||||
InfoBox,
|
||||
PageTab,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultPaymentCard: undefined,
|
||||
PaymentMethods: undefined,
|
||||
deletingID: undefined,
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('rows.card.number'),
|
||||
field: 'data.attributes.total',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
label: this.$t('rows.card.expiration'),
|
||||
field: 'data.attributes.total',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_user.table.action'),
|
||||
field: 'data.action',
|
||||
sortable: false
|
||||
},
|
||||
],
|
||||
isLoading: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getCardStatusColor(status) {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'green'
|
||||
break
|
||||
case 'card_declined':
|
||||
return 'yellow'
|
||||
break
|
||||
case 'expired':
|
||||
return 'red'
|
||||
break
|
||||
}
|
||||
},
|
||||
setDefaultCard(card) {
|
||||
events.$emit('confirm:open', {
|
||||
title: this.$t('popup_set_card.title'),
|
||||
message: this.$t('popup_set_card.message'),
|
||||
buttonColor: 'theme-solid',
|
||||
action: {
|
||||
id: card.card_id,
|
||||
operation: 'set-as-default-credit-card'
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteCard(card) {
|
||||
events.$emit('confirm:open', {
|
||||
title: this.$t('popup_delete_card.title'),
|
||||
message: this.$t('popup_delete_card.message'),
|
||||
action: {
|
||||
id: card.card_id,
|
||||
operation: 'delete-credit-card'
|
||||
}
|
||||
})
|
||||
},
|
||||
fetchPaymentMethods() {
|
||||
axios.get('/api/user/subscription/payment-cards')
|
||||
.then(response => {
|
||||
|
||||
if (response.status == 204) {
|
||||
this.PaymentMethods = {}
|
||||
}
|
||||
|
||||
if (response.status == 200) {
|
||||
this.defaultPaymentCard = response.data.default
|
||||
|
||||
this.PaymentMethods = response.data.others.data
|
||||
this.PaymentMethods.push(response.data.default)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.isLoading = false
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
// Get payments card
|
||||
this.fetchPaymentMethods()
|
||||
|
||||
// Delete credit card
|
||||
events.$on('action:confirmed', data => {
|
||||
|
||||
if (data.operation === 'delete-credit-card') {
|
||||
|
||||
this.deletingID = data.id
|
||||
|
||||
axios.post('/api/user/subscription/payment-cards/' + data.id, {
|
||||
_method: 'delete'
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
// Get payments card
|
||||
this.fetchPaymentMethods()
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.card_deleted'),
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (data.operation === 'set-as-default-credit-card') {
|
||||
|
||||
axios.post('/api/user/subscription/payment-cards/' + data.id, {
|
||||
default: 1,
|
||||
_method: 'patch'
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
// Get payments card
|
||||
this.fetchPaymentMethods()
|
||||
|
||||
// Show toaster
|
||||
events.$emit('toaster', {
|
||||
type: 'success',
|
||||
message: this.$t('toaster.card_set'),
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
events.$emit('alert:open', {
|
||||
title: this.$t('popup_error.title'),
|
||||
message: this.$t('popup_error.message'),
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
events.$off('action:confirmed')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '/resources/sass/vuefilemanager/_variables';
|
||||
@import '/resources/sass/vuefilemanager/_mixins';
|
||||
@import '/resources/sass/vuefilemanager/_forms';
|
||||
|
||||
.is-deleting {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.credit-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.credit-card-numbers {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.credit-card-icon {
|
||||
max-height: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
margin-top: 45px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
|
||||
}
|
||||
|
||||
.dark {
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,299 +0,0 @@
|
||||
<template>
|
||||
<PageTab :is-loading="isLoading">
|
||||
<div v-if="subscription" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Subscription') }}
|
||||
</FormLabel>
|
||||
|
||||
<b class="text-3xl font-extrabold -mt-3 block mb-0.5">
|
||||
{{ status }}
|
||||
</b>
|
||||
<b class="mb-3 block text-sm text-gray-400 mb-8">
|
||||
{{ subscription.relationships.plan.data.attributes.name }} / {{ price }}
|
||||
</b>
|
||||
|
||||
<div v-for="(limit, i) in limitations" :key="i" :class="{'mb-6': (Object.keys(limitations).length - 1) !== i}">
|
||||
<b class="mb-3 block text-sm text-gray-400">
|
||||
{{ limit.message }}
|
||||
</b>
|
||||
<ProgressLine :data="limit.distribution" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div v-if="subscription && ['paystack', 'paypal'].includes(subscription.attributes.driver)" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Update Payments') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch :title="$t('Update your Payment Method')" :description="$t('You will be redirected to your payment provider to edit your payment method.')" :is-last="true">
|
||||
<ButtonBase @click.native="updatePaymentMethod" :loading="isGeneratedUpdateLink" class="popup-button" button-style="theme">
|
||||
{{ $t('Update Payments') }}
|
||||
</ButtonBase>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<div v-if="subscription" class="card shadow-card">
|
||||
<FormLabel>
|
||||
{{ $t('Edit your Subscription') }}
|
||||
</FormLabel>
|
||||
|
||||
<AppInputSwitch :title="$t('Cancel Subscription')" :description="$t('You can cancel your subscription now. You\'ll continue to have access to the features you\'ve paid for until the end of your billing cycle.')">
|
||||
<ButtonBase @click.native="cancelSubscriptionConfirmation" :loading="isCancelling" class="popup-button" button-style="secondary">
|
||||
{{ $t('Cancel Now') }}
|
||||
</ButtonBase>
|
||||
</AppInputSwitch>
|
||||
|
||||
<AppInputSwitch :title="$t('Upgrade Plan')" :description="$t('You can upgrade your plan at any time you want.')" :is-last="true">
|
||||
<ButtonBase @click.native="$openUpgradeOptions" class="popup-button" button-style="secondary">
|
||||
{{ $t('Upgrade Now') }}
|
||||
</ButtonBase>
|
||||
</AppInputSwitch>
|
||||
</div>
|
||||
|
||||
<!--Transactions-->
|
||||
<div class="card shadow-card">
|
||||
<FormLabel icon="file-text">
|
||||
{{ $t('Transactions') }}
|
||||
</FormLabel>
|
||||
|
||||
<DatatableWrapper
|
||||
@init="isLoading = false"
|
||||
api="/api/subscriptions/transactions"
|
||||
:paginator="true"
|
||||
:columns="columns"
|
||||
>
|
||||
<template slot-scope="{ row }">
|
||||
<tr class="border-b dark:border-opacity-5 border-light border-dashed">
|
||||
<td class="py-4">
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.note }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<ColorLabel :color="$getTransactionStatusColor(row.data.attributes.status)">
|
||||
{{ row.data.attributes.status }}
|
||||
</ColorLabel>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold" :class="$getTransactionTypeTextColor(row.data.attributes.type)">
|
||||
{{ $getTransactionMark(row.data.attributes.type) + row.data.attributes.price }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm font-bold">
|
||||
{{ row.data.attributes.created_at }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<img class="inline-block max-h-5" :src="$getPaymentLogo(row.data.attributes.driver)" :alt="row.data.attributes.driver">
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<!--Empty page-->
|
||||
<template v-slot:empty-page>
|
||||
<InfoBox>
|
||||
<p>{{ $t('admin_page_user.invoices.empty') }}</p>
|
||||
</InfoBox>
|
||||
</template>
|
||||
</DatatableWrapper>
|
||||
</div>
|
||||
|
||||
<div v-if="! subscription && !isLoading" class="card shadow-card">
|
||||
<InfoBox style="margin-bottom: 0">
|
||||
<p>{{ $t("You don't have any subscription") }}</p>
|
||||
</InfoBox>
|
||||
</div>
|
||||
</PageTab>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DatatableWrapper from "../../components/Others/Tables/DatatableWrapper";
|
||||
import ColorLabel from "../../components/Others/ColorLabel";
|
||||
import ButtonBase from '/resources/js/components/FilesView/ButtonBase'
|
||||
import PageTab from '/resources/js/components/Others/Layout/PageTab'
|
||||
import InfoBox from '/resources/js/components/Others/Forms/InfoBox'
|
||||
import AppInputSwitch from "../../components/Admin/AppInputSwitch"
|
||||
import FormLabel from "../../components/Others/Forms/FormLabel"
|
||||
import ProgressLine from "../../components/Admin/ProgressLine"
|
||||
import {events} from '/resources/js/bus'
|
||||
import {mapGetters} from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'UserSubscription',
|
||||
components: {
|
||||
DatatableWrapper,
|
||||
ColorLabel,
|
||||
AppInputSwitch,
|
||||
ProgressLine,
|
||||
ButtonBase,
|
||||
FormLabel,
|
||||
InfoBox,
|
||||
PageTab,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'user',
|
||||
]),
|
||||
status() {
|
||||
return {
|
||||
'active': `Active until ${this.subscription.attributes.renews_at}`,
|
||||
'cancelled': `Ends at ${this.subscription.attributes.ends_at}`,
|
||||
}[this.subscription.attributes.status]
|
||||
},
|
||||
price() {
|
||||
return {
|
||||
'month': `${this.subscription.relationships.plan.data.attributes.price} Per Month`,
|
||||
'year': `${this.subscription.relationships.plan.data.attributes.price} Per Year`,
|
||||
}[this.subscription.relationships.plan.data.attributes.interval]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isGeneratedUpdateLink: false,
|
||||
subscription: undefined,
|
||||
isCancelling: false,
|
||||
limitations: [],
|
||||
isLoading: true,
|
||||
columns: [
|
||||
{
|
||||
label: this.$t('Note'),
|
||||
field: 'note',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Status'),
|
||||
field: 'status',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('admin_page_invoices.table.total'),
|
||||
field: 'amount',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Payed At'),
|
||||
field: 'created_at',
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
label: this.$t('Service'),
|
||||
field: 'driver',
|
||||
sortable: true
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
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
|
||||
})
|
||||
},
|
||||
cancelSubscriptionConfirmation() {
|
||||
events.$emit('confirm:open', {
|
||||
title: this.$t('Are you sure you want to cancel subscription?'),
|
||||
message: this.$t("You'll continue to have access to the features you've paid for until the end of your billing cycle."),
|
||||
action: {
|
||||
operation: 'cancel-subscription',
|
||||
}
|
||||
})
|
||||
},
|
||||
cancelSubscription() {
|
||||
|
||||
// Start deleting spinner button
|
||||
this.isCancelling = true
|
||||
|
||||
// Send post request
|
||||
axios
|
||||
.post('/api/subscriptions/cancel')
|
||||
.then(() => {
|
||||
|
||||
// Update user data
|
||||
this.$store.dispatch('getAppData').then(() => {
|
||||
this.fetchSubscriptionDetail()
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
},
|
||||
fetchSubscriptionDetail() {
|
||||
axios.get('/api/subscriptions/detail')
|
||||
.then(response => {
|
||||
|
||||
this.subscription = response.data.data
|
||||
|
||||
Object
|
||||
.entries(this.user.data.meta.limitations)
|
||||
.map(([key, item]) => {
|
||||
|
||||
let payload = {
|
||||
color: {
|
||||
'max_storage_amount': 'warning',
|
||||
'max_team_members': 'purple',
|
||||
},
|
||||
message: {
|
||||
'max_storage_amount': `Total ${item.use} of ${item.total} Used`,
|
||||
'max_team_members': `Total ${item.use} of ${item.total} Members`,
|
||||
},
|
||||
title: {
|
||||
'max_storage_amount': `Storage`,
|
||||
'max_team_members': `Team Members`,
|
||||
}
|
||||
}
|
||||
|
||||
this.limitations.push({
|
||||
message: payload.message[key],
|
||||
distribution: [
|
||||
{
|
||||
progress: item.percentage,
|
||||
color: payload.color[key],
|
||||
title: payload.title[key],
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
this.isLoading = false
|
||||
})
|
||||
.finally(() => {
|
||||
this.isLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchSubscriptionDetail()
|
||||
|
||||
events.$on('action:confirmed', data => {
|
||||
if (data.operation === 'cancel-subscription')
|
||||
this.cancelSubscription()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -43,6 +43,7 @@ class UserResource extends JsonResource
|
||||
'relationships' => [
|
||||
'settings' => new SettingsResource($this->settings),
|
||||
'favourites' => new FolderCollection($this->favouriteFolders),
|
||||
'creditCards' => new CreditCardCollection($this->creditCards),
|
||||
$this->mergeWhen($this->hasSubscription(), fn () => [
|
||||
'subscription' => new SubscriptionResource($this->subscription),
|
||||
]),
|
||||
@@ -52,9 +53,6 @@ class UserResource extends JsonResource
|
||||
$this->mergeWhen($isMeteredSubscription, fn () => [
|
||||
'alert' => new BillingAlertResource($this->billingAlert),
|
||||
]),
|
||||
$this->mergeWhen($isMeteredSubscription, fn () => [
|
||||
'creditCards' => new CreditCardCollection($this->creditCards),
|
||||
]),
|
||||
$this->mergeWhen($isMeteredSubscription, fn () => [
|
||||
'failedPayments' => new FailedPaymentsCollection($this->failedPayments),
|
||||
]),
|
||||
|
||||
Reference in New Issue
Block a user