Compare commits

...

26 Commits

Author SHA1 Message Date
Hafiz
872182ce19 Responsive Layout for Equipment Containers
- Added responsive CSS for mobile (<768px) and tablet (769px-1024px)
- Implemented flex-wrap layout that automatically stacks items in rows of 4 on smaller
2025-08-13 12:16:57 -05:00
Hafiz
9a1fb18959 Merge remote-tracking branch 'origin/develop' into qa/bat 2025-08-12 09:46:07 -05:00
Phillip Thelen
2ea0b64603 improve blocker form display 2025-08-05 14:57:25 +02:00
Phillip Thelen
bd1aa1e417 validate blocker value during input 2025-08-05 14:45:23 +02:00
Phillip Thelen
7c49b845d6 add option to errorHandler to skip logging 2025-08-04 17:40:26 +02:00
Phillip Thelen
1ee172139d lint fix 2025-08-04 16:32:40 +02:00
Phillip Thelen
6447b9ab4b update block error strings 2025-08-04 16:03:55 +02:00
Phillip Thelen
5c414099d9 improve navbar display for non fullAccess admin 2025-08-04 14:46:05 +02:00
Phillip Thelen
5e8e1179aa fix managing permissions from admin 2025-08-04 14:45:47 +02:00
Phillip Thelen
7e86a62624 improve permission check 2025-08-04 14:33:09 +02:00
Phillip Thelen
1ba9dda0ed add new permission for managing blockers 2025-08-04 14:21:36 +02:00
Phillip Thelen
227e5ceaa8 fix import 2025-07-30 11:26:55 +02:00
Phillip Thelen
f77ab5a3ab lint fixes 2025-07-30 11:26:55 +02:00
Phillip Thelen
1916faf647 fix 2025-07-30 11:26:55 +02:00
Phillip Thelen
80ecb5cef1 lint fix 2025-07-30 11:26:55 +02:00
Phillip Thelen
75c36e6622 add blocker to block emails from registration 2025-07-30 11:26:55 +02:00
Phillip Thelen
78330c975a Improve blocker UI 2025-07-30 11:26:55 +02:00
Phillip Thelen
95266f6cb3 improve test coverage 2025-07-30 11:26:55 +02:00
Phillip Thelen
e9b2c1b51a restructure admin pages 2025-07-30 11:26:54 +02:00
Phillip Thelen
2a2bea07ab Add UI for managing blockers 2025-07-30 11:26:54 +02:00
Phillip Thelen
ea60ddbf4c Tweak wording 2025-07-30 11:25:51 +02:00
Phillip Thelen
1c2ca0e478 correctly reset local data after creating blocker 2025-07-30 11:25:51 +02:00
Phillip Thelen
ef2b7eb928 Add UI for managing blockers 2025-07-30 11:25:51 +02:00
Phillip Thelen
3d16387a61 add new frontend files 2025-07-30 11:25:41 +02:00
Phillip Thelen
93b7770eaa begin building general blocking solution 2025-07-30 11:25:41 +02:00
Phillip Thelen
a9f84d3307 Read IP blocks from database 2025-07-30 11:25:41 +02:00
8 changed files with 130 additions and 8 deletions

View File

@@ -117,6 +117,15 @@ describe('Blocker middleware', () => {
checkIPBlockedErrorThrown(next); checkIPBlockedErrorThrown(next);
}); });
it('throws when the ip is blocked', () => {
req.ip = '192.168.1.1';
sandbox.stub(nconf, 'get').withArgs('BLOCKED_IPS').returns('192.168.1.1');
const attachBlocker = requireAgain(pathToBlocker).default;
attachBlocker(req, res, next);
checkIPBlockedErrorThrown(next);
});
}); });
describe('Blocking clients', () => { describe('Blocking clients', () => {

View File

@@ -120,6 +120,7 @@
<div <div
slot="drawer-slider" slot="drawer-slider"
class="equipment items items-one-line" class="equipment items items-one-line"
:class="getContainerClass()"
> >
<item <item
v-for="(label, group) in gearTypesToStrings" v-for="(label, group) in gearTypesToStrings"
@@ -238,6 +239,86 @@
background: $gray-10 !important; background: $gray-10 !important;
} }
@media (max-width: 768px) {
.equipment.items.items-one-line {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
padding: 0 8px;
.item-wrapper {
flex: 0 0 auto;
margin-right: 0;
margin-bottom: 8px;
}
&.equipment-scale-default .item-wrapper {
.item {
width: 94px;
height: 92px;
}
.item-label {
width: 94px;
font-size: 12px;
}
}
&.equipment-scale-small .item-wrapper {
.item {
width: 70px;
height: 70px;
}
.item-label {
width: 70px;
font-size: 10px;
}
}
.item-wrapper:nth-child(4n+1) {
clear: left;
}
}
}
@media (min-width: 769px) and (max-width: 1024px) {
.equipment.items.items-one-line {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
padding: 0 12px;
.item-wrapper {
flex: 0 0 auto;
margin-right: 0;
margin-bottom: 8px;
}
&.equipment-scale-default .item-wrapper {
.item {
width: 84px;
height: 82px;
}
.item-label {
width: 84px;
font-size: 11px;
}
}
&.equipment-scale-small .item-wrapper {
.item {
width: 65px;
height: 65px;
}
.item-label {
width: 65px;
font-size: 10px;
}
}
}
}
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -351,6 +432,7 @@ export default {
searchText: null, searchText: null,
searchTextThrottled: null, searchTextThrottled: null,
costumeMode: false, costumeMode: false,
windowWidth: window.innerWidth,
groupByItems: [ groupByItems: [
'type', 'class', 'type', 'class',
], ],
@@ -523,8 +605,27 @@ export default {
subSection: this.$t('equipment'), subSection: this.$t('equipment'),
section: this.$t('inventory'), section: this.$t('inventory'),
}); });
this.handleResize = throttle(() => {
this.windowWidth = window.innerWidth;
}, 250);
window.addEventListener('resize', this.handleResize);
},
beforeDestroy () {
window.removeEventListener('resize', this.handleResize);
}, },
methods: { methods: {
getContainerClass () {
const equippedCount = Object.keys(this.gearTypesToStrings).filter(group => {
const item = this.flatGear[this.activeItems[group]];
return item && item.key.indexOf('_base_0') === -1;
}).length;
if (this.windowWidth <= 1024) {
return equippedCount > 4 ? 'equipment-scale-small' : 'equipment-scale-default';
}
return '';
},
selectDrawerTab (tabName) { selectDrawerTab (tabName) {
let tabNameValue; let tabNameValue;
if (tabName === 'costume') { if (tabName === 'costume') {

View File

@@ -111,7 +111,7 @@
.toggle-switch-inner:before { .toggle-switch-inner:before {
content: ""; content: "";
padding-left: 10px; padding-left: 10px;
background-color: $green-50; background-color: $purple-300;
} }
.toggle-switch-inner:after { .toggle-switch-inner:after {

View File

@@ -187,5 +187,4 @@ api.deleteBlocker = {
res.respond(200, savedBlocker); res.respond(200, savedBlocker);
}, },
}; };
export default api; export default api;

View File

@@ -1,3 +1,4 @@
import nconf from 'nconf';
import { import {
Forbidden, Forbidden,
} from '../libs/errors'; } from '../libs/errors';
@@ -9,7 +10,19 @@ import { model as Blocker } from '../models/blocker';
// NOTE: it's meant to be used behind a proxy (for example a load balancer) // NOTE: it's meant to be used behind a proxy (for example a load balancer)
// that uses the 'x-forwarded-for' header to forward the original IP addresses. // that uses the 'x-forwarded-for' header to forward the original IP addresses.
const blockedIps = []; // A list of comma separated IPs to block
// It works fine as long as the list is short,
// if the list becomes too long for an env variable we'll switch to Redis.
const BLOCKED_IPS_RAW = nconf.get('BLOCKED_IPS');
const blockedIps = BLOCKED_IPS_RAW
? BLOCKED_IPS_RAW
.trim()
.split(',')
.map(blockedIp => blockedIp.trim())
.filter(blockedIp => Boolean(blockedIp))
: [];
const blockedClients = []; const blockedClients = [];
Blocker.watchBlockers({ Blocker.watchBlockers({