- {note}{" "}
+ return (
+
+ {note ? (
+
{note}
+ ) : null}
+
+ {memberCount === 1 ? (
+
+ ) : null}
{editable ? (
}
onClick={() => setEditing(true)}
- className="mt-2 ml-auto"
+ className="ml-auto"
>
- {t("q:looking.groups.editNote")}
+ {note
+ ? t("q:looking.groups.editNote")
+ : t("q:looking.groups.addNote")}
) : null}
- );
- }
+
+ );
+}
- if (!editable) return null;
+/** Changes the sub preference in place so the group keeps its spot in the list. */
+function LFGStayAsSubSwitch({ isStayAsSub }: { isStayAsSub: boolean }) {
+ const { t } = useTranslation(["forms"]);
+ const { submit, fetcher } = useActionSubmit(lookingSchema, {
+ encType: "application/json",
+ });
+
+ const submitted = fetcher.json as { stayAsSub: boolean } | undefined;
return (
-
setEditing(true)}
+ submit("SET_STAY_AS_SUB", { stayAsSub })}
>
- {t("q:looking.groups.addNote")}
-
+ {t("forms:labels.stayAsSub")}
+
);
}
function LFGEditGroupForm({
note,
- isStayAsSub,
- memberCount,
stopEditing,
}: {
note: string | null;
- isStayAsSub: boolean;
- memberCount: number;
stopEditing: () => void;
}) {
const { t } = useTranslation(["common"]);
@@ -321,7 +330,7 @@ function LFGEditGroupForm({
return (
}
>
- {({ FormField }) => (
- <>
-
- {memberCount === 1 ? : null}
- >
- )}
+ {({ FormField }) => }
);
}
diff --git a/app/features/tournament-lfg/tournament-lfg-schemas.ts b/app/features/tournament-lfg/tournament-lfg-schemas.ts
index 7e21ef7c8..01db46b47 100644
--- a/app/features/tournament-lfg/tournament-lfg-schemas.ts
+++ b/app/features/tournament-lfg/tournament-lfg-schemas.ts
@@ -37,7 +37,6 @@ export const joinQueueFormSchema = v.object({
export const updateGroupFormSchema = v.object({
_action: stringConstant("UPDATE_GROUP"),
note: noteFieldSchema,
- stayAsSub: stayAsSubFieldSchema,
});
export const lookingSchema = v.union([
@@ -67,6 +66,10 @@ export const lookingSchema = v.union([
userId: id,
}),
updateGroupFormSchema,
+ v.object({
+ _action: _action("SET_STAY_AS_SUB"),
+ stayAsSub: v.boolean(),
+ }),
v.object({
_action: _action("LEAVE_GROUP"),
}),
diff --git a/changelog/2026-09-05-lfg-stay-as-sub-toggle.md b/changelog/2026-09-05-lfg-stay-as-sub-toggle.md
new file mode 100644
index 000000000..0b30fd023
--- /dev/null
+++ b/changelog/2026-09-05-lfg-stay-as-sub-toggle.md
@@ -0,0 +1,5 @@
+---
+navItem: calendar
+type: feature
+---
+Toggle "Stay as sub" without having to leave the tournament LFG list first
diff --git a/e2e/pages/tournament/tournament-looking-page.ts b/e2e/pages/tournament/tournament-looking-page.ts
new file mode 100644
index 000000000..ae8ff3243
--- /dev/null
+++ b/e2e/pages/tournament/tournament-looking-page.ts
@@ -0,0 +1,31 @@
+import type { Page } from "@playwright/test";
+import { joinQueueFormSchema } from "~/features/tournament-lfg/tournament-lfg-schemas";
+import { tournamentSubsPage } from "~/utils/urls";
+import { navigate, waitForPOSTResponse } from "../../helpers/playwright";
+import { createFormHelpers } from "../../helpers/playwright-form";
+
+/** `/to/:id/looking` — the groups view shown while registration is still open. */
+export class TournamentLookingPage {
+ private readonly page: Page;
+ readonly joinQueueForm;
+ readonly locators;
+
+ constructor(page: Page) {
+ this.page = page;
+ this.joinQueueForm = createFormHelpers(page, joinQueueFormSchema);
+ this.locators = {
+ stayAsSubSwitch: page.getByRole("switch", { name: "Stay as sub" }),
+ };
+ }
+
+ goto(tournamentId: number) {
+ return navigate({ page: this.page, url: tournamentSubsPage(tournamentId) });
+ }
+
+ toggleStayAsSub() {
+ return waitForPOSTResponse(this.page, () =>
+ // the switch input itself is visually hidden behind its indicator
+ this.locators.stayAsSubSwitch.click({ force: true }),
+ );
+ }
+}
diff --git a/e2e/tournament-lfg.spec.ts b/e2e/tournament-lfg.spec.ts
index 9c2932449..ccb6d0257 100644
--- a/e2e/tournament-lfg.spec.ts
+++ b/e2e/tournament-lfg.spec.ts
@@ -1,7 +1,8 @@
-import { subDays } from "date-fns";
+import { addDays, subDays } from "date-fns";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import { expect, impersonate, isNotVisible, test } from "./helpers/playwright";
+import { TournamentLookingPage } from "./pages/tournament/tournament-looking-page";
import { TournamentSubsPage } from "./pages/tournament/tournament-subs-page";
const SUB_NAME = "Subby Sam";
@@ -44,4 +45,32 @@ test.describe("Tournament LFG", () => {
await expect(subs.locators.noPostsText).toBeVisible();
await expect(subs.locators.addPostButton).toBeVisible();
});
+
+ test("player changes their sub preference without leaving the queue", async ({
+ page,
+ factories,
+ }) => {
+ // registration is open (start time in the future) so the groups view is shown
+ const tournament = await factories.TournamentFactory.create({
+ authorId: ADMIN_ID,
+ startTimes: [dateToDatabaseTimestamp(addDays(new Date(), 1))],
+ });
+ const sub = await factories.UserFactory.create({
+ discordName: SUB_NAME,
+ });
+
+ await impersonate(page, sub.id);
+
+ const looking = new TournamentLookingPage(page);
+ await looking.goto(tournament.id);
+
+ await looking.joinQueueForm.submit();
+
+ await expect(looking.locators.stayAsSubSwitch).not.toBeChecked();
+
+ await looking.toggleStayAsSub();
+
+ await looking.goto(tournament.id);
+ await expect(looking.locators.stayAsSubSwitch).toBeChecked();
+ });
});