- Fixed. The missing controls now appear at:

Plans & Platform > Plans, Prices & Entitlements > Groups Available for 
each tier.
Config > Feature Availability > Groups Enabled as the global master 
switch.
This commit is contained in:
Ty Clifford
2026-06-08 16:47:02 -04:00
parent 2881a3b338
commit de8ec43aa4
9 changed files with 107 additions and 42 deletions
+30 -20
View File
@@ -6,7 +6,7 @@
const state = {
config: {}, user: null, groups: [], billing: null,
view: ['chat', 'groups', 'archive', 'account'].includes(initialView) ? initialView : 'chat',
groupId: null, lastId: 0, pollTimer: null, polling: false, pollGeneration: 0,
groupId: null, lastId: 0, pollTimer: null, polling: false, pollGeneration: 0, groupsAccess: null,
groupSeen: {}, groupUnread: {},
loginChallenge: initialChallenge,
verifyToken: new URLSearchParams(location.search).get('verify') || '',
@@ -47,12 +47,13 @@
? state.user.features[key] : fallback;
}
function groupsAvailable() {
return state.config.groups?.enabled !== false;
return state.config.groups?.enabled !== false && state.groupsAccess !== false;
}
function applyGroupsAvailability(enabled, groups = []) {
function applyGroupsAvailability(enabled, groups = [], platformEnabled = enabled) {
const next = enabled !== false;
const changed = groupsAvailable() !== next;
state.config.groups = { ...(state.config.groups || {}), enabled: next };
state.groupsAccess = next;
state.config.groups = { ...(state.config.groups || {}), enabled: platformEnabled !== false };
if (next) {
if (changed) syncGroups(groups);
} else {
@@ -64,8 +65,8 @@
}
return changed;
}
function disableGroups(message = '') {
applyGroupsAvailability(false);
function disableGroups(message = '', platformEnabled = state.config.groups?.enabled !== false) {
applyGroupsAvailability(false, [], platformEnabled);
if (message) toast(message);
app();
}
@@ -263,13 +264,11 @@
const [groups, billing] = await Promise.all([
groupsAvailable()
? api('groups.php', null, 'action=list').catch(() => ({ groups: [] }))
: Promise.resolve({ groups: [], enabled: false }),
: Promise.resolve({ groups: [], enabled: false, platform_enabled: false }),
api('billing.php', null, 'action=status').catch(() => null)
]);
state.config.groups = {
...(state.config.groups || {}),
enabled: groups.enabled !== false && groupsAvailable(),
};
state.groupsAccess = groups.enabled !== false;
state.config.groups = { ...(state.config.groups || {}), enabled: groups.platform_enabled !== false };
state.groups = groupsAvailable() ? (groups.groups || []) : [];
state.groupSeen = {};
state.groupUnread = {};
@@ -387,7 +386,7 @@
const result = await api('messages.php', form({ action: 'send', message, group_id: state.groupId || '' }));
if (result.groups_enabled === false) {
input.value = message;
return disableGroups(result.error);
return disableGroups(result.error, result.groups_platform_enabled !== false);
}
if (result.error) { input.value = message; return toast(result.error); }
appendMessage(result.message);
@@ -415,7 +414,11 @@
const result = await api('messages.php', null, query.toString()).catch(() => null);
if (generation !== state.pollGeneration || Number(state.groupId || 0) !== Number(requestedGroup || 0)) return;
if (result && !result.error && Number(result.group_id || 0) === Number(requestedGroup || 0)) {
const availabilityChanged = applyGroupsAvailability(result.groups_enabled !== false, result.groups || []);
const availabilityChanged = applyGroupsAvailability(
result.groups_enabled !== false,
result.groups || [],
result.groups_platform_enabled !== false
);
if (availabilityChanged) {
toast(groupsAvailable() ? 'Private groups are now available' : 'Private groups have been disabled', 'success');
app();
@@ -449,7 +452,10 @@
const result = await api('groups.php', null, 'action=list').catch(() => null);
if (!result || result.error || generation !== state.pollGeneration) return;
if (result.enabled === false) {
disableGroups('Private groups have been disabled');
disableGroups(
result.platform_enabled === false ? 'Private groups have been disabled' : 'Private groups are not available on your tier',
result.platform_enabled !== false
);
return;
}
syncGroups(result.groups || []);
@@ -604,7 +610,7 @@
body.append('voice', state.voice.blob, 'voice.webm');
const result = await api('messages.php', { method: 'POST', body });
state.voice.sending = false;
if (result.groups_enabled === false) return disableGroups(result.error);
if (result.groups_enabled === false) return disableGroups(result.error, result.groups_platform_enabled !== false);
if (result.error) return toast(result.error);
appendMessage(result.message);
state.lastId = Math.max(state.lastId, result.message.id);
@@ -643,7 +649,10 @@
api('groups.php', null, 'action=list').then(result => {
if (result.error || state.view !== 'groups') return;
if (result.enabled === false) {
disableGroups('Private groups have been disabled');
disableGroups(
result.platform_enabled === false ? 'Private groups have been disabled' : 'Private groups are not available on your tier',
result.platform_enabled !== false
);
return;
}
const before = JSON.stringify(state.groups);
@@ -662,21 +671,21 @@
}
async function createGroup() {
const result = await api('groups.php', form({ action: 'create', name: $('#group-name').value.trim(), members: $('#group-members').value.trim() }));
if (result.groups_enabled === false) return disableGroups(result.error);
if (result.groups_enabled === false) return disableGroups(result.error, result.groups_platform_enabled !== false);
if (result.error) return toast(result.error);
state.groups = result.groups;
groupsView();
}
async function addMember(groupId) {
const result = await api('groups.php', form({ action: 'add_member', group_id: groupId, username: $('#add-' + groupId).value.trim() }));
if (result.groups_enabled === false) return disableGroups(result.error);
if (result.groups_enabled === false) return disableGroups(result.error, result.groups_platform_enabled !== false);
if (result.error) return toast(result.error);
state.groups = result.groups;
groupsView();
}
async function removeMember(groupId, userId) {
const result = await api('groups.php', form({ action: 'remove_member', group_id: groupId, user_id: userId }));
if (result.groups_enabled === false) return disableGroups(result.error);
if (result.groups_enabled === false) return disableGroups(result.error, result.groups_platform_enabled !== false);
if (result.error) return toast(result.error);
state.groups = result.groups;
groupsView();
@@ -684,7 +693,7 @@
async function deleteGroup(groupId) {
if (!confirm('Delete this group and its messages?')) return;
const result = await api('groups.php', form({ action: 'delete', group_id: groupId }));
if (result.groups_enabled === false) return disableGroups(result.error);
if (result.groups_enabled === false) return disableGroups(result.error, result.groups_platform_enabled !== false);
if (result.error) return toast(result.error);
state.groups = result.groups;
if (state.groupId === groupId) state.groupId = null;
@@ -807,6 +816,7 @@
state.groups = [];
state.billing = null;
state.groupId = null;
state.groupsAccess = null;
state.groupSeen = {};
state.groupUnread = {};
state.voice.playQueue = [];