Skip to content
This repository was archived by the owner on Sep 21, 2026. It is now read-only.

Commit 0e69386

Browse files
authored
Merge pull request #3184 from appwrite/fix-users-limit-update-button
fix(auth): stop the users limit card silently blocking Update
2 parents 2325a53 + 6ad4663 commit 0e69386

3 files changed

Lines changed: 109 additions & 17 deletions

File tree

‎src/routes/(console)/project-[region]-[project]/auth/security/updateUsersLimit.svelte‎

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@
77
import { addNotification } from '$lib/stores/notifications';
88
import { sdk } from '$lib/stores/sdk';
99
import { Layout, Selector, Input, Badge } from '@appwrite.io/pink-svelte';
10-
import { tick } from 'svelte';
1110
import type { Models } from '@appwrite.io/console';
11+
import {
12+
USERS_LIMIT_MAX,
13+
USERS_LIMIT_MIN,
14+
usersLimitState,
15+
type UsersLimitMode
16+
} from './usersLimit';
1217
1318
let {
1419
project,
@@ -18,15 +23,11 @@
1823
policy: Models.PolicyUserLimit;
1924
} = $props();
2025
21-
let maxUsersInputField: HTMLInputElement | null = $state(null);
22-
23-
let value = $state(policy.total !== 0 ? 'limited' : 'unlimited');
26+
let value = $state<UsersLimitMode>(policy.total !== 0 ? 'limited' : 'unlimited');
2427
let newLimit = $state(policy.total !== 0 ? policy.total : 100);
2528
2629
const isLimited = $derived(value === 'limited');
27-
const btnDisabled = $derived.by(() => {
28-
return (!isLimited && policy.total === 0) || (isLimited && policy.total === newLimit);
29-
});
30+
const limitState = $derived(usersLimitState(value, newLimit, policy.total));
3031
3132
async function updateLimit() {
3233
try {
@@ -47,14 +48,6 @@
4748
trackError(error, Submit.AuthLimitUpdate);
4849
}
4950
}
50-
51-
$effect(() => {
52-
if (isLimited && maxUsersInputField) {
53-
tick().then(() => {
54-
maxUsersInputField.focus();
55-
});
56-
}
57-
});
5851
</script>
5952

6053
<CardGrid>
@@ -83,16 +76,22 @@
8376
name="limit"
8477
id="limit"
8578
class="input-text"
86-
max="10000"
79+
min={USERS_LIMIT_MIN}
80+
max={USERS_LIMIT_MAX}
81+
step={1}
82+
state={limitState.error ? 'error' : 'default'}
8783
disabled={!isLimited}
8884
bind:value={newLimit} />
8985
</Layout.Stack>
86+
{#if limitState.error}
87+
<Input.Helper state="error">{limitState.error}</Input.Helper>
88+
{/if}
9089
</Layout.Stack>
9190
</svelte:fragment>
9291

9392
<svelte:fragment slot="actions">
9493
<Button
95-
disabled={btnDisabled}
94+
disabled={limitState.disabled}
9695
on:click={() => {
9796
updateLimit();
9897
}}>Update</Button>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { USERS_LIMIT_MAX, USERS_LIMIT_MIN, usersLimitState } from './usersLimit';
3+
4+
describe('usersLimitState', () => {
5+
describe('limited', () => {
6+
it('accepts whole numbers within range', () => {
7+
expect(usersLimitState('limited', USERS_LIMIT_MIN, 100).error).toBeNull();
8+
expect(usersLimitState('limited', 250, 100).error).toBeNull();
9+
expect(usersLimitState('limited', USERS_LIMIT_MAX, 100).error).toBeNull();
10+
});
11+
12+
it('explains a limit of 0 instead of leaving Update dead', () => {
13+
const state = usersLimitState('limited', 0, 0);
14+
15+
expect(state.error).toContain('Unlimited');
16+
expect(state.disabled).toBe(true);
17+
});
18+
19+
it('refuses to save 0, which the API would read as no limit', () => {
20+
expect(usersLimitState('limited', 0, 100).disabled).toBe(true);
21+
});
22+
23+
it('rejects a cleared field', () => {
24+
expect(usersLimitState('limited', undefined, 100).error).not.toBeNull();
25+
expect(usersLimitState('limited', NaN, 100).error).not.toBeNull();
26+
});
27+
28+
it('rejects values outside the accepted range', () => {
29+
expect(usersLimitState('limited', -1, 100).error).not.toBeNull();
30+
expect(usersLimitState('limited', USERS_LIMIT_MAX + 1, 100).error).not.toBeNull();
31+
expect(usersLimitState('limited', 1.5, 100).error).not.toBeNull();
32+
});
33+
34+
it('enables Update for a valid new limit', () => {
35+
expect(usersLimitState('limited', 100, 0).disabled).toBe(false);
36+
expect(usersLimitState('limited', 1, 100).disabled).toBe(false);
37+
});
38+
39+
it('disables Update when the limit is unchanged', () => {
40+
expect(usersLimitState('limited', 100, 100).disabled).toBe(true);
41+
});
42+
});
43+
44+
describe('unlimited', () => {
45+
it('ignores whatever the disabled limit field holds', () => {
46+
expect(usersLimitState('unlimited', 0, 100).error).toBeNull();
47+
expect(usersLimitState('unlimited', undefined, 100).error).toBeNull();
48+
});
49+
50+
it('enables Update when a limit is being removed', () => {
51+
expect(usersLimitState('unlimited', 100, 100).disabled).toBe(false);
52+
});
53+
54+
it('disables Update when there is no limit to remove', () => {
55+
expect(usersLimitState('unlimited', 100, 0).disabled).toBe(true);
56+
});
57+
});
58+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export const USERS_LIMIT_MIN = 1;
2+
export const USERS_LIMIT_MAX = 10000;
3+
4+
export type UsersLimitMode = 'limited' | 'unlimited';
5+
6+
export type UsersLimitState = {
7+
error: string | null;
8+
disabled: boolean;
9+
};
10+
11+
// The API reserves `total: 0` for "no limit", so `limited` cannot represent zero.
12+
function limitError(limit: number): string | null {
13+
if (!Number.isInteger(limit) || limit < USERS_LIMIT_MIN || limit > USERS_LIMIT_MAX) {
14+
return `Enter a whole number between ${USERS_LIMIT_MIN} and ${USERS_LIMIT_MAX}, or select "Unlimited" to allow any number of users.`;
15+
}
16+
17+
return null;
18+
}
19+
20+
function limitChanged(mode: UsersLimitMode, limit: number, total: number): boolean {
21+
return mode === 'limited' ? limit !== total : total !== 0;
22+
}
23+
24+
export function usersLimitState(
25+
mode: UsersLimitMode,
26+
limit: number,
27+
total: number
28+
): UsersLimitState {
29+
const error = mode === 'limited' ? limitError(limit) : null;
30+
31+
return {
32+
error,
33+
disabled: error !== null || !limitChanged(mode, limit, total)
34+
};
35+
}

0 commit comments

Comments
 (0)