import { LitElement, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { generateCryptoRandomUUID, translateText } from "../client/Utils"; import { sanitizeClanTag } from "../core/Util"; import { MAX_CLAN_TAG_LENGTH, MAX_USERNAME_LENGTH, MIN_CLAN_TAG_LENGTH, MIN_USERNAME_LENGTH, validateClanTag, validateUsername, } from "../core/validations/username"; import { crazyGamesSDK } from "./CrazyGamesSDK"; interface LangSelectorLike { currentLang?: string; translations?: Record; defaultTranslations?: Record; } const usernameKey: string = "username"; const clanTagKey: string = "clanTag"; @customElement("username-input") export class UsernameInput extends LitElement { @state() private baseUsername: string = ""; @state() private clanTag: string = ""; @property({ type: String }) validationError: string = ""; private _isValid: boolean = true; private _lastValidatedLang: string | null = null; // Remove static styles since we're using Tailwind createRenderRoot() { // Disable shadow DOM to allow Tailwind classes to work return this; } public getUsername(): string { return this.baseUsername.trim(); } public getClanTag(): string | null { return this.clanTag.length >= MIN_CLAN_TAG_LENGTH && this.clanTag.length <= MAX_CLAN_TAG_LENGTH && validateClanTag(this.clanTag).isValid ? this.clanTag : null; } connectedCallback() { super.connectedCallback(); this.loadStoredUsername(); crazyGamesSDK.getUsername().then((username) => { if (username) { this.baseUsername = username; this.validateAndStore(); } }); crazyGamesSDK.addAuthListener((user) => { if (user) { this.baseUsername = user.username; this.validateAndStore(); } }); } protected updated(): void { // Re-validate when translations become available or language changes, // since initial validation may run before translations are loaded. if (this.validationError) { const langSelector = document.querySelector( "lang-selector", ); const lang = langSelector?.currentLang; const hasTranslations = langSelector?.translations ?? langSelector?.defaultTranslations; if (hasTranslations && lang && lang !== this._lastValidatedLang) { this._lastValidatedLang = lang; this.validateAndStore(); } } } private loadStoredUsername() { const storedUsername = localStorage.getItem(usernameKey); if (storedUsername) { this.clanTag = localStorage.getItem(clanTagKey) ?? ""; this.baseUsername = storedUsername; this.validateAndStore(); } else { this.baseUsername = genAnonUsername(); this.validateAndStore(); } } render() { return html`
${this.validationError ? html`
${this.validationError}
` : null} `; } private handleClanTagChange(e: Event) { const input = e.target as HTMLInputElement; const originalValue = input.value; const val = sanitizeClanTag(originalValue); // Only show toast if characters were actually removed (not just uppercased) if (originalValue.toUpperCase() !== val) { input.value = val; // Show toast when invalid characters are removed window.dispatchEvent( new CustomEvent("show-message", { detail: { message: translateText("username.tag_invalid_chars"), color: "red", duration: 2000, }, }), ); } else if (originalValue !== val) { // Just update the input without toast if only case changed input.value = val; } this.clanTag = val; this.validateAndStore(); } private handleUsernameChange(e: Event) { const input = e.target as HTMLInputElement; const originalValue = input.value; const val = originalValue.replace(/[[\]]/g, ""); if (originalValue !== val) { input.value = val; // Show toast when brackets are removed window.dispatchEvent( new CustomEvent("show-message", { detail: { message: translateText("username.invalid_chars"), color: "red", duration: 2000, }, }), ); } this.baseUsername = val; this.validateAndStore(); } private validateAndStore() { const trimmedBase = this.getUsername(); const clanTagResult = validateClanTag(this.clanTag); if (!clanTagResult.isValid) { this._isValid = false; this.validationError = clanTagResult.error ?? ""; return; } const result = validateUsername(trimmedBase); this._isValid = result.isValid; if (result.isValid) { localStorage.setItem(usernameKey, trimmedBase); localStorage.setItem(clanTagKey, this.getClanTag() ?? ""); this.validationError = ""; } else { this.validationError = result.error ?? ""; } } public isValid(): boolean { return this._isValid; } public showValidationFeedback(): void { const message = this.validationError || translateText("username.invalid_chars"); window.dispatchEvent( new CustomEvent("show-message", { detail: { message, color: "red", duration: 2500, }, }), ); } public validateOrShowError(): boolean { if (this.isValid()) { return true; } this.showValidationFeedback(); return false; } } export function genAnonUsername(): string { const uuid = generateCryptoRandomUUID(); const cleanUuid = uuid.replace(/-/g, "").toLowerCase(); const decimal = BigInt(`0x${cleanUuid}`); const threeDigits = decimal % 1000n; return "Anon" + threeDigits.toString().padStart(3, "0"); }