Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

52 lines
1.7 KiB
TypeScript

// Pure helpers for the one-time-token PIN entry. Kept free of React so the
// distribution/assembly logic can be unit-tested directly.
export const TOKEN_GROUPS = [4, 4, 4, 4, 4];
export const TOKEN_DIGIT_COUNT = TOKEN_GROUPS.reduce((sum, size) => sum + size, 0); // 20
export function onlyDigits(value: string): string {
return (value || '').replace(/\D+/g, '');
}
// Distribute typed/pasted digits across the fixed-width boxes starting at
// `startIndex`. Non-digits are ignored; overflow is dropped.
export function fillFrom(current: string[], startIndex: number, incoming: string): string[] {
const digits = onlyDigits(incoming);
const next = current.slice();
let cursor = startIndex;
for (const digit of digits) {
if (cursor >= TOKEN_DIGIT_COUNT) break;
next[cursor] = digit;
cursor += 1;
}
return next;
}
// The index of the first empty box at or after `from`, or the last box if full.
export function nextEmptyIndex(boxes: string[], from = 0): number {
for (let i = Math.max(0, from); i < TOKEN_DIGIT_COUNT; i += 1) {
if (!/\d/.test(boxes[i] || '')) return i;
}
return TOKEN_DIGIT_COUNT - 1;
}
// Join the boxes into the canonical 5x4 dashed token, e.g. 4817-2039-6152-...
export function assembleToken(boxes: string[]): string {
const digits = onlyDigits(boxes.join('')).slice(0, TOKEN_DIGIT_COUNT);
const groups: string[] = [];
let pos = 0;
for (const size of TOKEN_GROUPS) {
groups.push(digits.slice(pos, pos + size));
pos += size;
}
return groups.join('-');
}
export function isComplete(boxes: string[]): boolean {
return onlyDigits(boxes.join('')).length === TOKEN_DIGIT_COUNT;
}
export function emptyBoxes(): string[] {
return Array.from({ length: TOKEN_DIGIT_COUNT }, () => '');
}