Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117747257
D2734.1775175243.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
154 KB
Referenced Files
None
Subscribers
None
D2734.1775175243.diff
View Options
diff --git a/src/package-lock.json b/src/package-lock.json
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -3550,6 +3550,12 @@
"fastq": "^1.6.0"
}
},
+ "@popperjs/core": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz",
+ "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==",
+ "dev": true
+ },
"@stylelint/postcss-css-in-js": {
"version": "0.37.2",
"resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz",
@@ -4555,9 +4561,9 @@
"dev": true
},
"bootstrap": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz",
- "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==",
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.0.2.tgz",
+ "integrity": "sha512-1Ge963tyEQWJJ+8qtXFU6wgmAVj9gweEjibUdbmcCEYsn38tVwRk8107rk2vzt6cfQcRr3SlZ8aQBqaD8aqf+Q==",
"dev": true
},
"brace-expansion": {
@@ -4789,6 +4795,12 @@
"integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==",
"dev": true
},
+ "cash-dom": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/cash-dom/-/cash-dom-8.1.0.tgz",
+ "integrity": "sha512-QTa50rFuPaX8klEDEbwLr+jVutwpvZEBQ0NpMMyng+je7gNe9Bz/JsOLHIG24tvNSSSIN/Q1QD0bnF6PQzWKHA==",
+ "dev": true
+ },
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
@@ -7662,12 +7674,6 @@
}
}
},
- "jquery": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
- "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
- "dev": true
- },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -9280,12 +9286,6 @@
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
"dev": true
},
- "popper.js": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
- "integrity": "sha1-KiI8s9x7YhPXQOQDcr5A3kPmWxs=",
- "dev": true
- },
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
diff --git a/src/package.json b/src/package.json
--- a/src/package.json
+++ b/src/package.json
@@ -17,17 +17,17 @@
"@fortawesome/free-regular-svg-icons": "^5.15.3",
"@fortawesome/free-solid-svg-icons": "^5.15.3",
"@fortawesome/vue-fontawesome": "^0.1.10",
+ "@popperjs/core": "^2.9.2",
"anchorme": "^2.1.2",
"axios": "^0.21.1",
- "bootstrap": "^4.6.0",
+ "bootstrap": "^5.0.0",
+ "cash-dom": "^8.1.0",
"cross-env": "^7.0.3",
"eslint": "^7.26.0",
"eslint-plugin-vue": "^7.9.0",
"frappe-charts": "^1.5.8",
- "jquery": "^3.6.0",
"laravel-mix": "^6.0.27",
"openvidu-browser": "^2.18.0",
- "popper.js": "^1.16.0",
"postcss": "^8.3.6",
"resolve-url-loader": "^4.0.0",
"sass": "^1.32.8",
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -10,9 +10,10 @@
import MenuComponent from '../vue/Widgets/Menu'
import SupportForm from '../vue/Widgets/SupportForm'
import store from './store'
+import { Tab } from 'bootstrap'
import { loadLangAsync, i18n } from './locale'
-const loader = '<div class="app-loader"><div class="spinner-border" role="status"><span class="sr-only">Loading</span></div></div>'
+const loader = '<div class="app-loader"><div class="spinner-border" role="status"><span class="visually-hidden">Loading</span></div></div>'
let isLoading = 0
@@ -177,8 +178,14 @@
return `<img src="${src}" alt="${this.appName}">`
},
// Display "loading" overlay inside of the specified element
- addLoader(elem, small = true) {
- $(elem).css({position: 'relative'}).append(small ? $(loader).addClass('small') : $(loader))
+ addLoader(elem, small = true, style = null) {
+ if (style) {
+ $(elem).css(style)
+ } else {
+ $(elem).css('position', 'relative')
+ }
+
+ $(elem).append(small ? $(loader).addClass('small') : $(loader))
},
// Remove loader element added in addLoader()
removeLoader(elem) {
@@ -191,7 +198,7 @@
},
tab(e) {
e.preventDefault()
- $(e.target).tab('show')
+ new Tab(e.target).show()
},
errorPage(code, msg, hint) {
// Until https://github.com/vuejs/vue-router/issues/977 is implemented
@@ -267,10 +274,7 @@
},
clickRecord(event) {
if (!/^(a|button|svg|path)$/i.test(event.target.nodeName)) {
- let link = $(event.target).closest('tr').find('a')[0]
- if (link) {
- link.click()
- }
+ $(event.target).closest('tr').find('a').trigger('click')
}
},
domainStatusClass(domain) {
@@ -350,21 +354,19 @@
return page ? page : '404'
},
supportDialog(container) {
- let dialog = $('#support-dialog')
+ let dialog = $('#support-dialog')[0]
- // FIXME: Find a nicer way of doing this
- if (!dialog.length) {
+ if (!dialog) {
+ // FIXME: Find a nicer way of doing this
SupportForm.i18n = i18n
let form = new Vue(SupportForm)
form.$mount($('<div>').appendTo(container)[0])
form.$root = this
form.$toast = this.$toast
- dialog = $(form.$el)
+ dialog = form.$el
}
- dialog.on('shown.bs.modal', () => {
- dialog.find('input').first().focus()
- }).modal()
+ dialog.__vue__.showDialog()
},
userStatusClass(user) {
if (user.isDeleted) {
@@ -464,7 +466,7 @@
// Create an error message
// API responses can use a string, array or object
let msg_text = ''
- if ($.type(msg) !== 'string') {
+ if (typeof(msg) !== 'string') {
$.each(msg, (index, str) => {
msg_text += str + ' '
})
diff --git a/src/resources/js/bootstrap.js b/src/resources/js/bootstrap.js
--- a/src/resources/js/bootstrap.js
+++ b/src/resources/js/bootstrap.js
@@ -1,18 +1,22 @@
/**
- * We'll load jQuery and the Bootstrap jQuery plugin which provides support
- * for JavaScript based Bootstrap features such as modals and tabs. This
- * code may be modified to fit the specific needs of your application.
+ * Import Cash (jQuery replacement)
*/
-window.Popper = require('popper.js').default
-window.$ = window.jQuery = require('jquery')
+import $ from 'cash-dom'
+window.$ = $
-require('bootstrap')
+$.fn.focus = function() {
+ if (this.length && this[0].focus) {
+ this[0].focus()
+ }
+ return this
+}
/**
- * We'll load Vue, VueRouter and global components
+ * Load Vue, VueRouter and global components
*/
+import { Tooltip } from 'bootstrap'
import FontAwesomeIcon from './fontawesome'
import Vue from 'vue'
import VueRouter from 'vue-router'
@@ -24,26 +28,26 @@
Vue.component('SvgIcon', FontAwesomeIcon)
const vTooltip = (el, binding) => {
- const t = []
+ let t = []
if (binding.modifiers.focus) t.push('focus')
if (binding.modifiers.hover) t.push('hover')
if (binding.modifiers.click) t.push('click')
- if (!t.length) t.push('hover')
+ if (!t.length) t.push('click')
- $(el).tooltip({
+ el.tooltip = new Tooltip(el, {
title: binding.value,
placement: binding.arg || 'top',
trigger: t.join(' '),
- html: !!binding.modifiers.html,
- });
+ html: !!binding.modifiers.html
+ })
}
Vue.directive('tooltip', {
bind: vTooltip,
update: vTooltip,
unbind (el) {
- $(el).tooltip('dispose')
+ el.tooltip.dispose()
}
})
@@ -70,7 +74,7 @@
})
/**
- * We'll load the axios HTTP library which allows us to easily issue requests
+ * Load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
diff --git a/src/resources/js/meet/app.js b/src/resources/js/meet/app.js
--- a/src/resources/js/meet/app.js
+++ b/src/resources/js/meet/app.js
@@ -1,4 +1,5 @@
import anchorme from 'anchorme'
+import { Dropdown } from 'bootstrap'
import { library } from '@fortawesome/fontawesome-svg-core'
import { OpenVidu } from 'openvidu-browser'
@@ -1033,7 +1034,7 @@
}
if (params.isSelf) {
- wrapper.find('.link-setup').removeClass('hidden').click(() => sessionData.onMediaSetup())
+ wrapper.find('.link-setup').removeClass('hidden').on('click', () => sessionData.onMediaSetup())
} else {
let volumeInput = wrapper.find('.volume input')
let audioButton = wrapper.find('.link-audio')
@@ -1284,19 +1285,19 @@
+ '<div class="dropdown-divider permissions"></div>'
+ '<div class="permissions">'
+ '<h6 class="dropdown-header">' + $t('meet.perm') + '</h6>'
- + '<label class="dropdown-item action-role-publisher custom-control custom-switch">'
- + '<input type="checkbox" class="custom-control-input">'
- + ' <span class="custom-control-label">' + $t('meet.perm-av') + '</span>'
+ + '<label class="dropdown-item action-role-publisher form-check form-switch">'
+ + '<input type="checkbox" class="form-check-input">'
+ + ' <span class="form-check-label">' + $t('meet.perm-av') + '</span>'
+ '</label>'
- + '<label class="dropdown-item action-role-moderator custom-control custom-switch">'
- + '<input type="checkbox" class="custom-control-input">'
- + ' <span class="custom-control-label">' + $t('meet.perm-mod') + '</span>'
+ + '<label class="dropdown-item action-role-moderator form-check form-switch">'
+ + '<input type="checkbox" class="form-check-input">'
+ + ' <span class="form-check-label">' + $t('meet.perm-mod') + '</span>'
+ '</label>'
+ '</div>'
+ '<div class="dropdown-divider interpreting"></div>'
+ '<div class="interpreting">'
+ '<h6 class="dropdown-header">' + $t('meet.lang-int') + '</h6>'
- + '<div class="ml-4 mr-4"><select class="custom-select">'
+ + '<div class="ps-3 pe-3"><select class="form-select">'
+ '<option value="">- ' + $t('form.none') + ' -</option>'
+ languages.join('')
+ '</select></div>'
@@ -1307,8 +1308,9 @@
let nickname = element.find('.meet-nickname')
.addClass('btn btn-outline-' + (params.isSelf ? 'primary' : 'secondary'))
- .attr({title: $t('meet.menu-options'), 'data-toggle': 'dropdown'})
- .dropdown({boundary: container.parentNode})
+ .attr({title: $t('meet.menu-options'), 'data-bs-toggle': 'dropdown'})
+
+ const dropdown = new Dropdown(nickname[0], {boundary: container.parentNode})
if (params.isSelf) {
// Add events for nickname change
@@ -1393,7 +1395,7 @@
.on('change', e => {
const language = $(e.target).val()
sessionData.onConnectionChange(params.connectionId, { language })
- element.find('.meet-nickname').dropdown('hide')
+ dropdown.hide()
})
.on('click', e => {
// Prevents from closing the dropdown menu on click
diff --git a/src/resources/lang/en/ui.php b/src/resources/lang/en/ui.php
--- a/src/resources/lang/en/ui.php
+++ b/src/resources/lang/en/ui.php
@@ -336,6 +336,7 @@
'create' => "Create user",
'custno' => "Customer No.",
'delete' => "Delete user",
+ 'delete-account' => "Delete this account?",
'delete-email' => "Delete {email}",
'delete-text' => "Do you really want to delete this user permanently?"
. " This will delete all account data and withdraw the permission to access the email account."
diff --git a/src/resources/themes/bootstrap.scss b/src/resources/themes/bootstrap.scss
--- a/src/resources/themes/bootstrap.scss
+++ b/src/resources/themes/bootstrap.scss
@@ -1,15 +1,13 @@
-// Bootstrap
@import '~bootstrap/scss/bootstrap';
-// Bootstrap style fixes
+// Bootstrap style fixes and additions
-.btn-link,
-.table thead th {
- border: 0;
+a {
+ text-decoration: none;
}
-small {
- font-size: 0.875em;
+.table > :not(:last-child) > :last-child > * {
+ border-bottom-color: #ccc;
}
.hidden {
diff --git a/src/resources/themes/default/lang/en/faq.php b/src/resources/themes/default/lang/en/faq.php
--- a/src/resources/themes/default/lang/en/faq.php
+++ b/src/resources/themes/default/lang/en/faq.php
@@ -5,5 +5,6 @@
'account-upgrade' => "Can I upgrade an individual account to a group account?",
'storage' => "How much storage comes with my account?",
'tos' => "What are your terms of service?",
+ 'support' => "Need support?",
];
diff --git a/src/resources/themes/default/theme.json b/src/resources/themes/default/theme.json
--- a/src/resources/themes/default/theme.json
+++ b/src/resources/themes/default/theme.json
@@ -39,6 +39,10 @@
{
"href": "https://kb.kolabnow.com/faq/how-much-storage-comes-with-my-account",
"label": "storage"
+ },
+ {
+ "href": "/support",
+ "label": "support"
}
]
}
diff --git a/src/resources/themes/forms.scss b/src/resources/themes/forms.scss
--- a/src/resources/themes/forms.scss
+++ b/src/resources/themes/forms.scss
@@ -33,12 +33,15 @@
label {
margin-right: 0.5em;
+ min-width: 4em;
+ text-align: right;
+ line-height: 1.7;
}
}
.input-group-activable {
&.active {
- :not(.input-group-append):not(.activable) {
+ :not(.activable) {
display: none;
}
}
@@ -70,19 +73,32 @@
margin-top: -0.25rem;
}
-form.read-only {
- .row {
- margin-bottom: 0;
- }
-}
-
// Various improvements for mobile
@include media-breakpoint-down(sm) {
- .form-group {
- margin-bottom: 0.5rem;
+ .row.mb-3 {
+ margin-bottom: 0.5rem !important;
+ }
+
+ .nav-tabs {
+ flex-wrap: nowrap;
+ overflow-x: auto;
+
+ .nav-link {
+ white-space: nowrap;
+ padding: 0.5rem 0.75rem;
+ }
+ }
+
+ .tab-content {
+ margin-top: 0.5rem;
+ }
+
+ .col-form-label {
+ color: #666;
+ font-size: 95%;
}
- .form-group.plaintext .col-form-label {
+ .row.plaintext .col-form-label {
padding-bottom: 0;
}
@@ -93,19 +109,11 @@
width: 65%;
}
}
-}
-
-@include media-breakpoint-down(xs) {
- .col-form-label {
- color: #666;
- font-size: 95%;
- }
- .form-group.checkbox {
+ .row.checkbox {
position: relative;
& > div {
- position: initial;
padding-top: 0 !important;
input {
diff --git a/src/resources/themes/meet.scss b/src/resources/themes/meet.scss
--- a/src/resources/themes/meet.scss
+++ b/src/resources/themes/meet.scss
@@ -54,8 +54,7 @@
& + .dropdown-menu {
.permissions > label {
- margin: 0;
- padding-left: 3.75rem;
+ padding-left: 3.5rem;
}
}
}
@@ -514,7 +513,7 @@
}
}
-@include media-breakpoint-down(xs) {
+@include media-breakpoint-down(sm) {
#meet-session-menu {
white-space: nowrap;
margin: 0;
diff --git a/src/resources/themes/menu.scss b/src/resources/themes/menu.scss
--- a/src/resources/themes/menu.scss
+++ b/src/resources/themes/menu.scss
@@ -3,13 +3,10 @@
padding: 0;
line-height: 85px;
- .navbar {
- padding-right: 0;
- }
-
.navbar-brand {
- padding: 0;
+ padding: 0 0.5rem;
outline: 0;
+ margin: 0;
> img {
display: inline;
@@ -20,7 +17,7 @@
.nav-link {
color: #202020;
line-height: 85px;
- padding: 0 0 0 25px;
+ padding: 0 0.5rem 0 0.5rem;
background: transparent;
&:focus {
@@ -46,12 +43,18 @@
overflow: hidden;
padding: 0;
- .navbar {
- padding-right: 0;
+ .container {
+ flex-wrap: nowrap;
+ }
+
+ .navbar-nav {
+ display: flex;
+ flex-direction: row;
}
.navbar-brand {
margin: 0;
+ padding: 0 0.5rem;
img {
width: 170px;
@@ -69,7 +72,6 @@
margin: 2em 0;
.dropdown-toggle {
- padding-left: 20px;
line-height: 30px;
font-weight: lighter;
}
@@ -88,7 +90,7 @@
line-height: 21px;
letter-spacing: 1px;
padding: 6px 34px;
- margin: 25px 0 25px 25px;
+ margin: 25px 0.5rem 25px 0.5rem;
&:focus,
&:hover {
@@ -99,25 +101,18 @@
}
}
}
-
- .navbar {
- .navbar {
- justify-content: flex-end;
- }
- }
-
- #footer-menu {
- .navbar {
- flex-direction: column;
- align-items: flex-end;
- }
- }
}
-@include media-breakpoint-down(md) {
+@include media-breakpoint-down(lg) {
#header-menu {
.navbar-nav {
- padding-bottom: 1em;
+ display: block;
+ width: 100%;
+ padding: 0;
+
+ li {
+ border-top: 1px solid #eee;
+ }
}
.nav-link {
@@ -126,13 +121,15 @@
}
}
+ #language-selector {
+ margin: 0;
+ }
+}
+
+@include media-breakpoint-down(md) {
#footer-menu {
height: 80px;
- .navbar {
- padding-left: 0;
- }
-
.navbar-nav {
display: none;
}
@@ -141,29 +138,11 @@
flex-wrap: nowrap;
}
}
-
- #language-selector {
- margin: 0;
-
- .dropdown-toggle:after {
- display: none;
- }
- }
}
@include media-breakpoint-down(sm) {
#header-menu {
padding: 0 1em;
-
- .navbar-nav {
- display: block;
- width: 100%;
- padding: 0;
-
- li {
- border-top: 1px solid #eee;
- }
- }
}
#footer-menu {
@@ -171,6 +150,10 @@
flex-direction: column;
}
+ .navbar-brand {
+ padding: 0.5rem;
+ }
+
#footer-company {
display: none;
}
diff --git a/src/resources/themes/toast.scss b/src/resources/themes/toast.scss
--- a/src/resources/themes/toast.scss
+++ b/src/resources/themes/toast.scss
@@ -8,7 +8,7 @@
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: rgba(52, 58, 64, 0.95) transparent;
- z-index: 1055; // above Bootstrap's modal backdrop and dialogs
+ z-index: 1065; // above Bootstrap's modal backdrop and dialogs
@media (max-width: 375px) {
left: 0;
@@ -42,15 +42,9 @@
margin-right: 0.5rem;
}
- button.close {
- color: #eee;
- opacity: 1 !important;
- text-shadow: none;
- font-size: 1.2rem;
-
- &:hover {
- color: #fff;
- }
+ .btn-close {
+ font-size: 0.8em;
+ cursor: pointer;
}
}
diff --git a/src/resources/vue/Admin/Distlist.vue b/src/resources/vue/Admin/Distlist.vue
--- a/src/resources/vue/Admin/Distlist.vue
+++ b/src/resources/vue/Admin/Distlist.vue
@@ -5,7 +5,7 @@
<div class="card-title">{{ list.email }}</div>
<div class="card-text">
<form class="read-only short">
- <div class="form-group row">
+ <div class="row plaintext">
<label for="distlistid" class="col-sm-4 col-form-label">
{{ $t('form.id') }} <span class="text-muted">({{ $t('form.created') }})</span>
</label>
@@ -15,13 +15,13 @@
</span>
</div>
</div>
- <div class="form-group row">
+ <div class="row plaintext">
<label for="status" class="col-sm-4 col-form-label">{{ $t('form.status') }}</label>
<div class="col-sm-8">
<span :class="$root.distlistStatusClass(list) + ' form-control-plaintext'" id="status">{{ $root.distlistStatusText(list) }}</span>
</div>
</div>
- <div class="form-group row">
+ <div class="row plaintext">
<label for="members-input" class="col-sm-4 col-form-label">{{ $t('distlist.recipients') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="members">
diff --git a/src/resources/vue/Admin/Domain.vue b/src/resources/vue/Admin/Domain.vue
--- a/src/resources/vue/Admin/Domain.vue
+++ b/src/resources/vue/Admin/Domain.vue
@@ -5,7 +5,7 @@
<div class="card-title">{{ domain.namespace }}</div>
<div class="card-text">
<form class="read-only short">
- <div class="form-group row">
+ <div class="row plaintext">
<label for="domainid" class="col-sm-4 col-form-label">
{{ $t('form.id') }} <span class="text-muted">({{ $t('form.created') }})</span>
</label>
@@ -15,7 +15,7 @@
</span>
</div>
</div>
- <div class="form-group row">
+ <div class="row plaintext">
<label for="first_name" class="col-sm-4 col-form-label">{{ $t('form.status') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="status">
@@ -62,7 +62,7 @@
<div class="card-body">
<div class="card-text">
<form class="read-only short">
- <div class="form-group row plaintext">
+ <div class="row plaintext">
<label for="spf_whitelist" class="col-sm-4 col-form-label">{{ $t('domain.spf-whitelist') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="spf_whitelist">
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -5,7 +5,7 @@
<h1 class="card-title">{{ user.email }}</h1>
<div class="card-text">
<form class="read-only short">
- <div v-if="user.wallet.user_id != user.id" class="form-group row plaintext">
+ <div v-if="user.wallet.user_id != user.id" class="row plaintext">
<label for="manager" class="col-sm-4 col-form-label">{{ $t('user.managed-by') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="manager">
@@ -13,7 +13,7 @@
</span>
</div>
</div>
- <div class="form-group row plaintext">
+ <div class="row plaintext">
<label for="userid" class="col-sm-4 col-form-label">ID <span class="text-muted">({{ $t('form.created') }})</span></label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="userid">
@@ -21,7 +21,7 @@
</span>
</div>
</div>
- <div class="form-group row plaintext">
+ <div class="row plaintext">
<label for="status" class="col-sm-4 col-form-label">{{ $t('form.status') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="status">
@@ -29,31 +29,31 @@
</span>
</div>
</div>
- <div class="form-group row plaintext" v-if="user.first_name">
+ <div class="row plaintext" v-if="user.first_name">
<label for="first_name" class="col-sm-4 col-form-label">{{ $t('form.firstname') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="first_name">{{ user.first_name }}</span>
</div>
</div>
- <div class="form-group row plaintext" v-if="user.last_name">
+ <div class="row plaintext" v-if="user.last_name">
<label for="last_name" class="col-sm-4 col-form-label">{{ $t('form.lastname') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="last_name">{{ user.last_name }}</span>
</div>
</div>
- <div class="form-group row plaintext" v-if="user.organization">
+ <div class="row plaintext" v-if="user.organization">
<label for="organization" class="col-sm-4 col-form-label">{{ $t('user.org') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="organization">{{ user.organization }}</span>
</div>
</div>
- <div class="form-group row plaintext" v-if="user.phone">
+ <div class="row plaintext" v-if="user.phone">
<label for="phone" class="col-sm-4 col-form-label">{{ $t('form.phone') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="phone">{{ user.phone }}</span>
</div>
</div>
- <div class="form-group row plaintext">
+ <div class="row plaintext">
<label for="external_email" class="col-sm-4 col-form-label">{{ $t('user.ext-email') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="external_email">
@@ -62,13 +62,13 @@
</span>
</div>
</div>
- <div class="form-group row plaintext" v-if="user.billing_address">
+ <div class="row plaintext" v-if="user.billing_address">
<label for="billing_address" class="col-sm-4 col-form-label">{{ $t('user.address') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" style="white-space:pre" id="billing_address">{{ user.billing_address }}</span>
</div>
</div>
- <div class="form-group row plaintext">
+ <div class="row plaintext">
<label for="country" class="col-sm-4 col-form-label">{{ $t('user.country') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="country">{{ user.country }}</span>
@@ -132,7 +132,7 @@
</h2>
<div class="card-text">
<form class="read-only short">
- <div class="form-group row">
+ <div class="row">
<label class="col-sm-4 col-form-label">{{ $t('user.discount') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="discount">
@@ -141,7 +141,7 @@
</span>
</div>
</div>
- <div class="form-group row" v-if="wallet.mandate && wallet.mandate.id">
+ <div class="row" v-if="wallet.mandate && wallet.mandate.id">
<label class="col-sm-4 col-form-label">{{ $t('user.auto-payment') }}</label>
<div class="col-sm-8">
<span id="autopayment" :class="'form-control-plaintext' + (wallet.mandateState ? ' text-danger' : '')"
@@ -155,7 +155,7 @@
</span>
</div>
</div>
- <div class="form-group row" v-if="wallet.providerLink">
+ <div class="row" v-if="wallet.providerLink">
<label class="col-sm-4 col-form-label">{{ capitalize(wallet.provider) }} {{ $t('form.id') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" v-html="wallet.providerLink"></span>
@@ -174,8 +174,8 @@
<div class="tab-pane" id="user-aliases" role="tabpanel" aria-labelledby="tab-aliases">
<div class="card-body">
<div class="card-text">
- <table class="table table-sm table-hover">
- <thead class="thead-light">
+ <table class="table table-sm table-hover mb-0">
+ <thead>
<tr>
<th scope="col">{{ $t('form.email') }}</th>
</tr>
@@ -198,7 +198,7 @@
<div class="card-body">
<div class="card-text">
<table class="table table-sm table-hover mb-0">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('user.subscription') }}</th>
<th scope="col">{{ $t('user.price') }}</th>
@@ -231,8 +231,8 @@
<div class="tab-pane" id="user-domains" role="tabpanel" aria-labelledby="tab-domains">
<div class="card-body">
<div class="card-text">
- <table class="table table-sm table-hover">
- <thead class="thead-light">
+ <table class="table table-sm table-hover mb-0">
+ <thead>
<tr>
<th scope="col">{{ $t('domain.namespace') }}</th>
</tr>
@@ -257,8 +257,8 @@
<div class="tab-pane" id="user-users" role="tabpanel" aria-labelledby="tab-users">
<div class="card-body">
<div class="card-text">
- <table class="table table-sm table-hover">
- <thead class="thead-light">
+ <table class="table table-sm table-hover mb-0">
+ <thead>
<tr>
<th scope="col">{{ $t('form.primary-email') }}</th>
</tr>
@@ -284,8 +284,8 @@
<div class="tab-pane" id="user-distlists" role="tabpanel" aria-labelledby="tab-distlists">
<div class="card-body">
<div class="card-text">
- <table class="table table-sm table-hover">
- <thead class="thead-light">
+ <table class="table table-sm table-hover mb-0">
+ <thead>
<tr>
<th scope="col">{{ $t('form.email') }}</th>
</tr>
@@ -311,7 +311,7 @@
<div class="card-body">
<div class="card-text">
<form class="read-only short">
- <div class="form-group row plaintext">
+ <div class="row plaintext">
<label for="greylisting" class="col-sm-4 col-form-label">{{ $t('user.greylisting') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="greylisting">
@@ -331,20 +331,18 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('user.discount-title') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
- <p class="form-group">
- <select v-model="wallet.discount_id" class="custom-select">
+ <p>
+ <select v-model="wallet.discount_id" class="form-select">
<option value="">- {{ $t('form.none') }} -</option>
<option v-for="item in discounts" :value="item.id" :key="item.id">{{ item.label }}</option>
</select>
</p>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="button" class="btn btn-primary modal-action" @click="submitDiscount()">
<svg-icon icon="check"></svg-icon> {{ $t('btn.submit') }}
</button>
@@ -358,17 +356,15 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('user.ext-email') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
- <p class="form-group">
+ <p>
<input v-model="external_email" name="external_email" class="form-control">
</p>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="button" class="btn btn-primary modal-action" @click="submitEmail()">
<svg-icon icon="check"></svg-icon> {{ $t('btn.submit') }}
</button>
@@ -382,29 +378,25 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t(oneoff_negative ? 'user.add-penalty-title' : 'user.add-bonus-title') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<form data-validation-prefix="oneoff_">
- <div class="form-group">
+ <div class="row mb-3">
<label for="oneoff_amount" class="col-form-label">{{ $t('form.amount') }}</label>
<div class="input-group">
<input type="text" class="form-control" id="oneoff_amount" v-model="oneoff_amount" required>
- <span class="input-group-append">
- <span class="input-group-text">{{ wallet.currency }}</span>
- </span>
+ <span class="input-group-text">{{ wallet.currency }}</span>
</div>
</div>
- <div class="form-group">
+ <div class="row">
<label for="oneoff_description" class="col-form-label">{{ $t('form.description') }}</label>
<input class="form-control" id="oneoff_description" v-model="oneoff_description" required>
</div>
</form>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="button" class="btn btn-primary modal-action" @click="submitOneOff()">
<svg-icon icon="check"></svg-icon> {{ $t('btn.submit') }}
</button>
@@ -418,16 +410,14 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('user.reset-2fa-title') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<p>{{ $t('user.2fa-hint1') }}</p>
<p>{{ $t('user.2fa-hint2') }}</p>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="button" class="btn btn-danger modal-action" @click="reset2FA()">{{ $t('btn.reset') }}</button>
</div>
</div>
@@ -437,6 +427,7 @@
</template>
<script>
+ import { Modal } from 'bootstrap'
import TransactionLog from '../Widgets/TransactionLog'
export default {
@@ -575,13 +566,19 @@
this.oneOffDialog(false)
},
discountEdit() {
- $('#discount-dialog')
- .on('shown.bs.modal', e => {
- $(e.target).find('select').focus()
+ if (!this.discount_dialog) {
+ const dialog = $('#discount-dialog')[0]
+
+ dialog.addEventListener('shown.bs.modal', e => {
+ $(dialog).find('select').focus()
// Note: Vue v-model is strict, convert null to a string
this.wallet.discount_id = this.wallet_discount_id || ''
})
- .modal()
+
+ this.discount_dialog = new Modal(dialog)
+ }
+
+ this.discount_dialog.show()
if (!this.discounts.length) {
// Fetch discounts
@@ -595,11 +592,17 @@
this.external_email = this.user.external_email
this.$root.clearFormValidation($('#email-dialog'))
- $('#email-dialog')
- .on('shown.bs.modal', e => {
- $(e.target).find('input').focus()
+ if (!this.email_dialog) {
+ const dialog = $('#email-dialog')[0]
+
+ dialog.addEventListener('shown.bs.modal', e => {
+ $(dialog).find('input').focus()
})
- .modal()
+
+ this.email_dialog = new Modal(dialog)
+ }
+
+ this.email_dialog.show()
},
setMandateState() {
let mandate = this.wallet.mandate
@@ -613,10 +616,19 @@
},
oneOffDialog(negative) {
this.oneoff_negative = negative
- this.dialog = $('#oneoff-dialog').on('shown.bs.modal', event => {
- this.$root.clearFormValidation(event.target)
- $(event.target).find('#oneoff_amount').focus()
- }).modal()
+
+ if (!this.oneoff_dialog) {
+ const dialog = $('#oneoff-dialog')[0]
+
+ dialog.addEventListener('shown.bs.modal', () => {
+ this.$root.clearFormValidation(dialog)
+ $(dialog).find('#oneoff_amount').focus()
+ })
+
+ this.oneoff_dialog = new Modal(dialog)
+ }
+
+ this.oneoff_dialog.show()
},
penalizeDialog() {
this.oneOffDialog(true)
@@ -627,7 +639,7 @@
this.$nextTick(() => { this.walletReload = false })
},
reset2FA() {
- $('#reset-2fa-dialog').modal('hide')
+ new Modal('#reset-2fa-dialog').hide()
axios.post('/api/v4/users/' + this.user.id + '/reset2FA')
.then(response => {
if (response.data.status == 'success') {
@@ -638,10 +650,10 @@
})
},
reset2FADialog() {
- $('#reset-2fa-dialog').modal()
+ new Modal('#reset-2fa-dialog').show()
},
submitDiscount() {
- $('#discount-dialog').modal('hide')
+ this.discount_dialog.hide()
axios.put('/api/v4/wallets/' + this.user.wallets[0].id, { discount: this.wallet.discount_id })
.then(response => {
@@ -665,7 +677,7 @@
axios.put('/api/v4/users/' + this.user.id, { external_email: this.external_email })
.then(response => {
if (response.data.status == 'success') {
- $('#email-dialog').modal('hide')
+ this.email_dialog.hide()
this.$toast.success(response.data.message)
this.user.external_email = this.external_email
this.external_email = null // required because of Vue
@@ -683,12 +695,12 @@
post.amount *= -1
}
- this.$root.clearFormValidation(this.dialog)
+ this.$root.clearFormValidation('#oneoff-dialog')
axios.post('/api/v4/wallets/' + wallet_id + '/one-off', post)
.then(response => {
if (response.data.status == 'success') {
- this.dialog.modal('hide')
+ this.oneoff_dialog.hide()
this.$toast.success(response.data.message)
this.wallet = Object.assign({}, this.wallet, {balance: response.data.balance})
this.oneoff_amount = ''
diff --git a/src/resources/vue/App.vue b/src/resources/vue/App.vue
--- a/src/resources/vue/App.vue
+++ b/src/resources/vue/App.vue
@@ -77,21 +77,24 @@
$('#faq').remove()
if (result && result.length) {
let faq = $('<div id="faq" class="faq mt-3"><h5>' + this.$t('app.faq') + '</h5><ul class="pl-4"></ul></div>')
- let list = []
+ let list = $([])
result.forEach(item => {
- list.push($('<li>').append($('<a>').attr('href', item.href).text(item.title)))
+ let li = $('<li>').append($('<a>').attr('href', item.href).text(item.title))
// Handle internal links with the vue-router
if (item.href.charAt(0) == '/') {
- list[list.length-1].find('a').on('click', event => {
+ li.find('a').on('click', event => {
event.preventDefault()
this.$router.push(item.href)
})
}
+
+ list = list.add(li)
})
faq.find('ul').append(list)
+
$(this.$el).append(faq)
}
})
diff --git a/src/resources/vue/Distlist/Info.vue b/src/resources/vue/Distlist/Info.vue
--- a/src/resources/vue/Distlist/Info.vue
+++ b/src/resources/vue/Distlist/Info.vue
@@ -6,26 +6,26 @@
<div class="card-body">
<div class="card-title" v-if="list_id !== 'new'">
{{ $tc('distlist.list-title', 1) }}
- <button class="btn btn-outline-danger button-delete float-right" @click="deleteList()" tag="button">
+ <button class="btn btn-outline-danger button-delete float-end" @click="deleteList()" tag="button">
<svg-icon icon="trash-alt"></svg-icon> {{ $t('distlist.delete') }}
</button>
</div>
<div class="card-title" v-if="list_id === 'new'">{{ $t('distlist.new') }}</div>
<div class="card-text">
<form @submit.prevent="submit">
- <div v-if="list_id !== 'new'" class="form-group row plaintext">
+ <div v-if="list_id !== 'new'" class="row plaintext mb-3">
<label for="status" class="col-sm-4 col-form-label">{{ $t('form.status') }}</label>
<div class="col-sm-8">
<span :class="$root.distlistStatusClass(list) + ' form-control-plaintext'" id="status">{{ $root.distlistStatusText(list) }}</span>
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="email" class="col-sm-4 col-form-label">{{ $t('form.email') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="email" :disabled="list_id !== 'new'" required v-model="list.email">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="members-input" class="col-sm-4 col-form-label">{{ $t('distlist.recipients') }}</label>
<div class="col-sm-8">
<list-input id="members" :list="list.members"></list-input>
diff --git a/src/resources/vue/Distlist/List.vue b/src/resources/vue/Distlist/List.vue
--- a/src/resources/vue/Distlist/List.vue
+++ b/src/resources/vue/Distlist/List.vue
@@ -4,13 +4,13 @@
<div class="card-body">
<div class="card-title">
{{ $tc('distlist.list-title', 2) }}
- <router-link class="btn btn-success float-right create-list" :to="{ path: 'distlist/new' }" tag="button">
+ <router-link class="btn btn-success float-end create-list" :to="{ path: 'distlist/new' }" tag="button">
<svg-icon icon="users"></svg-icon> {{ $t('distlist.create') }}
</router-link>
</div>
<div class="card-text">
<table class="table table-sm table-hover">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('distlist.email') }}</th>
</tr>
diff --git a/src/resources/vue/Domain/Info.vue b/src/resources/vue/Domain/Info.vue
--- a/src/resources/vue/Domain/Info.vue
+++ b/src/resources/vue/Domain/Info.vue
@@ -51,11 +51,11 @@
<div class="tab-pane" id="settings" role="tabpanel" aria-labelledby="tab-settings">
<div class="card-body">
<form @submit.prevent="submitSettings">
- <div class="form-group row">
+ <div class="row mb-3">
<label for="spf_whitelist" class="col-sm-4 col-form-label">{{ $t('domain.spf-whitelist') }}</label>
<div class="col-sm-8">
<list-input id="spf_whitelist" name="spf_whitelist" :list="spf_whitelist"></list-input>
- <small id="spf-hint" class="form-text text-muted">
+ <small id="spf-hint" class="text-muted d-block mt-2">
{{ $t('domain.spf-whitelist-text') }}
<span class="d-block" v-html="$t('domain.spf-whitelist-ex')"></span>
</small>
diff --git a/src/resources/vue/Domain/List.vue b/src/resources/vue/Domain/List.vue
--- a/src/resources/vue/Domain/List.vue
+++ b/src/resources/vue/Domain/List.vue
@@ -5,7 +5,7 @@
<div class="card-title">{{ $t('user.domains') }}</div>
<div class="card-text">
<table class="table table-sm table-hover">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('domain.namespace') }}</th>
<th scope="col"></th>
diff --git a/src/resources/vue/Login.vue b/src/resources/vue/Login.vue
--- a/src/resources/vue/Login.vue
+++ b/src/resources/vue/Login.vue
@@ -1,37 +1,31 @@
<template>
<div class="container d-flex flex-column align-items-center justify-content-center">
<div id="logon-form" class="card col-sm-8 col-lg-6">
- <div class="card-body">
+ <div class="card-body p-4">
<h1 class="card-title text-center mb-3">{{ $t('login.header') }}</h1>
- <div class="card-text">
+ <div class="card-text m-2 mb-0">
<form class="form-signin" @submit.prevent="submitLogin">
- <div class="form-group">
- <label for="inputEmail" class="sr-only">{{ $t('form.email') }}</label>
+ <div class="row mb-3">
+ <label for="inputEmail" class="visually-hidden">{{ $t('form.email') }}</label>
<div class="input-group">
- <span class="input-group-prepend">
- <span class="input-group-text"><svg-icon icon="user"></svg-icon></span>
- </span>
+ <span class="input-group-text"><svg-icon icon="user"></svg-icon></span>
<input type="email" id="inputEmail" class="form-control" :placeholder="$t('form.email')" required autofocus v-model="email">
</div>
</div>
- <div class="form-group">
- <label for="inputPassword" class="sr-only">{{ $t('form.password') }}</label>
+ <div class="row mb-4">
+ <label for="inputPassword" class="visually-hidden">{{ $t('form.password') }}</label>
<div class="input-group">
- <span class="input-group-prepend">
- <span class="input-group-text"><svg-icon icon="lock"></svg-icon></span>
- </span>
+ <span class="input-group-text"><svg-icon icon="lock"></svg-icon></span>
<input type="password" id="inputPassword" class="form-control" :placeholder="$t('form.password')" required v-model="password">
</div>
</div>
- <div class="form-group pt-3" v-if="$root.isUser">
- <label for="secondfactor" class="sr-only">{{ $t('login.2fa') }}</label>
+ <div class="row mb-3" v-if="$root.isUser">
+ <label for="secondfactor" class="visually-hidden">{{ $t('login.2fa') }}</label>
<div class="input-group">
- <span class="input-group-prepend">
- <span class="input-group-text"><svg-icon icon="key"></svg-icon></span>
- </span>
- <input type="text" id="secondfactor" class="form-control rounded-right" :placeholder="$t('login.2fa')" v-model="secondFactor">
+ <span class="input-group-text"><svg-icon icon="key"></svg-icon></span>
+ <input type="text" id="secondfactor" class="form-control" :placeholder="$t('login.2fa')" v-model="secondFactor">
</div>
- <small class="form-text text-muted">{{ $t('login.2fa_desc') }}</small>
+ <small class="text-muted mt-2">{{ $t('login.2fa_desc') }}</small>
</div>
<div class="text-center">
<button class="btn btn-primary" type="submit">
diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue
--- a/src/resources/vue/Meet/Room.vue
+++ b/src/resources/vue/Meet/Room.vue
@@ -17,7 +17,7 @@
<svg-icon icon="hand-paper"></svg-icon>
</button>
<span id="channel-select" :style="'display:' + (channels.length ? '' : 'none')" class="dropdown">
- <button :class="'btn link-channel' + (session.channel ? ' on' : '')" data-toggle="dropdown"
+ <button :class="'btn link-channel' + (session.channel ? ' on' : '')" data-bs-toggle="dropdown"
:title="$t('meet.menu-channel')" aria-haspopup="true" aria-expanded="false"
>
<svg-icon icon="headphones"></svg-icon>
@@ -58,33 +58,33 @@
<div class="volume"><div class="bar"></div></div>
</div>
<div class="col-sm-6 align-self-center">
- <div class="input-group">
- <label for="setup-microphone" class="input-group-prepend mb-0">
- <span class="input-group-text" :title="$t('meet.mic')"><svg-icon icon="microphone"></svg-icon></span>
+ <div class="input-group mb-2">
+ <label for="setup-microphone" class="input-group-text mb-0" :title="$t('meet.mic')">
+ <svg-icon icon="microphone"></svg-icon>
</label>
- <select class="custom-select" id="setup-microphone" v-model="microphone" @change="setupMicrophoneChange">
+ <select class="form-select" id="setup-microphone" v-model="microphone" @change="setupMicrophoneChange">
<option value="">{{ $t('form.none') }}</option>
<option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option>
</select>
</div>
- <div class="input-group mt-2">
- <label for="setup-camera" class="input-group-prepend mb-0">
- <span class="input-group-text" :title="$t('meet.cam')"><svg-icon icon="video"></svg-icon></span>
+ <div class="input-group mb-2">
+ <label for="setup-camera" class="input-group-text mb-0" :title="$t('meet.cam')">
+ <svg-icon icon="video"></svg-icon>
</label>
- <select class="custom-select" id="setup-camera" v-model="camera" @change="setupCameraChange">
+ <select class="form-select" id="setup-camera" v-model="camera" @change="setupCameraChange">
<option value="">{{ $t('form.none') }}</option>
<option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option>
</select>
</div>
- <div class="input-group mt-2">
- <label for="setup-nickname" class="input-group-prepend mb-0">
- <span class="input-group-text" :title="$t('meet.nick')"><svg-icon icon="user"></svg-icon></span>
+ <div class="input-group mb-2">
+ <label for="setup-nickname" class="input-group-text mb-0" :title="$t('meet.nick')">
+ <svg-icon icon="user"></svg-icon>
</label>
<input class="form-control" type="text" id="setup-nickname" v-model="nickname" :placeholder="$t('meet.nick-placeholder')">
</div>
<div class="input-group mt-2" v-if="session.config && session.config.requires_password">
- <label for="setup-password" class="input-group-prepend mb-0">
- <span class="input-group-text" :title="$t('form.password')"><svg-icon icon="key"></svg-icon></span>
+ <label for="setup-password" class="input-group-text mb-0" :title="$t('form.password')">
+ <svg-icon icon="key"></svg-icon>
</label>
<input type="password" class="form-control" id="setup-password" v-model="password" :placeholder="$t('form.password')">
</div>
@@ -126,15 +126,13 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('meet.leave-title') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<p>{{ $t('meet.leave-body') }}</p>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-danger modal-action" data-dismiss="modal">{{ $t('btn.close') }}</button>
+ <button type="button" class="btn btn-danger modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</button>
</div>
</div>
</div>
@@ -145,27 +143,25 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('meet.media-title') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<form class="media-setup-form">
<div class="media-setup-preview"></div>
<div class="input-group mt-2">
- <label for="setup-mic" class="input-group-prepend mb-0">
- <span class="input-group-text" :title="$t('meet.mic')"><svg-icon icon="microphone"></svg-icon></span>
+ <label for="setup-mic" class="input-group-text mb-0" :title="$t('meet.mic')">
+ <svg-icon icon="microphone"></svg-icon>
</label>
- <select class="custom-select" id="setup-mic" v-model="microphone" @change="setupMicrophoneChange">
+ <select class="form-select" id="setup-mic" v-model="microphone" @change="setupMicrophoneChange">
<option value="">{{ $t('form.none') }}</option>
<option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option>
</select>
</div>
<div class="input-group mt-2">
- <label for="setup-cam" class="input-group-prepend mb-0">
- <span class="input-group-text" :title="$t('meet.cam')"><svg-icon icon="video"></svg-icon></span>
+ <label for="setup-cam" class="input-group-text mb-0" :title="$t('meet.cam')">
+ <svg-icon icon="video"></svg-icon>
</label>
- <select class="custom-select" id="setup-cam" v-model="camera" @change="setupCameraChange">
+ <select class="form-select" id="setup-cam" v-model="camera" @change="setupCameraChange">
<option value="">{{ $t('form.none') }}</option>
<option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option>
</select>
@@ -173,7 +169,7 @@
</form>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-action" data-dismiss="modal">{{ $t('btn.close') }}</button>
+ <button type="button" class="btn btn-secondary modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</button>
</div>
</div>
</div>
@@ -184,6 +180,7 @@
</template>
<script>
+ import { Modal } from 'bootstrap'
import { Meet, Roles } from '../../js/meet/app.js'
import StatusMessage from '../Widgets/StatusMessage'
import LogonForm from '../Login'
@@ -294,6 +291,18 @@
// Setup the room UI
this.setupSession()
+
+ // Configure dialog events
+ $('#leave-dialog')[0].addEventListener('hide.bs.modal', () => {
+ // FIXME: Where exactly the user should land? Currently he'll land
+ // on dashboard (if he's logged in) or login form (if he's not).
+
+ this.$router.push({ name: 'dashboard' })
+ })
+
+ const dialog = $('#media-setup-dialog')[0]
+ dialog.addEventListener('show.bs.modal', () => { this.meet.setupStart() })
+ dialog.addEventListener('hide.bs.modal', () => { this.meet.setupStop() })
},
beforeDestroy() {
clearTimeout(roomRequest)
@@ -453,9 +462,9 @@
+ `<div class="picture"><img src="${data.picture}"></div>`
+ `<div class="content">`
+ `<p class="mb-2"></p>`
- + `<div class="text-right">`
+ + `<div class="text-end">`
+ `<button type="button" class="btn btn-sm btn-success accept">${this.$t('btn.accept')}</button>`
- + `<button type="button" class="btn btn-sm btn-danger deny ml-2">${this.$t('btn.deny')}</button>`
+ + `<button type="button" class="btn btn-sm btn-danger deny ms-2">${this.$t('btn.deny')}</button>`
)
this.$toast.message({
@@ -523,12 +532,7 @@
// TODO: Display different message for each reason: forceDisconnectByUser,
// forceDisconnectByServer, sessionClosedByServer?
if (event.reason != 'disconnect' && event.reason != 'networkDisconnect' && !this.isRoomOwner()) {
- $('#leave-dialog').on('hide.bs.modal', () => {
- // FIXME: Where exactly the user should land? Currently he'll land
- // on dashboard (if he's logged in) or login form (if he's not).
-
- this.$router.push({ name: 'dashboard' })
- }).modal()
+ new Modal('#leave-dialog').show()
}
}
this.session.onDismiss = connId => { this.dismissParticipant(connId) }
@@ -621,18 +625,16 @@
return this.reqId
},
roomOptions() {
- $('#room-options-dialog').modal()
+ new Modal('#room-options-dialog').show()
},
setupMedia() {
- let dialog = $('#media-setup-dialog')
+ const dialog = $('#media-setup-dialog')[0]
- if (!dialog.find('video').length) {
- $('#meet-setup').find('video,div.volume').appendTo(dialog.find('.media-setup-preview'))
+ if (!$('video', dialog).length) {
+ $('#meet-setup').find('video,div.volume').appendTo($('.media-setup-preview', dialog))
}
- dialog.on('show.bs.modal', () => { this.meet.setupStart() })
- .on('hide.bs.modal', () => { this.meet.setupStop() })
- .modal()
+ new Modal(dialog).show()
},
setupSession() {
this.meet.setupStart({
diff --git a/src/resources/vue/Meet/RoomOptions.vue b/src/resources/vue/Meet/RoomOptions.vue
--- a/src/resources/vue/Meet/RoomOptions.vue
+++ b/src/resources/vue/Meet/RoomOptions.vue
@@ -5,50 +5,46 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('meet.options') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<form id="room-options-password">
- <div id="password-input" class="input-group input-group-activable">
+ <div id="password-input" class="input-group input-group-activable mb-2">
<span class="input-group-text label">{{ $t('meet.password') }}:</span>
<span v-if="config.password" id="password-input-text" class="input-group-text">{{ config.password }}</span>
<span v-else id="password-input-text" class="input-group-text text-muted">{{ $t('meet.password-none') }}</span>
- <input type="text" :value="config.password" name="password" class="form-control rounded-left activable">
- <div class="input-group-append">
- <button type="button" @click="passwordSave" id="password-save-btn" class="btn btn-outline-primary activable rounded-right">{{ $t('btn.save') }}</button>
- <button type="button" v-if="config.password" id="password-clear-btn" @click="passwordClear" class="btn btn-outline-danger rounded">{{ $t('meet.password-clear') }}</button>
- <button type="button" v-else @click="passwordSet" id="password-set-btn" class="btn btn-outline-primary rounded">{{ $t('meet.password-set') }}</button>
- </div>
+ <input type="text" :value="config.password" name="password" class="form-control rounded-start activable">
+ <button type="button" @click="passwordSave" id="password-save-btn" class="btn btn-outline-primary activable rounded-end">{{ $t('btn.save') }}</button>
+ <button type="button" v-if="config.password" id="password-clear-btn" @click="passwordClear" class="btn btn-outline-danger rounded">{{ $t('meet.password-clear') }}</button>
+ <button type="button" v-else @click="passwordSet" id="password-set-btn" class="btn btn-outline-primary rounded">{{ $t('meet.password-set') }}</button>
</div>
- <small class="form-text text-muted">
+ <small class="text-muted">
{{ $t('meet.password-text') }}
</small>
</form>
<hr>
<form id="room-options-lock">
- <div id="room-lock">
+ <div id="room-lock" class="mb-2">
<label for="room-lock-input">{{ $t('meet.lock') }}:</label>
<input type="checkbox" id="room-lock-input" name="lock" value="1" :checked="config.locked" @click="lockSave">
</div>
- <small class="form-text text-muted">
+ <small class="text-muted">
{{ $t('meet.lock-text') }}
</small>
</form>
<hr>
<form id="room-options-nomedia">
- <div id="room-nomedia">
+ <div id="room-nomedia" class="mb-2">
<label for="room-nomedia-input">{{ $t('meet.nomedia') }}:</label>
<input type="checkbox" id="room-nomedia-input" name="lock" value="1" :checked="config.nomedia" @click="nomediaSave">
</div>
- <small class="form-text text-muted">
+ <small class="text-muted">
{{ $t('meet.nomedia-text') }}
</small>
</form>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-action" data-dismiss="modal">{{ $t('btn.close') }}</button>
+ <button type="button" class="btn btn-secondary modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</button>
</div>
</div>
</div>
@@ -63,7 +59,7 @@
room: { type: String, default: () => null }
},
mounted() {
- $('#room-options-dialog').on('show.bs.modal', e => {
+ $('#room-options-dialog')[0].addEventListener('show.bs.modal', e => {
$(e.target).find('.input-group-activable.active').removeClass('active')
})
},
diff --git a/src/resources/vue/PasswordReset.vue b/src/resources/vue/PasswordReset.vue
--- a/src/resources/vue/PasswordReset.vue
+++ b/src/resources/vue/PasswordReset.vue
@@ -8,8 +8,8 @@
<span v-if="fromEmail">{{ $t('password.reset-step1-hint', { email: fromEmail }) }}</span>
</p>
<form @submit.prevent="submitStep1" data-validation-prefix="reset_">
- <div class="form-group">
- <label for="reset_email" class="sr-only">{{ $t('form.email') }}</label>
+ <div class="mb-3">
+ <label for="reset_email" class="visually-hidden">{{ $t('form.email') }}</label>
<input type="text" class="form-control" id="reset_email" :placeholder="$t('form.email')" required v-model="email">
</div>
<button class="btn btn-primary" type="submit"><svg-icon icon="check"></svg-icon> {{ $t('btn.continue') }}</button>
@@ -24,8 +24,8 @@
{{ $t('password.reset-step2') }}
</p>
<form @submit.prevent="submitStep2" data-validation-prefix="reset_">
- <div class="form-group">
- <label for="reset_short_code" class="sr-only">{{ $t('form.code') }}</label>
+ <div class="mb-3">
+ <label for="reset_short_code" class="visually-hidden">{{ $t('form.code') }}</label>
<input type="text" class="form-control" id="reset_short_code" :placeholder="$t('form.code')" required v-model="short_code">
</div>
<button class="btn btn-secondary" type="button" @click="stepBack">{{ $t('btn.back') }}</button>
@@ -41,12 +41,12 @@
<p class="card-text">
</p>
<form @submit.prevent="submitStep3" data-validation-prefix="reset_">
- <div class="form-group">
- <label for="reset_password" class="sr-only">{{ $t('form.password') }}</label>
+ <div class="mb-3">
+ <label for="reset_password" class="visually-hidden">{{ $t('form.password') }}</label>
<input type="password" class="form-control" id="reset_password" :placeholder="$t('form.password')" required v-model="password">
</div>
- <div class="form-group">
- <label for="reset_confirm" class="sr-only">{{ $t('form.password-confirm') }}</label>
+ <div class="mb-3">
+ <label for="reset_confirm" class="visually-hidden">{{ $t('form.password-confirm') }}</label>
<input type="password" class="form-control" id="reset_confirm" :placeholder="$t('form.password-confirm')" required v-model="password_confirmation">
</div>
<button class="btn btn-secondary" type="button" @click="stepBack">{{ $t('btn.back') }}</button>
diff --git a/src/resources/vue/Reseller/Invitations.vue b/src/resources/vue/Reseller/Invitations.vue
--- a/src/resources/vue/Reseller/Invitations.vue
+++ b/src/resources/vue/Reseller/Invitations.vue
@@ -9,19 +9,17 @@
<div class="mb-2 d-flex">
<form @submit.prevent="searchInvitations" id="search-form" class="input-group" style="flex:1">
<input class="form-control" type="text" :placeholder="$t('invitation.search')" v-model="search">
- <div class="input-group-append">
- <button type="submit" class="btn btn-primary"><svg-icon icon="search"></svg-icon> {{ $t('btn.search') }}</button>
- </div>
+ <button type="submit" class="btn btn-primary"><svg-icon icon="search"></svg-icon> {{ $t('btn.search') }}</button>
</form>
<div>
- <button class="btn btn-success create-invite ml-1" @click="inviteUserDialog">
+ <button class="btn btn-success create-invite ms-1" @click="inviteUserDialog">
<svg-icon icon="envelope-open-text"></svg-icon> {{ $t('invitation.create') }}
</button>
</div>
</div>
<table id="invitations-list" class="table table-sm table-hover">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('user.ext-email') }}</th>
<th scope="col">{{ $t('form.created') }}</th>
@@ -38,11 +36,11 @@
{{ inv.created }}
</td>
<td class="buttons">
- <button class="btn text-danger button-delete p-0 ml-1" @click="deleteInvite(inv.id)">
+ <button class="btn text-danger button-delete p-0 ms-1" @click="deleteInvite(inv.id)">
<svg-icon icon="trash-alt"></svg-icon>
<span class="btn-label">{{ $t('btn.delete') }}</span>
</button>
- <button class="btn button-resend p-0 ml-1" :disabled="inv.isNew || inv.isCompleted" @click="resendInvite(inv.id)">
+ <button class="btn button-resend p-0 ms-1" :disabled="inv.isNew || inv.isCompleted" @click="resendInvite(inv.id)">
<svg-icon icon="redo"></svg-icon>
<span class="btn-label">{{ $t('btn.resend') }}</span>
</button>
@@ -67,9 +65,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('invitation.create-title') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<form>
@@ -79,14 +75,13 @@
</div>
<div class="form-separator"><hr><span>{{ $t('form.or') }}</span></div>
<p>{{ $t('invitation.create-csv') }}</p>
- <div class="custom-file">
- <input id="file" type="file" class="custom-file-input" name="csv" @change="fileChange">
- <label class="custom-file-label" for="file">{{ $t('btn.file') }}</label>
+ <div>
+ <input id="file" type="file" class="form-control" name="csv">
</div>
</form>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="button" class="btn btn-primary modal-action" @click="inviteUser()">
<svg-icon icon="paper-plane"></svg-icon> {{ $t('invitation.send') }}
</button>
@@ -98,6 +93,7 @@
</template>
<script>
+ import { Modal } from 'bootstrap'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faEnvelopeOpenText, faPaperPlane, faRedo } from '@fortawesome/free-solid-svg-icons'
@@ -115,6 +111,10 @@
mounted() {
this.$root.startLoading()
this.loadInvitations(null, () => this.$root.stopLoading())
+
+ $('#invite-create')[0].addEventListener('shown.bs.modal', event => {
+ $('input', event.target).first().focus()
+ })
},
methods: {
deleteInvite(id) {
@@ -160,7 +160,7 @@
axios.post('/api/v4/invitations', post, params)
.then(response => {
if (response.data.status == 'success') {
- dialog.modal('hide')
+ this.dialog.hide()
this.$toast.success(response.data.message)
if (response.data.count) {
this.loadInvitations({ reset: true })
@@ -169,16 +169,15 @@
})
},
inviteUserDialog() {
- let dialog = $('#invite-create')
- let form = dialog.find('form')
+ const dialog = $('#invite-create')[0]
+ const form = $('form', dialog)
form.get(0).reset()
this.fileChange({ target: form.find('#file')[0] }) // resets file input label
this.$root.clearFormValidation(form)
- dialog.on('shown.bs.modal', () => {
- dialog.find('input').get(0).focus()
- }).modal()
+ this.dialog = new Modal(dialog)
+ this.dialog.show()
},
loadInvitations(params, callback) {
let loader
diff --git a/src/resources/vue/Reseller/Stats.vue b/src/resources/vue/Reseller/Stats.vue
--- a/src/resources/vue/Reseller/Stats.vue
+++ b/src/resources/vue/Reseller/Stats.vue
@@ -9,7 +9,6 @@
mixins: [Stats],
data() {
return {
-// charts: {},
chartTypes: ['users', 'users-all', 'discounts']
}
}
diff --git a/src/resources/vue/Signup.vue b/src/resources/vue/Signup.vue
--- a/src/resources/vue/Signup.vue
+++ b/src/resources/vue/Signup.vue
@@ -1,16 +1,18 @@
<template>
<div class="container">
<div id="step0" v-if="!invitation">
- <div class="plan-selector card-deck">
- <div v-for="item in plans" :key="item.id" :class="'card bg-light plan-' + item.title">
- <div class="card-header plan-header">
- <div class="plan-ico text-center">
- <svg-icon :icon="plan_icons[item.title]"></svg-icon>
+ <div class="plan-selector row row-cols-sm-2 g-3">
+ <div v-for="item in plans" :key="item.id">
+ <div :class="'card bg-light plan-' + item.title">
+ <div class="card-header plan-header">
+ <div class="plan-ico text-center">
+ <svg-icon :icon="plan_icons[item.title]"></svg-icon>
+ </div>
+ </div>
+ <div class="card-body text-center">
+ <button class="btn btn-primary" :data-title="item.title" @click="selectPlan(item.title)" v-html="item.button"></button>
+ <div class="plan-description text-start mt-3" v-html="item.description"></div>
</div>
- </div>
- <div class="card-body text-center ">
- <button class="btn btn-primary" :data-title="item.title" @click="selectPlan(item.title)" v-html="item.button"></button>
- <div class="plan-description text-left mt-3" v-html="item.description"></div>
</div>
</div>
</div>
@@ -23,14 +25,14 @@
{{ $t('signup.step1') }}
</p>
<form @submit.prevent="submitStep1" data-validation-prefix="signup_">
- <div class="form-group">
+ <div class="mb-3">
<div class="input-group">
<input type="text" class="form-control" id="signup_first_name" :placeholder="$t('form.firstname')" autofocus v-model="first_name">
- <input type="text" class="form-control rounded-right" id="signup_last_name" :placeholder="$t('form.surname')" v-model="last_name">
+ <input type="text" class="form-control rounded-end" id="signup_last_name" :placeholder="$t('form.surname')" v-model="last_name">
</div>
</div>
- <div class="form-group">
- <label for="signup_email" class="sr-only">{{ $t('signup.email') }}</label>
+ <div class="mb-3">
+ <label for="signup_email" class="visually-hidden">{{ $t('signup.email') }}</label>
<input type="text" class="form-control" id="signup_email" :placeholder="$t('signup.email')" required v-model="email">
</div>
<button class="btn btn-secondary" type="button" @click="stepBack">{{ $t('btn.back') }}</button>
@@ -46,8 +48,8 @@
{{ $t('signup.step2') }}
</p>
<form @submit.prevent="submitStep2" data-validation-prefix="signup_">
- <div class="form-group">
- <label for="signup_short_code" class="sr-only">{{ $t('form.code') }}</label>
+ <div class="mb-3">
+ <label for="signup_short_code" class="visually-hidden">{{ $t('form.code') }}</label>
<input type="text" class="form-control" id="signup_short_code" :placeholder="$t('form.code')" required v-model="short_code">
</div>
<button class="btn btn-secondary" type="button" @click="stepBack">{{ $t('btn.back') }}</button>
@@ -64,35 +66,33 @@
{{ $t('signup.step3') }}
</p>
<form @submit.prevent="submitStep3" data-validation-prefix="signup_">
- <div class="form-group" v-if="invitation">
+ <div class="mb-3" v-if="invitation">
<div class="input-group">
<input type="text" class="form-control" id="signup_first_name" :placeholder="$t('form.firstname')" autofocus v-model="first_name">
- <input type="text" class="form-control rounded-right" id="signup_last_name" :placeholder="$t('form.surname')" v-model="last_name">
+ <input type="text" class="form-control rounded-end" id="signup_last_name" :placeholder="$t('form.surname')" v-model="last_name">
</div>
</div>
- <div class="form-group">
- <label for="signup_login" class="sr-only"></label>
+ <div class="mb-3">
+ <label for="signup_login" class="visually-hidden"></label>
<div class="input-group">
<input type="text" class="form-control" id="signup_login" required v-model="login" :placeholder="$t('signup.login')">
- <span class="input-group-append input-group-prepend">
- <span class="input-group-text">@</span>
- </span>
- <input v-if="is_domain" type="text" class="form-control rounded-right" id="signup_domain" required v-model="domain" :placeholder="$t('form.domain')">
- <select v-else class="custom-select rounded-right" id="signup_domain" required v-model="domain">
+ <span class="input-group-text">@</span>
+ <input v-if="is_domain" type="text" class="form-control rounded-end" id="signup_domain" required v-model="domain" :placeholder="$t('form.domain')">
+ <select v-else class="form-select rounded-end" id="signup_domain" required v-model="domain">
<option v-for="domain in domains" :key="domain" :value="domain">{{ domain }}</option>
</select>
</div>
</div>
- <div class="form-group">
- <label for="signup_password" class="sr-only">{{ $t('form.password') }}</label>
+ <div class="mb-3">
+ <label for="signup_password" class="visually-hidden">{{ $t('form.password') }}</label>
<input type="password" class="form-control" id="signup_password" :placeholder="$t('form.password')" required v-model="password">
</div>
- <div class="form-group">
- <label for="signup_confirm" class="sr-only">{{ $t('form.password-confirm') }}</label>
+ <div class="mb-3">
+ <label for="signup_confirm" class="visually-hidden">{{ $t('form.password-confirm') }}</label>
<input type="password" class="form-control" id="signup_confirm" :placeholder="$t('form.password-confirm')" required v-model="password_confirmation">
</div>
- <div class="form-group pt-2 pb-2">
- <label for="signup_voucher" class="sr-only">{{ $t('signup.voucher') }}</label>
+ <div class="mb-3 pt-2">
+ <label for="signup_voucher" class="visually-hidden">{{ $t('signup.voucher') }}</label>
<input type="text" class="form-control" id="signup_voucher" :placeholder="$t('signup.voucher')" v-model="voucher">
</div>
<button v-if="!invitation" class="btn btn-secondary" type="button" @click="stepBack">{{ $t('btn.back') }}</button>
diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue
--- a/src/resources/vue/User/Info.vue
+++ b/src/resources/vue/User/Info.vue
@@ -6,7 +6,7 @@
<div class="card-body">
<div class="card-title" v-if="user_id !== 'new'">{{ $t('user.title') }}
<button
- class="btn btn-outline-danger button-delete float-right"
+ class="btn btn-outline-danger button-delete float-end"
@click="showDeleteConfirmation()" type="button"
>
<svg-icon icon="trash-alt"></svg-icon> {{ $t('user.delete') }}
@@ -30,59 +30,59 @@
<div class="tab-pane show active" id="general" role="tabpanel" aria-labelledby="tab-general">
<div class="card-body">
<form @submit.prevent="submit">
- <div v-if="user_id !== 'new'" class="form-group row plaintext">
+ <div v-if="user_id !== 'new'" class="row plaintext mb-3">
<label for="status" class="col-sm-4 col-form-label">{{ $t('form.status') }}</label>
<div class="col-sm-8">
<span :class="$root.userStatusClass(user) + ' form-control-plaintext'" id="status">{{ $root.userStatusText(user) }}</span>
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="first_name" class="col-sm-4 col-form-label">{{ $t('form.firstname') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="first_name" v-model="user.first_name">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="last_name" class="col-sm-4 col-form-label">{{ $t('form.lastname') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="last_name" v-model="user.last_name">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="organization" class="col-sm-4 col-form-label">{{ $t('user.org') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="organization" v-model="user.organization">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="email" class="col-sm-4 col-form-label">{{ $t('form.email') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="email" :disabled="user_id !== 'new'" required v-model="user.email">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="aliases-input" class="col-sm-4 col-form-label">{{ $t('user.aliases-email') }}</label>
<div class="col-sm-8">
<list-input id="aliases" :list="user.aliases"></list-input>
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="password" class="col-sm-4 col-form-label">{{ $t('form.password') }}</label>
<div class="col-sm-8">
<input type="password" class="form-control" id="password" v-model="user.password" :required="user_id === 'new'">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="password_confirmaton" class="col-sm-4 col-form-label">{{ $t('form.password-confirm') }}</label>
<div class="col-sm-8">
<input type="password" class="form-control" id="password_confirmation" v-model="user.password_confirmation" :required="user_id === 'new'">
</div>
</div>
- <div v-if="user_id === 'new'" id="user-packages" class="form-group row">
+ <div v-if="user_id === 'new'" id="user-packages" class="row mb-3">
<label class="col-sm-4 col-form-label">Package</label>
<div class="col-sm-8">
<table class="table table-sm form-list">
- <thead class="thead-light sr-only">
+ <thead class="visually-hidden">
<tr>
<th scope="col"></th>
<th scope="col">{{ $t('user.package') }}</th>
@@ -106,9 +106,9 @@
{{ $root.priceLabel(pkg.cost, discount) }}
</td>
<td class="buttons">
- <button v-if="pkg.description" type="button" class="btn btn-link btn-lg p-0" v-tooltip.click="pkg.description">
+ <button v-if="pkg.description" type="button" class="btn btn-link btn-lg p-0" v-tooltip="pkg.description">
<svg-icon icon="info-circle"></svg-icon>
- <span class="sr-only">{{ $t('btn.moreinfo') }}</span>
+ <span class="visually-hidden">{{ $t('btn.moreinfo') }}</span>
</button>
</td>
</tr>
@@ -120,11 +120,11 @@
</small>
</div>
</div>
- <div v-if="user_id !== 'new'" id="user-skus" class="form-group row">
+ <div v-if="user_id !== 'new'" id="user-skus" class="row mb-3">
<label class="col-sm-4 col-form-label">{{ $t('user.subscriptions') }}</label>
<div class="col-sm-8">
<table class="table table-sm form-list">
- <thead class="thead-light sr-only">
+ <thead class="visually-hidden">
<tr>
<th scope="col"></th>
<th scope="col">{{ $t('user.subscription') }}</th>
@@ -147,7 +147,7 @@
<div v-if="sku.range" class="range-input">
<label class="text-nowrap">{{ sku.range.min }} {{ sku.range.unit }}</label>
<input
- type="range" class="custom-range" @input="rangeUpdate"
+ type="range" class="form-range" @input="rangeUpdate"
:value="sku.value || sku.range.min"
:min="sku.range.min"
:max="sku.range.max"
@@ -158,9 +158,9 @@
{{ $root.priceLabel(sku.cost, discount) }}
</td>
<td class="buttons">
- <button v-if="sku.description" type="button" class="btn btn-link btn-lg p-0" v-tooltip.click="sku.description">
+ <button v-if="sku.description" type="button" class="btn btn-link btn-lg p-0" v-tooltip="sku.description">
<svg-icon icon="info-circle"></svg-icon>
- <span class="sr-only">{{ $t('btn.moreinfo') }}</span>
+ <span class="visually-hidden">{{ $t('btn.moreinfo') }}</span>
</button>
</td>
</tr>
@@ -179,11 +179,11 @@
<div class="tab-pane" id="settings" role="tabpanel" aria-labelledby="tab-settings">
<div class="card-body">
<form @submit.prevent="submitSettings">
- <div class="form-group row checkbox">
+ <div class="row checkbox mb-3">
<label for="greylisting" class="col-sm-4 col-form-label">{{ $t('user.greylisting') }}</label>
<div class="col-sm-8 pt-2">
- <input type="checkbox" id="greylisting" name="greylisting" value="1" :checked="user.config.greylisting">
- <small id="greylisting-hint" class="form-text text-muted">
+ <input type="checkbox" id="greylisting" name="greylisting" value="1" class="form-check-input d-block mb-2" :checked="user.config.greylisting">
+ <small id="greylisting-hint" class="text-muted">
{{ $t('user.greylisting-text') }}
</small>
</div>
@@ -201,15 +201,13 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $t('user.delete-email', { email: user.email }) }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<p>{{ $t('user.delete-text') }}</p>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="button" class="btn btn-danger modal-action" @click="deleteUser()">
<svg-icon icon="trash-alt"></svg-icon> {{ $t('btn.delete') }}
</button>
@@ -221,6 +219,7 @@
</template>
<script>
+ import { Modal } from 'bootstrap'
import ListInput from '../Widgets/ListInput'
import StatusComponent from '../Widgets/Status'
@@ -312,6 +311,9 @@
},
mounted() {
$('#first_name').focus()
+ $('#delete-warning')[0].addEventListener('shown.bs.modal', event => {
+ $(event.target).find('button.modal-cancel').focus()
+ })
},
methods: {
submit() {
@@ -469,15 +471,12 @@
})
},
showDeleteConfirmation() {
- // Deleting self, redirect to /profile/delete page
if (this.user_id == this.$store.state.authInfo.id) {
+ // Deleting self, redirect to /profile/delete page
this.$router.push({ name: 'profile-delete' })
} else {
// Display the warning
- let dialog = $('#delete-warning')
- dialog.on('shown.bs.modal', () => {
- dialog.find('button.modal-cancel').focus()
- }).modal()
+ new Modal('#delete-warning').show()
}
}
}
diff --git a/src/resources/vue/User/List.vue b/src/resources/vue/User/List.vue
--- a/src/resources/vue/User/List.vue
+++ b/src/resources/vue/User/List.vue
@@ -4,13 +4,13 @@
<div class="card-body">
<div class="card-title">
{{ $t('user.list-title') }}
- <router-link class="btn btn-success float-right create-user" :to="{ path: 'user/new' }" tag="button">
+ <router-link class="btn btn-success float-end create-user" :to="{ path: 'user/new' }" tag="button">
<svg-icon icon="user"></svg-icon> {{ $t('user.create') }}
</router-link>
</div>
<div class="card-text">
<table class="table table-sm table-hover">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('form.primary-email') }}</th>
</tr>
diff --git a/src/resources/vue/User/Profile.vue b/src/resources/vue/User/Profile.vue
--- a/src/resources/vue/User/Profile.vue
+++ b/src/resources/vue/User/Profile.vue
@@ -6,7 +6,7 @@
{{ $t('user.profile-title') }}
<router-link
v-if="$root.isController(wallet_id)"
- class="btn btn-outline-danger button-delete float-right"
+ class="btn btn-outline-danger button-delete float-end"
to="/profile/delete" tag="button"
>
<svg-icon icon="trash-alt"></svg-icon> {{ $t('user.profile-delete') }}
@@ -14,70 +14,70 @@
</div>
<div class="card-text">
<form @submit.prevent="submit">
- <div class="form-group row plaintext">
+ <div class="row mb-3 plaintext">
<label class="col-sm-4 col-form-label">{{ $t('user.custno') }}</label>
<div class="col-sm-8">
<span class="form-control-plaintext" id="userid">{{ user_id }}</span>
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="first_name" class="col-sm-4 col-form-label">{{ $t('form.firstname') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="first_name" v-model="profile.first_name">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="last_name" class="col-sm-4 col-form-label">{{ $t('form.lastname') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="last_name" v-model="profile.last_name">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="organization" class="col-sm-4 col-form-label">{{ $t('user.org') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="organization" v-model="profile.organization">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="phone" class="col-sm-4 col-form-label">{{ $t('form.phone') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="phone" v-model="profile.phone">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="external_email" class="col-sm-4 col-form-label">{{ $t('user.ext-email') }}</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="external_email" v-model="profile.external_email">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="billing_address" class="col-sm-4 col-form-label">{{ $t('user.address') }}</label>
<div class="col-sm-8">
<textarea class="form-control" id="billing_address" rows="3" v-model="profile.billing_address"></textarea>
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="country" class="col-sm-4 col-form-label">{{ $t('user.country') }}</label>
<div class="col-sm-8">
- <select class="form-control custom-select" id="country" v-model="profile.country">
+ <select class="form-select" id="country" v-model="profile.country">
<option value="">-</option>
<option v-for="(item, code) in countries" :value="code" :key="code">{{ item[1] }}</option>
</select>
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="password" class="col-sm-4 col-form-label">{{ $t('form.password') }}</label>
<div class="col-sm-8">
<input type="password" class="form-control" id="password" v-model="profile.password">
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="password_confirmaton" class="col-sm-4 col-form-label">{{ $t('form.password-confirm') }}</label>
<div class="col-sm-8">
<input type="password" class="form-control" id="password_confirmation" v-model="profile.password_confirmation">
</div>
</div>
- <button class="btn btn-primary button-submit" type="submit"><svg-icon icon="check"></svg-icon> {{ $t('btn.submit') }}</button>
+ <button class="btn btn-primary button-submit mt-2" type="submit"><svg-icon icon="check"></svg-icon> {{ $t('btn.submit') }}</button>
</form>
</div>
</div>
diff --git a/src/resources/vue/User/ProfileDelete.vue b/src/resources/vue/User/ProfileDelete.vue
--- a/src/resources/vue/User/ProfileDelete.vue
+++ b/src/resources/vue/User/ProfileDelete.vue
@@ -2,7 +2,7 @@
<div class="container">
<div class="card" id="user-delete">
<div class="card-body">
- <div class="card-title">Delete this account?</div>
+ <div class="card-title">{{ $t('user.delete-account') }}</div>
<div class="card-text">
<p>{{ $t('user.profile-delete-text1') }} <strong>{{ $t('user.profile-delete-warning') }}</strong>.</p>
<p>{{ $t('user.profile-delete-text2') }}</p>
diff --git a/src/resources/vue/Wallet.vue b/src/resources/vue/Wallet.vue
--- a/src/resources/vue/Wallet.vue
+++ b/src/resources/vue/Wallet.vue
@@ -69,11 +69,9 @@
<select id="receipt-id" class="form-control">
<option v-for="(receipt, index) in receipts" :key="index" :value="receipt">{{ receipt }}</option>
</select>
- <div class="input-group-append">
- <button type="button" class="btn btn-secondary" @click="receiptDownload">
- <svg-icon icon="download"></svg-icon> {{ $t('btn.download') }}
- </button>
- </div>
+ <button type="button" class="btn btn-secondary" @click="receiptDownload">
+ <svg-icon icon="download"></svg-icon> {{ $t('btn.download') }}
+ </button>
</div>
<p v-if="!receipts.length">
{{ $t('wallet.receipts-none') }}
@@ -98,9 +96,7 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ paymentDialogTitle }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
<div id="payment-method" v-if="paymentForm == 'method'">
@@ -125,15 +121,11 @@
{{ $t('wallet.payment-amount-hint') }}
</p>
<form id="payment-form" @submit.prevent="payment">
- <div class="form-group">
- <div class="input-group">
- <input type="text" class="form-control" id="amount" v-model="amount" required>
- <span class="input-group-append">
- <span class="input-group-text">{{ wallet.currency }}</span>
- </span>
- </div>
+ <div class="input-group">
+ <input type="text" class="form-control" id="amount" v-model="amount" required>
+ <span class="input-group-text">{{ wallet.currency }}</span>
</div>
- <div v-if="wallet.currency != selectedPaymentMethod.currency && !isNaN(amount)" class="alert alert-warning">
+ <div v-if="wallet.currency != selectedPaymentMethod.currency && !isNaN(amount)" class="alert alert-warning m-0 mt-3">
{{ $t('wallet.payment-warning', { price: $root.price(amount * selectedPaymentMethod.exchangeRate * 100, selectedPaymentMethod.currency) }) }}
</div>
</form>
@@ -143,37 +135,35 @@
<p>
{{ $t('wallet.auto-payment-hint') }}
</p>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="mandate_amount" class="col-sm-6 col-form-label">{{ $t('wallet.fill-up') }}</label>
- <div class="input-group col-sm-6">
- <input type="text" class="form-control" id="mandate_amount" v-model="mandate.amount" required>
- <span class="input-group-append">
+ <div class="col-sm-6">
+ <div class="input-group">
+ <input type="text" class="form-control" id="mandate_amount" v-model="mandate.amount" required>
<span class="input-group-text">{{ wallet.currency }}</span>
- </span>
+ </div>
</div>
</div>
- <div class="form-group row">
+ <div class="row mb-3">
<label for="mandate_balance" class="col-sm-6 col-form-label">{{ $t('wallet.when-below') }}</label>
<div class="col-sm-6">
<div class="input-group">
<input type="text" class="form-control" id="mandate_balance" v-model="mandate.balance" required>
- <span class="input-group-append">
- <span class="input-group-text">{{ wallet.currency }}</span>
- </span>
+ <span class="input-group-text">{{ wallet.currency }}</span>
</div>
</div>
</div>
<p v-if="!mandate.isValid">
{{ $t('wallet.auto-payment-next') }}
</p>
- <div v-if="mandate.isValid && mandate.isDisabled" class="disabled-mandate alert alert-danger">
+ <div v-if="mandate.isValid && mandate.isDisabled" class="disabled-mandate alert alert-danger m-0">
{{ $t('wallet.auto-payment-disabled-next') }}
</div>
</form>
</div>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="button" class="btn btn-primary modal-action"
v-if="paymentForm == 'auto' && (mandate.isValid || mandate.isPending)"
@click="autoPayment"
@@ -200,6 +190,7 @@
</template>
<script>
+ import { Modal } from 'bootstrap'
import TransactionLog from './Widgets/TransactionLog'
import PaymentLog from './Widgets/PaymentLog'
@@ -261,16 +252,14 @@
.then(response => {
this.showPendingPayments = response.data.hasPending
})
-
},
updated() {
$(this.$el).find('ul.nav-tabs a').on('click', e => {
- e.preventDefault()
- $(e.target).tab('show')
+ this.$root.tab(e)
+
if ($(e.target).is('#tab-history')) {
this.loadTransactions = true
- }
- if ($(e.target).is('#tab-payments')) {
+ } else if ($(e.target).is('#tab-payments')) {
this.loadPayments = true
}
})
@@ -295,16 +284,10 @@
},
selectPaymentMethod(method) {
this.formLock = false
-
this.selectedPaymentMethod = method
-
this.paymentForm = this.nextForm
- this.formLock = false
- setTimeout(() => {
- this.dialog.find('#mandate_amount').focus()
- this.dialog.find('#amount').focus()
- }, 10)
+ setTimeout(() => { $('#payment-dialog').find('#amount,#mandate_amount').focus() }, 10)
},
payment() {
if (this.formLock) {
@@ -362,7 +345,7 @@
} else {
// an update
if (response.data.status == 'success') {
- this.dialog.modal('hide');
+ this.dialog.hide();
this.mandate = response.data
this.$toast.success(response.data.message)
}
@@ -381,43 +364,34 @@
}
})
},
-
paymentMethodForm(nextForm) {
- const dialog = $('#payment-dialog')
this.formLock = false
this.paymentMethods = []
-
this.paymentForm = 'method'
this.nextForm = nextForm
- if (nextForm == 'auto') {
- this.paymentDialogTitle = this.$t('wallet.auto-payment-setup')
- } else {
- this.paymentDialogTitle = this.$t('wallet.top-up')
- }
+ this.paymentDialogTitle = this.$t(nextForm == 'auto' ? 'wallet.auto-payment-setup' : 'wallet.top-up')
- const methods = $('#payment-method')
- this.$root.addLoader(methods, false)
- axios.get('/api/v4/payments/methods', {params: {type: nextForm == 'manual' ? 'oneoff' : 'recurring'}})
- .then(response => {
- this.$root.removeLoader(methods)
- this.paymentMethods = response.data
- })
- .catch(this.$root.errorHandler)
+ this.dialog = new Modal('#payment-dialog')
+ this.dialog.show()
- this.dialog = dialog.on('shown.bs.modal', () => {}).modal()
+ this.$nextTick().then(() => {
+ const form = $('#payment-method')
+ this.$root.addLoader(form, false, {'min-height': '10em'})
+
+ axios.get('/api/v4/payments/methods', {params: {type: nextForm == 'manual' ? 'oneoff' : 'recurring'}})
+ .then(response => {
+ this.$root.removeLoader(form)
+ this.paymentMethods = response.data
+ })
+ })
},
autoPaymentForm(event, title) {
- const dialog = $('#payment-dialog')
-
this.paymentForm = 'auto'
this.paymentDialogTitle = title
this.formLock = false
- this.dialog = dialog.on('shown.bs.modal', () => {
- dialog.find('#mandate_amount').focus()
- }).modal()
-
- setTimeout(() => { this.dialog.find('#mandate_amount').focus()}, 10)
+ this.dialog = new Modal('#payment-dialog')
+ this.dialog.show()
},
receiptDownload() {
const receipt = $('#receipt-id').val()
diff --git a/src/resources/vue/Widgets/ListInput.vue b/src/resources/vue/Widgets/ListInput.vue
--- a/src/resources/vue/Widgets/ListInput.vue
+++ b/src/resources/vue/Widgets/ListInput.vue
@@ -2,21 +2,17 @@
<div class="list-input" :id="id">
<div class="input-group">
<input :id="id + '-input'" type="text" class="form-control main-input" @keydown="keyDown">
- <div class="input-group-append">
- <a href="#" class="btn btn-outline-secondary" @click.prevent="addItem">
- <svg-icon icon="plus"></svg-icon>
- <span class="sr-only">{{ $t('btn.add') }}</span>
- </a>
- </div>
+ <a href="#" class="btn btn-outline-secondary" @click.prevent="addItem">
+ <svg-icon icon="plus"></svg-icon>
+ <span class="visually-hidden">{{ $t('btn.add') }}</span>
+ </a>
</div>
<div class="input-group" v-for="(item, index) in list" :key="index">
<input type="text" class="form-control" v-model="list[index]">
- <div class="input-group-append">
- <a href="#" class="btn btn-outline-secondary" @click.prevent="deleteItem(index)">
- <svg-icon icon="trash-alt"></svg-icon>
- <span class="sr-only">{{ $t('btn.delete') }}</span>
- </a>
- </div>
+ <a href="#" class="btn btn-outline-secondary" @click.prevent="deleteItem(index)">
+ <svg-icon icon="trash-alt"></svg-icon>
+ <span class="visually-hidden">{{ $t('btn.delete') }}</span>
+ </a>
</div>
</div>
</template>
diff --git a/src/resources/vue/Widgets/Menu.vue b/src/resources/vue/Widgets/Menu.vue
--- a/src/resources/vue/Widgets/Menu.vue
+++ b/src/resources/vue/Widgets/Menu.vue
@@ -1,15 +1,15 @@
<template>
- <nav :id="mode + '-menu'" class="navbar navbar-expand-lg navbar-light">
- <div class="container">
+ <nav :id="mode + '-menu'" :class="'navbar navbar-light navbar-expand-' + (mode == 'header' ? 'lg' : 'sm')">
+ <div class="container p-0">
<router-link class="navbar-brand" to="/" v-html="$root.logo(mode)"></router-link>
<button v-if="mode == 'header'" class="navbar-toggler" type="button"
- data-toggle="collapse" :data-target="'#' + mode + '-menu-navbar'"
- aria-controls="navbar" aria-expanded="false" :aria-label="$t('menu.toggle')"
+ data-bs-toggle="collapse" data-bs-target="#header-menu-navbar"
+ aria-controls="header-menu-navbar" aria-expanded="false" :aria-label="$t('menu.toggle')"
>
<span class="navbar-toggler-icon"></span>
</button>
- <div :id="mode + '-menu-navbar'" :class="'navbar' + (mode == 'header' ? ' collapse navbar-collapse' : '')">
- <ul class="navbar-nav">
+ <div :id="mode + '-menu-navbar'" :class="mode == 'header' ? 'collapse navbar-collapse justify-content-end' : ''">
+ <ul class="navbar-nav justify-content-end">
<li class="nav-item" v-for="item in menu" :key="item.index">
<a v-if="item.href" :class="'nav-link link-' + item.index" :href="item.href">{{ item.title }}</a>
<router-link v-if="item.to"
@@ -34,8 +34,8 @@
<router-link class="nav-link menulogin link-login" :to="{name: 'login'}">{{ $t('menu.login') }}</router-link>
</li>
<li v-if="languages.length > 1 && mode == 'header'" id="language-selector" class="nav-item dropdown">
- <a href="#" class="nav-link link-lang dropdown-toggle" role="button" data-toggle="dropdown">{{ getLang().toUpperCase() }}</a>
- <div class="dropdown-menu dropdown-menu-right">
+ <a href="#" class="nav-link link-lang dropdown-toggle" role="button" data-bs-toggle="dropdown">{{ getLang().toUpperCase() }}</a>
+ <div class="dropdown-menu dropdown-menu-right mb-2">
<a class="dropdown-item" href="#" v-for="lang in languages" :key="lang" @click="setLang(lang)">
{{ lang.toUpperCase() }} - {{ $t('lang.' + lang) }}
</a>
diff --git a/src/resources/vue/Widgets/PaymentLog.vue b/src/resources/vue/Widgets/PaymentLog.vue
--- a/src/resources/vue/Widgets/PaymentLog.vue
+++ b/src/resources/vue/Widgets/PaymentLog.vue
@@ -1,7 +1,7 @@
<template>
<div>
<table class="table table-sm m-0 payments">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('form.date') }}</th>
<th scope="col">{{ $t('form.description') }}</th>
diff --git a/src/resources/vue/Widgets/SupportForm.vue b/src/resources/vue/Widgets/SupportForm.vue
--- a/src/resources/vue/Widgets/SupportForm.vue
+++ b/src/resources/vue/Widgets/SupportForm.vue
@@ -4,37 +4,35 @@
<form class="modal-content" @submit.prevent="submit">
<div class="modal-header">
<h5 class="modal-title">{{ $t('support.title') }}</h5>
- <button type="button" class="close" data-dismiss="modal" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
</div>
<div class="modal-body">
- <div class="form">
- <div class="form-group">
- <label for="support-user">{{ $t('support.id') }}</label>
+ <form>
+ <div class="mb-3">
+ <label for="support-user" class="form-label">{{ $t('support.id') }}</label>
<input id="support-user" type="text" class="form-control" :placeholder="$t('support.id-pl')" v-model="user" />
- <small class="form-text text-muted">{{ $t('support.id-hint') }}</small>
+ <small class="text-muted">{{ $t('support.id-hint') }}</small>
</div>
- <div class="form-group">
- <label for="support-name">{{ $t('support.name') }}</label>
+ <div class="mb-3">
+ <label for="support-name" class="form-label">{{ $t('support.name') }}</label>
<input id="support-name" type="text" class="form-control" :placeholder="$t('support.name-pl')" v-model="name" />
</div>
- <div class="form-group">
- <label for="support-email">{{ $t('support.email') }}</label>
+ <div class="mb-3">
+ <label for="support-email" class="form-label">{{ $t('support.email') }}</label>
<input id="support-email" type="email" class="form-control" :placeholder="$t('support.email-pl')" v-model="email" required />
</div>
- <div class="form-group">
- <label for="support-summary">{{ $t('support.summary') }}</label>
+ <div class="mb-3">
+ <label for="support-summary" class="form-label">{{ $t('support.summary') }}</label>
<input id="support-summary" type="text" class="form-control" :placeholder="$t('support.summary-pl')" v-model="summary" required />
</div>
- <div class="form-group">
- <label for="support-body">{{ $t('support.expl') }}</label>
+ <div>
+ <label for="support-body" class="form-label">{{ $t('support.expl') }}</label>
<textarea id="support-body" class="form-control" rows="5" v-model="body" required></textarea>
</div>
- </div>
+ </form>
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-secondary modal-cancel" data-dismiss="modal">{{ $t('btn.cancel') }}</button>
+ <button type="button" class="btn btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</button>
<button type="submit" class="btn btn-primary modal-action"><svg-icon icon="check"></svg-icon> {{ $t('btn.submit') }}</button>
</div>
</form>
@@ -43,6 +41,8 @@
</template>
<script>
+ import { Modal } from 'bootstrap'
+
export default {
data() {
return {
@@ -54,21 +54,32 @@
}
},
mounted() {
- this.dialog = $('#support-dialog')
- .on('hide.bs.modal', () => {
- this.lockForm(false)
- if (this.cancelToken) {
- this.cancelToken()
- this.cancelToken = null
- }
- })
- .on('show.bs.modal', () => {
+ const dialog = this.$el
+
+ dialog.addEventListener('hide.bs.modal', () => {
+ this.lockForm(false)
+ if (this.cancelToken) {
+ this.cancelToken()
this.cancelToken = null
- })
+ }
+ })
+
+ dialog.addEventListener('show.bs.modal', () => {
+ this.cancelToken = null
+ })
+
+ dialog.addEventListener('shown.bs.modal', () => {
+ $(dialog).find('input').first().focus()
+ })
+
+ this.dialog = new Modal(dialog)
},
methods: {
lockForm(lock) {
- this.dialog.find('input,textarea,.modal-action').prop('disabled', lock)
+ $(this.$el).find('input,textarea,.modal-action').prop('disabled', lock)
+ },
+ showDialog() {
+ this.dialog.show()
},
submit() {
this.lockForm(true)
@@ -94,7 +105,7 @@
this.summary = ''
this.body = ''
this.lockForm(false)
- this.dialog.modal('hide')
+ this.dialog.hide()
this.$toast.success(response.data.message)
})
.catch(error => {
diff --git a/src/resources/vue/Widgets/ToastMessage.vue b/src/resources/vue/Widgets/ToastMessage.vue
--- a/src/resources/vue/Widgets/ToastMessage.vue
+++ b/src/resources/vue/Widgets/ToastMessage.vue
@@ -7,9 +7,7 @@
<svg-icon icon="exclamation-circle" v-else-if="data.type == 'warning'"></svg-icon>
<svg-icon :icon="data.icon" v-else-if="data.type == 'custom' && data.icon"></svg-icon>
<strong>{{ data.title || $t('msg.' + data.type) }}</strong>
- <button type="button" class="close" data-dismiss="toast" :aria-label="$t('btn.close')">
- <span aria-hidden="true">×</span>
- </button>
+ <button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" :aria-label="$t('btn.close')"></button>
</div>
<div v-if="data.body" v-html="data.body" class="toast-body"></div>
<div v-else class="toast-body">{{ data.msg }}</div>
@@ -17,27 +15,29 @@
</template>
<script>
+ import { Toast } from 'bootstrap'
+
export default {
props: {
data: { type: Object, default: () => {} }
},
mounted() {
- $(this.$el)
- .on('hidden.bs.toast', () => {
- (this.$el).remove()
- this.$destroy()
- })
- .on('shown.bs.toast', () => {
- if (this.data.onShow) {
- this.data.onShow(this.$el)
- }
- })
- .toast({
+ this.$el.addEventListener('hidden.bs.toast', () => {
+ (this.$el).remove()
+ this.$destroy()
+ })
+
+ this.$el.addEventListener('shown.bs.toast', () => {
+ if (this.data.onShow) {
+ this.data.onShow(this.$el)
+ }
+ })
+
+ new Toast(this.$el, {
animation: true,
autohide: this.data.timeout > 0,
delay: this.data.timeout
- })
- .toast('show')
+ }).show()
},
methods: {
className() {
diff --git a/src/resources/vue/Widgets/TransactionLog.vue b/src/resources/vue/Widgets/TransactionLog.vue
--- a/src/resources/vue/Widgets/TransactionLog.vue
+++ b/src/resources/vue/Widgets/TransactionLog.vue
@@ -1,7 +1,7 @@
<template>
<div>
<table class="table table-sm m-0 transactions">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('form.date') }}</th>
<th scope="col" v-if="isAdmin">{{ $t('form.user') }}</th>
diff --git a/src/resources/vue/Widgets/UserSearch.vue b/src/resources/vue/Widgets/UserSearch.vue
--- a/src/resources/vue/Widgets/UserSearch.vue
+++ b/src/resources/vue/Widgets/UserSearch.vue
@@ -4,13 +4,11 @@
<form @submit.prevent="searchUser" class="row justify-content-center">
<div class="input-group col-sm-8">
<input class="form-control" type="text" :placeholder="$t('user.search-pl')" v-model="search">
- <div class="input-group-append">
- <button type="submit" class="btn btn-primary"><svg-icon icon="search"></svg-icon> {{ $t('btn.search') }}</button>
- </div>
+ <button type="submit" class="btn btn-primary"><svg-icon icon="search"></svg-icon> {{ $t('btn.search') }}</button>
</div>
</form>
<table v-if="users.length" class="table table-sm table-hover mt-4">
- <thead class="thead-light">
+ <thead>
<tr>
<th scope="col">{{ $t('form.primary-email') }}</th>
<th scope="col">{{ $t('form.id') }}</th>
@@ -47,7 +45,7 @@
}
},
mounted() {
- $('#search-box input').focus()
+ $('#search-box input', this.$el).focus()
},
methods: {
searchUser() {
diff --git a/src/tests/Browser.php b/src/tests/Browser.php
--- a/src/tests/Browser.php
+++ b/src/tests/Browser.php
@@ -160,7 +160,7 @@
*/
public function clearToasts()
{
- $this->script("jQuery('.toast-container > *').remove()");
+ $this->script("\$('.toast-container > *').remove()");
return $this;
}
diff --git a/src/tests/Browser/Components/Dialog.php b/src/tests/Browser/Components/Dialog.php
--- a/src/tests/Browser/Components/Dialog.php
+++ b/src/tests/Browser/Components/Dialog.php
@@ -45,7 +45,7 @@
public function elements()
{
return [
- '@title' => '.modal-header .modal-title',
+ '@title' => '.modal-title',
'@body' => '.modal-body',
'@button-action' => '.modal-footer button.modal-action',
'@button-cancel' => '.modal-footer button.modal-cancel',
diff --git a/src/tests/Browser/Components/Toast.php b/src/tests/Browser/Components/Toast.php
--- a/src/tests/Browser/Components/Toast.php
+++ b/src/tests/Browser/Components/Toast.php
@@ -97,6 +97,6 @@
*/
public function closeToast($browser)
{
- $this->element->findElements(WebDriverBy::cssSelector('button.close'))[0]->click();
+ $this->element->findElements(WebDriverBy::cssSelector('.btn-close'))[0]->click();
}
}
diff --git a/src/tests/Browser/FaqTest.php b/src/tests/Browser/FaqTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Browser/FaqTest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Tests\Browser;
+
+use Tests\Browser;
+use Tests\Browser\Pages\Signup;
+use Tests\TestCaseDusk;
+
+class FaqTest extends TestCaseDusk
+{
+ /**
+ * Test FAQ widget
+ */
+ public function testFaq(): void
+ {
+ $this->browse(function (Browser $browser) {
+ $browser->visit(new Signup())
+ ->whenAvailable('#faq', function ($browser) {
+ $browser->assertSeeIn('h5', 'FAQ')
+ ->assertElementsCount('ul > li', 4)
+ ->assertSeeIn('li:last-child a', 'Need support?')
+ ->click('li:last-child a');
+ })
+ ->waitForLocation('/support');
+ });
+ }
+}
diff --git a/src/tests/Browser/Meet/RoomSetupTest.php b/src/tests/Browser/Meet/RoomSetupTest.php
--- a/src/tests/Browser/Meet/RoomSetupTest.php
+++ b/src/tests/Browser/Meet/RoomSetupTest.php
@@ -168,7 +168,7 @@
// Join the room (click the button twice, to make sure it does not
// produce redundant participants/subscribers in the room)
->clickWhenEnabled('@setup-button')
- ->pause(10)
+ ->pause(5)
->click('@setup-button')
->waitFor('@session')
->assertMissing('@setup-form')
diff --git a/src/tests/Browser/Pages/UserInfo.php b/src/tests/Browser/Pages/UserInfo.php
--- a/src/tests/Browser/Pages/UserInfo.php
+++ b/src/tests/Browser/Pages/UserInfo.php
@@ -42,6 +42,7 @@
'@nav' => 'ul.nav-tabs',
'@packages' => '#user-packages',
'@settings' => '#settings',
+ '@general' => '#general',
'@skus' => '#user-skus',
'@status' => '#status-box',
];
diff --git a/src/tests/Browser/Reseller/InvitationsTest.php b/src/tests/Browser/Reseller/InvitationsTest.php
--- a/src/tests/Browser/Reseller/InvitationsTest.php
+++ b/src/tests/Browser/Reseller/InvitationsTest.php
@@ -94,12 +94,11 @@
->assertMissing('@body input#email.is-invalid')
// Submit an empty file
->attach('@body input#file', __DIR__ . '/../../data/empty.csv')
- ->assertSeeIn('@body input#file + label', 'empty.csv')
->click('@button-action')
->assertToast(Toast::TYPE_ERROR, "Form validation error")
// ->waitFor('input#file.is-invalid')
->assertSeeIn(
- '@body input#file.is-invalid + label + .invalid-feedback',
+ '@body input#file.is-invalid + .invalid-feedback',
"Failed to find any valid email addresses in the uploaded file."
)
// Submit non-empty file
diff --git a/src/tests/Browser/SignupTest.php b/src/tests/Browser/SignupTest.php
--- a/src/tests/Browser/SignupTest.php
+++ b/src/tests/Browser/SignupTest.php
@@ -113,10 +113,10 @@
$browser->assertMenuItems(['signup', 'explore', 'blog', 'support', 'login', 'lang'], 'signup');
});
- $browser->waitFor('@step0 .plan-selector > .card');
+ $browser->waitFor('@step0 .plan-selector .card');
// Assert first plan box and press the button
- $browser->with('@step0 .plan-selector > .plan-individual', function ($step) {
+ $browser->with('@step0 .plan-selector .plan-individual', function ($step) {
$step->assertVisible('button')
->assertSeeIn('button', 'Individual Account')
->assertVisible('.plan-description')
@@ -139,7 +139,7 @@
->assertMissing('@step3');
// Choose the group account plan
- $browser->click('@step0 .plan-selector > .plan-group button')
+ $browser->click('@step0 .plan-selector .plan-group button')
->waitForLocation('/signup/group')
->assertVisible('@step1')
->assertMissing('@step0')
diff --git a/src/tests/Browser/UsersTest.php b/src/tests/Browser/UsersTest.php
--- a/src/tests/Browser/UsersTest.php
+++ b/src/tests/Browser/UsersTest.php
@@ -146,7 +146,7 @@
->click('@table tr:nth-child(3) a')
->on(new UserInfo())
->assertSeeIn('#user-info .card-title', 'User account')
- ->with('@form', function (Browser $browser) {
+ ->with('@general', function (Browser $browser) {
// Assert form content
$browser->assertSeeIn('div.row:nth-child(1) label', 'Status')
->assertSeeIn('div.row:nth-child(1) #status', 'Active')
@@ -181,7 +181,7 @@
->click('@table tr:nth-child(3) a')
->on(new UserInfo())
->assertSeeIn('#user-info .card-title', 'User account')
- ->with('@form', function (Browser $browser) {
+ ->with('@general', function (Browser $browser) {
// Test error handling (password)
$browser->type('#password', 'aaaaaa')
->vueClear('#password_confirmation')
@@ -223,7 +223,7 @@
$this->assertTrue(!empty($alias));
// Test subscriptions
- $browser->with('@form', function (Browser $browser) {
+ $browser->with('@general', function (Browser $browser) {
$browser->assertSeeIn('div.row:nth-child(9) label', 'Subscriptions')
->assertVisible('@skus.row:nth-child(9)')
->with('@skus', function ($browser) {
@@ -301,7 +301,7 @@
$this->assertUserEntitlements($john, $expected);
// Test subscriptions interaction
- $browser->with('@form', function (Browser $browser) {
+ $browser->with('@general', function (Browser $browser) {
$browser->with('@skus', function ($browser) {
// Uncheck 'groupware', expect activesync unchecked
$browser->click('#sku-input-groupware')
@@ -343,8 +343,9 @@
$john = $this->getTestUser('john@kolab.org');
$john->setSetting('greylist_enabled', null);
- $this->browse(function (Browser $browser) {
- $browser->on(new UserInfo())
+ $this->browse(function (Browser $browser) use ($john) {
+ $browser->visit('/user/' . $john->id)
+ ->on(new UserInfo())
->assertElementsCount('@nav a', 2)
->assertSeeIn('@nav #tab-general', 'General')
->assertSeeIn('@nav #tab-settings', 'Settings')
@@ -373,7 +374,7 @@
->click('button.create-user')
->on(new UserInfo())
->assertSeeIn('#user-info .card-title', 'New user account')
- ->with('@form', function (Browser $browser) {
+ ->with('@general', function (Browser $browser) {
// Assert form content
$browser->assertFocused('div.row:nth-child(1) input')
->assertSeeIn('div.row:nth-child(1) label', 'First Name')
@@ -428,7 +429,7 @@
});
// Test form error handling (aliases)
- $browser->with('@form', function (Browser $browser) {
+ $browser->with('@general', function (Browser $browser) {
$browser->type('#email', 'julia.roberts@kolab.org')
->type('#password_confirmation', 'simple123')
->with(new ListInput('#aliases'), function (Browser $browser) {
@@ -442,7 +443,7 @@
});
// Successful account creation
- $browser->with('@form', function (Browser $browser) {
+ $browser->with('@general', function (Browser $browser) {
$browser->type('#first_name', 'Julia')
->type('#last_name', 'Roberts')
->type('#organization', 'Test Org')
@@ -462,6 +463,7 @@
$julia = User::where('email', 'julia.roberts@kolab.org')->first();
$alias = UserAlias::where('user_id', $julia->id)->where('alias', 'julia.roberts2@kolab.org')->first();
+
$this->assertTrue(!empty($alias));
$this->assertUserEntitlements($julia, ['mailbox', 'storage', 'storage', 'storage', 'storage', 'storage']);
$this->assertSame('Julia', $julia->getSetting('first_name'));
@@ -469,26 +471,33 @@
$this->assertSame('Test Org', $julia->getSetting('organization'));
// Some additional tests for the list input widget
- $browser->click('tbody tr:nth-child(4) a')
+ $browser->click('@table tbody tr:nth-child(4) a')
->on(new UserInfo())
->with(new ListInput('#aliases'), function (Browser $browser) {
$browser->assertListInputValue(['julia.roberts2@kolab.org'])
->addListEntry('invalid address')
- ->type('.input-group:nth-child(2) input', '@kolab.org');
+ ->type('.input-group:nth-child(2) input', '@kolab.org')
+ ->keys('.input-group:nth-child(2) input', '{enter}');
})
- ->click('button[type=submit]')
+ // TODO: Investigate why this click does not work, for now we
+ // submit the form with Enter key above
+ //->click('@general button[type=submit]')
->assertToast(Toast::TYPE_ERROR, 'Form validation error')
->with(new ListInput('#aliases'), function (Browser $browser) {
$browser->assertVisible('.input-group:nth-child(2) input.is-invalid')
->assertVisible('.input-group:nth-child(3) input.is-invalid')
->type('.input-group:nth-child(2) input', 'julia.roberts3@kolab.org')
- ->type('.input-group:nth-child(3) input', 'julia.roberts4@kolab.org');
+ ->type('.input-group:nth-child(3) input', 'julia.roberts4@kolab.org')
+ ->keys('.input-group:nth-child(3) input', '{enter}');
})
- ->click('button[type=submit]')
+ // TODO: Investigate why this click does not work, for now we
+ // submit the form with Enter key above
+ //->click('@general button[type=submit]')
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.');
$julia = User::where('email', 'julia.roberts@kolab.org')->first();
$aliases = $julia->aliases()->orderBy('alias')->get()->pluck('alias')->all();
+
$this->assertSame(['julia.roberts3@kolab.org', 'julia.roberts4@kolab.org'], $aliases);
});
}
@@ -585,7 +594,7 @@
->waitFor('@table tr:nth-child(2)')
->click('@table tr:nth-child(2) a') // joe@kolab.org
->on(new UserInfo())
- ->with('@form', function (Browser $browser) {
+ ->with('@general', function (Browser $browser) {
$browser->whenAvailable('@skus', function (Browser $browser) {
$quota_input = new QuotaInput('tbody tr:nth-child(2) .range-input');
$browser->waitFor('tbody tr')
@@ -614,7 +623,7 @@
$browser->visit(new UserList())
->click('button.create-user')
->on(new UserInfo())
- ->with('@form', function (Browser $browser) {
+ ->with('@general', function (Browser $browser) {
$browser->whenAvailable('@packages', function (Browser $browser) {
$browser->assertElementsCount('tbody tr', 2)
->assertSeeIn('tbody tr:nth-child(1) .price', '8,91 CHF/month¹') // Groupware
@@ -648,7 +657,7 @@
$browser->visit('/user/' . $joe->id)
->on(new UserInfo())
- ->with('@form', function (Browser $browser) {
+ ->with('@general', function (Browser $browser) {
$browser->whenAvailable('@skus', function (Browser $browser) {
$quota_input = new QuotaInput('tbody tr:nth-child(2) .range-input');
$browser->waitFor('tbody tr')
@@ -726,7 +735,7 @@
->click('#sku-input-beta')
->click('#sku-input-distlist');
})
- ->click('button[type=submit]')
+ ->click('@general button[type=submit]')
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.');
$expected = [
@@ -741,8 +750,9 @@
$browser->visit('/user/' . $john->id)
->on(new UserInfo())
+ ->waitFor('#sku-input-beta')
->click('#sku-input-beta')
- ->click('button[type=submit]')
+ ->click('@general button[type=submit]')
->assertToast(Toast::TYPE_SUCCESS, 'User data updated successfully.');
$expected = [
diff --git a/src/webpack.mix.js b/src/webpack.mix.js
--- a/src/webpack.mix.js
+++ b/src/webpack.mix.js
@@ -14,14 +14,6 @@
const glob = require('glob');
const mix = require('laravel-mix');
-mix.webpackConfig({
- resolve: {
- alias: {
- 'jquery$': 'jquery/dist/jquery.slim.js',
- }
- }
-})
-
mix.js('resources/js/user/app.js', 'public/js/user.js').vue()
.js('resources/js/admin/app.js', 'public/js/admin.js').vue()
.js('resources/js/reseller/app.js', 'public/js/reseller.js').vue()
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 3, 12:14 AM (20 h, 24 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18821650
Default Alt Text
D2734.1775175243.diff (154 KB)
Attached To
Mode
D2734: Bootstrap v5
Attached
Detach File
Event Timeline