diff --git a/src/components/layout/AppHeader/Navigation.tsx b/src/components/layout/AppHeader/Navigation.tsx
index 26572e76..7df7ddf9 100644
--- a/src/components/layout/AppHeader/Navigation.tsx
+++ b/src/components/layout/AppHeader/Navigation.tsx
@@ -1,5 +1,5 @@
import { Link, useRouter } from "@tanstack/react-router";
-import { ArrowLeft, Users } from "lucide-react";
+import { ArrowLeft, UserPlus, Users } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Tooltip,
@@ -7,6 +7,7 @@ import {
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useAuth } from "@/contexts/AuthContext";
+import { useActiveGroup } from "@/hooks/useActiveGroup";
interface NavigationProps {
showBackButton?: boolean;
@@ -50,6 +51,10 @@ export function Navigation({
}: NavigationProps) {
const { user } = useAuth();
const router = useRouter();
+ const { activeGroup, hasGroups, isLoading: isGroupsLoading } = useActiveGroup();
+
+ const groupsButtonClassName =
+ "border-purple-400/50 text-purple-300 hover:bg-purple-600 hover:text-white hover:border-purple-600 transition-colors";
return (
@@ -68,19 +73,34 @@ export function Navigation({
)}
- {/* Groups Button */}
+ {/* Groups / Active Group Indicator */}
{showGroupsButton && user && (
-
-
- {!isMobile && Groups}
-
+ {hasGroups || isGroupsLoading ? (
+
+
+ {!isMobile && (
+ {activeGroup?.name || "Groups"}
+ )}
+
+ ) : (
+
+
+ {!isMobile && Create/Join a Group}
+
+ )}
)}
diff --git a/src/hooks/useActiveGroup.ts b/src/hooks/useActiveGroup.ts
new file mode 100644
index 00000000..b128ebcf
--- /dev/null
+++ b/src/hooks/useActiveGroup.ts
@@ -0,0 +1,31 @@
+import { useAuth } from "@/contexts/AuthContext";
+import { useUserGroupsQuery } from "@/api/groups/useUserGroups";
+import { resolveActiveGroupId } from "@/lib/activeGroup";
+import type { Group } from "@/api/groups/types";
+
+interface ActiveGroupState {
+ activeGroupId: string | undefined;
+ activeGroup: Group | undefined;
+ groups: Group[];
+ hasGroups: boolean;
+ isLoading: boolean;
+}
+
+export function useActiveGroup(): ActiveGroupState {
+ const { user, profile } = useAuth();
+ const groupsQuery = useUserGroupsQuery(user?.id);
+ const groups = groupsQuery.data ?? [];
+
+ const activeGroupId = resolveActiveGroupId({
+ profileActiveGroupId: profile?.active_group_id,
+ groupIds: groups.map((group) => group.id),
+ });
+
+ return {
+ activeGroupId,
+ activeGroup: groups.find((group) => group.id === activeGroupId),
+ groups,
+ hasGroups: groups.length > 0,
+ isLoading: groupsQuery.isLoading,
+ };
+}
diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts
index ab971e76..953a6dd7 100644
--- a/src/integrations/supabase/types.ts
+++ b/src/integrations/supabase/types.ts
@@ -519,6 +519,7 @@ export type Database = {
};
profiles: {
Row: {
+ active_group_id: string | null;
completed_onboarding: boolean | null;
created_at: string;
email: string | null;
@@ -526,6 +527,7 @@ export type Database = {
username: string | null;
};
Insert: {
+ active_group_id?: string | null;
completed_onboarding?: boolean | null;
created_at?: string;
email?: string | null;
@@ -533,13 +535,22 @@ export type Database = {
username?: string | null;
};
Update: {
+ active_group_id?: string | null;
completed_onboarding?: boolean | null;
created_at?: string;
email?: string | null;
id?: string;
username?: string | null;
};
- Relationships: [];
+ Relationships: [
+ {
+ foreignKeyName: "profiles_active_group_id_fkey";
+ columns: ["active_group_id"];
+ isOneToOne: false;
+ referencedRelation: "groups";
+ referencedColumns: ["id"];
+ },
+ ];
};
set_artists: {
Row: {
diff --git a/src/lib/activeGroup.test.ts b/src/lib/activeGroup.test.ts
new file mode 100644
index 00000000..c8b0b676
--- /dev/null
+++ b/src/lib/activeGroup.test.ts
@@ -0,0 +1,64 @@
+import { describe, expect, it } from "vitest";
+import { resolveActiveGroupId } from "./activeGroup";
+
+describe("resolveActiveGroupId", () => {
+ it("returns undefined when the user has no groups", () => {
+ expect(
+ resolveActiveGroupId({ profileActiveGroupId: null, groupIds: [] }),
+ ).toBeUndefined();
+ });
+
+ it("auto-activates the single group when no active group is set", () => {
+ expect(
+ resolveActiveGroupId({
+ profileActiveGroupId: null,
+ groupIds: ["group-1"],
+ }),
+ ).toBe("group-1");
+ });
+
+ it("does not auto-activate when the user belongs to multiple groups", () => {
+ expect(
+ resolveActiveGroupId({
+ profileActiveGroupId: null,
+ groupIds: ["group-1", "group-2"],
+ }),
+ ).toBeUndefined();
+ });
+
+ it("returns the persisted active group when it is still a membership", () => {
+ expect(
+ resolveActiveGroupId({
+ profileActiveGroupId: "group-2",
+ groupIds: ["group-1", "group-2"],
+ }),
+ ).toBe("group-2");
+ });
+
+ it("ignores a persisted active group the user is no longer a member of", () => {
+ expect(
+ resolveActiveGroupId({
+ profileActiveGroupId: "group-3",
+ groupIds: ["group-1", "group-2"],
+ }),
+ ).toBeUndefined();
+ });
+
+ it("falls back to auto-activation when the stale active group leaves exactly one membership", () => {
+ expect(
+ resolveActiveGroupId({
+ profileActiveGroupId: "group-3",
+ groupIds: ["group-1"],
+ }),
+ ).toBe("group-1");
+ });
+
+ it("prefers the persisted active group over auto-activation when only one group remains", () => {
+ expect(
+ resolveActiveGroupId({
+ profileActiveGroupId: "group-1",
+ groupIds: ["group-1"],
+ }),
+ ).toBe("group-1");
+ });
+});
diff --git a/src/lib/activeGroup.ts b/src/lib/activeGroup.ts
new file mode 100644
index 00000000..c10198e8
--- /dev/null
+++ b/src/lib/activeGroup.ts
@@ -0,0 +1,19 @@
+interface ResolveActiveGroupIdParams {
+ profileActiveGroupId: string | null | undefined;
+ groupIds: string[];
+}
+
+export function resolveActiveGroupId({
+ profileActiveGroupId,
+ groupIds,
+}: ResolveActiveGroupIdParams): string | undefined {
+ if (profileActiveGroupId && groupIds.includes(profileActiveGroupId)) {
+ return profileActiveGroupId;
+ }
+
+ if (groupIds.length === 1) {
+ return groupIds[0];
+ }
+
+ return undefined;
+}
diff --git a/supabase/migrations/20260710154342_add_active_group_id_to_profiles.sql b/supabase/migrations/20260710154342_add_active_group_id_to_profiles.sql
new file mode 100644
index 00000000..62d0766b
--- /dev/null
+++ b/supabase/migrations/20260710154342_add_active_group_id_to_profiles.sql
@@ -0,0 +1,7 @@
+-- Add active_group_id to profiles: the Group a user is currently viewing the
+-- app "as". Global across festivals/editions. Nullable; cleared automatically
+-- if the referenced group is deleted.
+ALTER TABLE public.profiles
+ADD COLUMN active_group_id UUID REFERENCES public.groups(id) ON DELETE SET NULL;
+
+CREATE INDEX idx_profiles_active_group_id ON public.profiles(active_group_id);