1import { Button } from "../ui/button";
2import { Alert, AlertAction, AlertDescription } from "../ui/alert";
3import { SettingsPageContainer } from "./settingsLayout";
4import { useSettingsScope } from "./SettingsScopeContext";
5import { useEnvironments } from "../../state/environments";
6import type { SettingsScopeSearch } from "./settingsScope";
7import { useSettingsProjectGroups } from "./useSettingsProjectGroups";
8import { useLocation, useNavigate } from "@tanstack/react-router";
9import type { EnvironmentId } from "@t3tools/contracts";
10
11/** Offer an explicit target change when a category has no settings at this scope. */
12export function SettingsScopeNotice({
13 children,
14 target,
15 targetId,
16 eligibleEnvironmentIds,
17}: {
18 children: string;
19 target: "environment" | "all" | "project" | "checkout";
20 targetId?: string;
21 eligibleEnvironmentIds?: readonly EnvironmentId[];
22}) {
23 const { selectScope, search } = useSettingsScope();
24 const navigate = useNavigate({ from: "/settings" });
25 const pathname = useLocation({ select: (location) => location.pathname });
26 const { environments } = useEnvironments();
27 const groups = useSettingsProjectGroups();
28 const choices: { label: string; search: SettingsScopeSearch }[] =
29 target === "checkout"
30 ? groups
31 .filter((group) => !search.project || group.projectKey === search.project)
32 .flatMap((group) =>
33 group.memberProjects.map((member) => ({
34 label: `${group.displayName} · ${member.environmentLabel ?? "Environment"} · ${member.workspaceRoot}`,
35 search: {
36 project: group.projectKey,
37 machine: member.environmentId,
38 checkout: member.physicalProjectKey,
39 },
40 })),
41 )
42 : target === "project"
43 ? groups.map((group) => ({
44 label: group.displayName,
45 search: { project: group.projectKey },
46 }))
47 : target === "environment"
48 ? environments
49 .filter(
50 (entry) =>
51 eligibleEnvironmentIds === undefined ||
52 eligibleEnvironmentIds.includes(entry.environmentId),
53 )
54 .map((entry) => ({
55 label: environments.some(
56 (other) =>
57 other.environmentId !== entry.environmentId && other.label === entry.label,
58 )
59 ? `${entry.label} · ${entry.displayUrl || entry.environmentId}`
60 : entry.label,
61 search: { machine: entry.environmentId },
62 }))
63 : [{ label: "Open all environments", search: {} }];
64 return (
65 <SettingsPageContainer>
66 <Alert role="status">
67 <AlertDescription>
68 <p>{children}</p>
69 <AlertAction className="flex-wrap">
70 {choices.map((choice) => (
71 <Button
72 key={JSON.stringify(choice.search)}
73 size="sm-multiline"
74 variant="outline"
75 className="max-w-full break-all text-left"
76 onClick={() => {
77 if (targetId)
78 void navigate({ to: pathname, search: () => choice.search, hash: targetId });
79 else selectScope(choice.search);
80 }}
81 >
82 {choice.label}
83 </Button>
84 ))}
85 </AlertAction>
86 </AlertDescription>
87 </Alert>
88 </SettingsPageContainer>
89 );
90}