Skip to content
Merged
42 changes: 32 additions & 10 deletions site/src/pages/TemplatesPage/TemplatesFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import { API } from "api/api";
import type { Organization } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { Filter, MenuSkeleton, type useFilter } from "components/Filter/Filter";
import {
Filter,
MenuSkeleton,
type UseFilterResult,
} from "components/Filter/Filter";
import { useFilterMenu } from "components/Filter/menu";
import {
SelectFilter,
type SelectFilterOption,
} from "components/Filter/SelectFilter";
import type { FC } from "react";
import {
type UserFilterMenu,
UserMenu,
} from "../../components/Filter/UserFilter";
import { useDashboard } from "../../modules/dashboard/useDashboard";

interface TemplatesFilterProps {
filter: ReturnType<typeof useFilter>;
filter: UseFilterResult;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better. Thank you!

error?: unknown;

userMenu?: UserFilterMenu;
}

export const TemplatesFilter: FC<TemplatesFilterProps> = ({
filter,
error,
userMenu,
}) => {
const { showOrganizations } = useDashboard();
const width = showOrganizations ? 175 : undefined;
const organizationMenu = useFilterMenu({
onChange: (option) =>
filter.update({ ...filter.values, organization: option?.value }),
Expand Down Expand Up @@ -50,15 +64,23 @@ export const TemplatesFilter: FC<TemplatesFilterProps> = ({
filter={filter}
error={error}
options={
<SelectFilter
placeholder="All organizations"
label="Select an organization"
options={organizationMenu.searchOptions}
selectedOption={organizationMenu.selectedOption ?? undefined}
onSelect={organizationMenu.selectOption}
/>
<>
{userMenu && <UserMenu width={width} menu={userMenu} />}
<SelectFilter
placeholder="All organizations"
label="Select an organization"
options={organizationMenu.searchOptions}
selectedOption={organizationMenu.selectedOption ?? undefined}
onSelect={organizationMenu.selectOption}
/>
</>
}
optionsSkeleton={
<>
{userMenu && <MenuSkeleton />}
<MenuSkeleton />
</>
}
optionsSkeleton={<MenuSkeleton />}
/>
);
};
Expand Down
52 changes: 47 additions & 5 deletions site/src/pages/TemplatesPage/TemplatesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { workspacePermissionsByOrganization } from "api/queries/organizations";
import { templateExamples, templates } from "api/queries/templates";
import { useFilter } from "components/Filter/Filter";
import { useUserFilterMenu } from "components/Filter/UserFilter";
import { useAuthenticated } from "hooks";
import { useDashboard } from "modules/dashboard/useDashboard";
import type { FC } from "react";
Expand All @@ -15,14 +16,13 @@ const TemplatesPage: FC = () => {
const { showOrganizations } = useDashboard();

const [searchParams, setSearchParams] = useSearchParams();
const filter = useFilter({
fallbackFilter: "deprecated:false",
const filterState = useTemplatesFilter({
searchParams,
onSearchParamsChange: setSearchParams,
onUpdate: () => {}, // reset pagination
onFilterChange: () => {},
});

const templatesQuery = useQuery(templates({ q: filter.query }));
const templatesQuery = useQuery(templates({ q: filterState.filter.query }));
const examplesQuery = useQuery({
...templateExamples(),
enabled: permissions.createTemplates,
Expand All @@ -47,7 +47,7 @@ const TemplatesPage: FC = () => {
</Helmet>
<TemplatesPageView
error={error}
filter={filter}
filterState={filterState}
showOrganizations={showOrganizations}
canCreateTemplates={permissions.createTemplates}
examples={examplesQuery.data}
Expand All @@ -59,3 +59,45 @@ const TemplatesPage: FC = () => {
};

export default TemplatesPage;

export type TemplateFilterState = {
filter: ReturnType<typeof useFilter>;
menus: {
user?: ReturnType<typeof useUserFilterMenu>;
};
};

type UseTemplatesFilterOptions = {
searchParams: URLSearchParams;
onSearchParamsChange: (params: URLSearchParams) => void;
onFilterChange: () => void;
};

const useTemplatesFilter = ({
searchParams,
onSearchParamsChange,
onFilterChange,
}: UseTemplatesFilterOptions): TemplateFilterState => {
const filter = useFilter({
fallbackFilter: "deprecated:false",
searchParams,
onSearchParamsChange,
onUpdate: onFilterChange,
});

const { permissions } = useAuthenticated();
const canFilterByUser = permissions.viewAllUsers;
const userMenu = useUserFilterMenu({
value: filter.values.author,
onChange: (option) =>
filter.update({ ...filter.values, author: option?.value }),
enabled: canFilterByUser,
});

return {
filter,
menus: {
user: canFilterByUser ? userMenu : undefined,
},
};
};
55 changes: 43 additions & 12 deletions site/src/pages/TemplatesPage/TemplatesPageView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,35 @@ import {
MockTemplate,
MockTemplateExample,
MockTemplateExample2,
MockUserOwner,
mockApiError,
} from "testHelpers/entities";
import { withDashboardProvider } from "testHelpers/storybook";
import type { Meta, StoryObj } from "@storybook/react-vite";
import { getDefaultFilterProps } from "components/Filter/storyHelpers";
import {
getDefaultFilterProps,
MockMenu,
} from "components/Filter/storyHelpers";
import type { TemplateFilterState } from "./TemplatesPage";
import { TemplatesPageView } from "./TemplatesPageView";

const defaultFilterProps = getDefaultFilterProps<TemplateFilterState>({
query: "deprecated:false",
menus: {
organizations: MockMenu,
},
values: {
author: MockUserOwner.username,
},
});

const meta: Meta<typeof TemplatesPageView> = {
title: "pages/TemplatesPage",
decorators: [withDashboardProvider],
parameters: { chromatic: chromaticWithTablet },
component: TemplatesPageView,
args: {
...getDefaultFilterProps({
query: "deprecated:false",
menus: {},
values: {},
}),
filterState: defaultFilterProps,
},
};

Expand Down Expand Up @@ -104,12 +115,32 @@ export const WithFilteredAllTemplates: Story = {
args: {
...WithTemplates.args,
templates: [],
...getDefaultFilterProps({
query: "deprecated:false searchnotfound",
menus: {},
values: {},
used: true,
}),
filterState: {
filter: {
...defaultFilterProps.filter,
query: "deprecated:false searchnotfound",
values: {},
used: true,
},
menus: defaultFilterProps.menus,
},
},
};

export const WithUserDropdown: Story = {
args: {
...WithTemplates.args,
filterState: {
...defaultFilterProps,
menus: {
user: MockMenu,
},
filter: {
...defaultFilterProps.filter,
query: "author:me",
values: { author: "me" },
},
},
},
};

Expand Down
14 changes: 9 additions & 5 deletions site/src/pages/TemplatesPage/TemplatesPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { AvatarData } from "components/Avatar/AvatarData";
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton";
import { DeprecatedBadge } from "components/Badges/Badges";
import { Button } from "components/Button/Button";
import type { useFilter } from "components/Filter/Filter";
import {
HelpTooltip,
HelpTooltipContent,
Expand Down Expand Up @@ -52,6 +51,7 @@ import {
} from "utils/templates";
import { EmptyTemplates } from "./EmptyTemplates";
import { TemplatesFilter } from "./TemplatesFilter";
import type { TemplateFilterState } from "./TemplatesPage";

const Language = {
developerCount: (activeCount: number): string => {
Expand Down Expand Up @@ -184,7 +184,7 @@ const TemplateRow: FC<TemplateRowProps> = ({

interface TemplatesPageViewProps {
error?: unknown;
filter: ReturnType<typeof useFilter>;
filterState: TemplateFilterState;
showOrganizations: boolean;
canCreateTemplates: boolean;
examples: TemplateExample[] | undefined;
Expand All @@ -194,7 +194,7 @@ interface TemplatesPageViewProps {

export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
error,
filter,
filterState,
showOrganizations,
canCreateTemplates,
examples,
Expand Down Expand Up @@ -229,7 +229,11 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
</PageHeaderSubtitle>
</PageHeader>

<TemplatesFilter filter={filter} error={error} />
<TemplatesFilter
filter={filterState.filter}
error={error}
userMenu={filterState.menus.user}
/>
{/* Validation errors are shown on the filter, other errors are an alert box. */}
{hasError(error) && !isApiValidationError(error) && (
<ErrorAlert error={error} />
Expand All @@ -256,7 +260,7 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = ({
<EmptyTemplates
canCreateTemplates={canCreateTemplates}
examples={examples ?? []}
isUsingFilter={filter.used}
isUsingFilter={filterState.filter.used}
/>
) : (
templates?.map((template) => (
Expand Down
Loading