feat: Support direct clan detail links (#3928)

## Description:

Add support for opening clan details directly with `clan=<tag>`

## 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
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## 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-14 19:58:09 +00:00
committed by GitHub
parent 769da27257
commit 6a8900fac2
2 changed files with 28 additions and 2 deletions
+9 -1
View File
@@ -18,6 +18,7 @@ import "./components/clan/ClanTransferView";
import "./components/ConfirmDialog"; import "./components/ConfirmDialog";
import "./components/CopyButton"; import "./components/CopyButton";
import { modalHeader } from "./components/ui/ModalHeader"; import { modalHeader } from "./components/ui/ModalHeader";
import { modalRouter } from "./ModalRouter";
import { translateText } from "./Utils"; import { translateText } from "./Utils";
type View = type View =
@@ -195,6 +196,7 @@ export class ClanModal extends BaseModal {
this.selectedClanTag = ""; this.selectedClanTag = "";
this.myRole = null; this.myRole = null;
this.detailCache = null; this.detailCache = null;
modalRouter.syncArgs("clan", { clan: null, tag: null });
this.gameHistoryCache = null; this.gameHistoryCache = null;
this.setActiveTab(this.previousListTab); this.setActiveTab(this.previousListTab);
}, },
@@ -204,7 +206,12 @@ export class ClanModal extends BaseModal {
} }
protected onOpen(args?: Record<string, unknown>): void { protected onOpen(args?: Record<string, unknown>): void {
const targetTag = typeof args?.tag === "string" ? args.tag : ""; const targetTag =
typeof args?.clan === "string"
? args.clan.trim()
: typeof args?.tag === "string"
? args.tag.trim()
: "";
if (targetTag) { if (targetTag) {
this.openDetail(targetTag.toUpperCase()); this.openDetail(targetTag.toUpperCase());
} }
@@ -489,6 +496,7 @@ export class ClanModal extends BaseModal {
} }
this.selectedClanTag = tag; this.selectedClanTag = tag;
this.view = "detail"; this.view = "detail";
modalRouter.syncArgs("clan", { clan: tag, tag: null });
// modalConfig() returns detail tabs; setActiveTab anchors activeTab to // modalConfig() returns detail tabs; setActiveTab anchors activeTab to
// "overview" and syncs the URL router (routerName = "clan"). // "overview" and syncs the URL router (routerName = "clan").
this.setActiveTab("overview"); this.setActiveTab("overview");
+19 -1
View File
@@ -123,6 +123,24 @@ class ModalRouter {
this.replaceHash("#" + params.toString()); this.replaceHash("#" + params.toString());
} }
/** Called when a router-managed modal changes non-tab route state. */
syncArgs(name: string, args: Record<string, unknown>): void {
if (this.routingFromUrl) return;
if (this.currentName !== name) return;
const params = this.currentHashParams();
params.set("modal", name);
for (const [key, value] of Object.entries(args)) {
if (key === "modal") continue;
if (value === undefined || value === null || value === "") {
params.delete(key);
continue;
}
if (typeof value === "object") continue;
params.set(key, String(value));
}
this.replaceHash("#" + params.toString());
}
/** True if the current hash is `#modal=...`. */ /** True if the current hash is `#modal=...`. */
isHashRouted(): boolean { isHashRouted(): boolean {
const hash = window.location.hash; const hash = window.location.hash;
@@ -142,7 +160,7 @@ class ModalRouter {
if (args) { if (args) {
for (const [key, value] of Object.entries(args)) { for (const [key, value] of Object.entries(args)) {
if (key === "modal") continue; if (key === "modal") continue;
if (value === undefined || value === null) continue; if (value === undefined || value === null || value === "") continue;
if (typeof value === "object") continue; if (typeof value === "object") continue;
params.set(key, String(value)); params.set(key, String(value));
} }