Make clan tag warning clickable (#4163)

> **Before opening a PR:** discuss new features on
[Discord](https://discord.gg/K9zernJB5z) first, and file bugs or small
improvements as
[issues](https://github.com/openfrontio/OpenFrontIO/issues/new/choose).
You must be assigned to an `approved` issue — unsolicited PRs will be
auto-closed.

**Add approved & assigned issue number here:**

Resolves #4154

## Description:

Adds a join path from reserved clan tag warnings to the clan detail
modal.


https://github.com/user-attachments/assets/cc0f4cb8-be8e-414a-8147-7a744069999e


## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory

## Please put your Discord username so you can be contacted if a bug or
regression is found:

aotumuri
This commit is contained in:
Aotumuri
2026-06-11 05:42:22 +09:00
committed by GitHub
parent e38b25f206
commit dda47b0813
5 changed files with 57 additions and 12 deletions
+1 -1
View File
@@ -674,7 +674,7 @@
"tag_too_short": "Clan tag must be 2-5 alphanumeric characters.",
"tag_too_long": "Clan tag cannot exceed 5 characters.",
"tag_invalid_chars": "Clan tag can only contain letters and numbers.",
"tag_not_member": "Join the {tag} clan before using its tag.",
"tag_not_member": "Join the {tag} clan before using its tag. Click this message to join it.",
"tag_check_failed": "Couldn't verify clan tag. Try again or remove it."
},
"host_modal": {
+3
View File
@@ -233,6 +233,9 @@ export async function joinClan(
if (res.status === 429) {
return { error: "clan_modal.error_rate_limited_generic" };
}
if (res.status === 401) {
return { error: "clan_modal.sign_in_for_clans" };
}
if (res.status === 403) {
const body = await res.json().catch(() => ({}));
const b = body as { code?: string; reason?: string | null };
+14 -3
View File
@@ -203,8 +203,12 @@ export class ClanModal extends BaseModal {
});
}
protected onOpen(): void {
this.loadMyClans();
protected onOpen(args?: Record<string, unknown>): void {
const targetTag = typeof args?.tag === "string" ? args.tag : "";
if (targetTag) {
this.openDetail(targetTag.toUpperCase());
}
this.loadMyClans({ allowGuest: Boolean(targetTag) });
}
protected onClose(): void {
@@ -219,12 +223,19 @@ export class ClanModal extends BaseModal {
this.gameHistoryCache = null;
}
private async loadMyClans() {
private async loadMyClans(opts: { allowGuest?: boolean } = {}) {
this.loading = true;
try {
const me = await getUserMe();
if (!this.isModalOpen) return;
if (!me || Object.keys(me.user).length === 0) {
if (opts.allowGuest) {
this.myPublicId = null;
this.myPendingRequests = [];
this.myClanRoles = new Map();
this.myClans = [];
return;
}
window.dispatchEvent(
new CustomEvent("show-message", {
detail: {
+36 -8
View File
@@ -174,18 +174,46 @@ export class UsernameInput extends LitElement {
${this.validationError}
</div>`
: this.clanTagOwnershipError
? html`<div
id="clan-tag-validation-error"
class="absolute top-full left-0 z-50 mt-1 px-3 py-2 text-sm font-medium border border-red-500/50 rounded-lg bg-red-900/90 text-red-200 backdrop-blur-md shadow-lg whitespace-nowrap"
>
${translateText(this.clanTagOwnershipError, {
tag: this.clanTag,
})}
</div>`
? this.renderClanTagOwnershipError()
: null}
`;
}
private renderClanTagOwnershipError() {
const content = translateText(this.clanTagOwnershipError, {
tag: this.clanTag,
});
const className =
"absolute top-full left-0 z-50 mt-1 px-3 py-2 text-sm font-medium border border-red-500/50 rounded-lg bg-red-900/90 text-red-200 backdrop-blur-md shadow-lg whitespace-nowrap";
if (this.clanTagOwnershipError !== "username.tag_not_member") {
return html`<div id="clan-tag-validation-error" class=${className}>
${content}
</div>`;
}
const tag = this.clanTag;
return html`<button
id="clan-tag-validation-error"
type="button"
class="${className} underline decoration-red-200/50 underline-offset-2 hover:bg-red-800/90 focus:outline-none focus:ring-2 focus:ring-red-200/70"
@click=${() => this.openClanJoinModal(tag)}
>
${content}
</button>`;
}
private openClanJoinModal(tag: string) {
window.showPage?.("page-clan");
void customElements.whenDefined("clan-modal").then(() => {
document
.querySelector<
HTMLElement & { open: (args: { tag: string }) => void }
>("clan-modal")
?.open({ tag });
});
}
private handleClanTagChange(e: Event) {
const input = e.target as HTMLInputElement;
const originalValue = input.value;
@@ -254,6 +254,9 @@ export class ClanDetailView extends LitElement {
try {
const result = await joinClan(this.selectedClan.tag);
if ("error" in result) {
if (result.error === "clan_modal.sign_in_for_clans") {
window.showPage?.("page-account");
}
showToast(
result.reason
? translateText(result.error, { reason: result.reason })