Skip to content

Commit 1678860

Browse files
committed
fix: always render the session page nav bar
The previous commit improved matters on the session page by handling `isError` and `isLoading` conditions, but has annoying behavior when the student is typing a program name into the search bar and we're doing live search, because the sessions page was rendered all-or-nothing. Now we always show the nav bar. This means we no longer have a nice single component for the sessions page (i.e., the `SessionsPage` component is no more), but `ChooseSession` is now more or less the same thing, only with better error and loading behavior. I haven't bothered to add a Storybook story for it, but it would be easyenough to add later if we need it. Signed-off-by: Drew Hess <src@drewhess.com>
1 parent 500c49f commit 1678860

4 files changed

Lines changed: 85 additions & 235 deletions

File tree

src/components/ChooseSession/index.tsx

Lines changed: 85 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import type { MouseEventHandler } from "react";
21
import { useState } from "react";
32
import { useCookies } from "react-cookie";
4-
import { exampleAccount, SessionsPage } from "@/components";
5-
import type { PaginatedMeta, Session, Uuid } from "@/primer-api";
3+
import {
4+
exampleAccount,
5+
SessionList,
6+
SessionNameModal,
7+
SessionsNavBar,
8+
SimplePaginationBar,
9+
} from "@/components";
10+
import type { Uuid } from "@/primer-api";
611
import {
712
useGetSessionList,
813
useCreateSession,
@@ -15,6 +20,12 @@ import { useQueryClient } from "@tanstack/react-query";
1520
const ChooseSession = (): JSX.Element => {
1621
const [cookies] = useCookies(["id"]);
1722

23+
const [importPrelude, setImportPrelude] = useState(true);
24+
const [showModal, setShowModal] = useState(false);
25+
const onClickNewProgram = (): void => {
26+
setShowModal(true);
27+
};
28+
1829
// NOTE: pagination in our API is 1-indexed.
1930
const [page, setPage] = useState(1);
2031
const [pageSize] = useState(20);
@@ -68,64 +79,88 @@ const ChooseSession = (): JSX.Element => {
6879
}
6980
);
7081

71-
// Note that we show data if it's available, without first checking for
82+
// Note that we show data if it's available, regardless of the status of
7283
// `isLoading` or `isError`. This means we may show stale data, but we prefer
73-
// this over showing a loading message or an error for short server outages. See:
84+
// this over showing a loading message or an error for short server outages.
85+
// See:
7486
//
7587
// https://tkdodo.eu/blog/status-checks-in-react-query
7688
//
7789
// Note that React Query will not show stale data indefinitely, and will
7890
// eventually show an error message if the data is stale for too long.
7991

80-
if (data) {
81-
const sessions: Session[] = data.items;
82-
const meta: PaginatedMeta = data.meta;
83-
const startIndex: number = (meta.thisPage - 1) * meta.pageSize + 1;
84-
85-
const onClickNextPage: MouseEventHandler<unknown> | undefined =
86-
meta.thisPage < meta.lastPage ? () => setPage(page + 1) : undefined;
87-
const onClickPreviousPage: MouseEventHandler<unknown> | undefined =
88-
meta.thisPage > 1 ? () => setPage(page - 1) : undefined;
89-
90-
return (
91-
<SessionsPage
92-
account={{ ...exampleAccount, id: cookies.id }}
93-
sessions={sessions}
94-
startIndex={startIndex}
95-
numItems={meta.pageSize}
96-
totalItems={meta.totalItems}
97-
onClickNewProgram={(name: string, importPrelude: boolean) =>
98-
newSession.mutate({ data: { name, importPrelude } })
99-
}
100-
onClickNextPage={onClickNextPage}
101-
onClickPreviousPage={onClickPreviousPage}
102-
onClickDelete={(sessionId) => deleteSession.mutate({ sessionId })}
103-
onSubmitSearch={(nameFilter: string) => {
104-
// Unlike `onChangeSearch`, this callback is always triggered
105-
// by an explicit action, and never by, e.g., a page refresh,
106-
// so we always want to reset the page when this callback is
107-
// invoked.
108-
setSessionNameFilter(nameFilter);
109-
setPage(1);
110-
}}
111-
onChangeSearch={(nameFilter: string) => {
112-
// For technical reasons, this callback may be triggered even
113-
// if the value of the search term didn't actually change
114-
// (e.g., because the page is redrawn), and in these cases, we
115-
// don't want to update the page, so we filter these spurious
116-
// "changes" out.
117-
if (nameFilter != sessionNameFilter) {
92+
return (
93+
<div className="relative grid h-[100dvh] grid-cols-1 grid-rows-[auto,1fr] overflow-hidden">
94+
<div className="relative z-40 px-1 shadow-md lg:px-4">
95+
<SessionsNavBar
96+
onClickNewProgram={onClickNewProgram}
97+
account={{ ...exampleAccount, id: cookies.id }}
98+
onSubmitSearch={(nameFilter: string) => {
99+
// Unlike `onChangeSearch`, this callback is always triggered
100+
// by an explicit action, and never by, e.g., a page refresh,
101+
// so we always want to reset the page when this callback is
102+
// invoked.
118103
setSessionNameFilter(nameFilter);
119104
setPage(1);
120-
}
105+
}}
106+
onChangeSearch={(nameFilter: string) => {
107+
// For technical reasons, this callback may be triggered even
108+
// if the value of the search term didn't actually change
109+
// (e.g., because the page is redrawn), and in these cases, we
110+
// don't want to update the page, so we filter these spurious
111+
// "changes" out.
112+
if (nameFilter != sessionNameFilter) {
113+
setSessionNameFilter(nameFilter);
114+
setPage(1);
115+
}
116+
}}
117+
/>
118+
</div>
119+
<div className="max-h-screen overflow-auto rounded-sm bg-grey-primary p-3 shadow-inner">
120+
{data ? (
121+
<SessionList
122+
sessions={data.items}
123+
onClickDelete={(sessionId) => deleteSession.mutate({ sessionId })}
124+
/>
125+
) : isError ? (
126+
<div>Error: {error.message}</div>
127+
) : (
128+
<div>Loading...</div>
129+
)}
130+
</div>
131+
<div className="relative z-40 px-1 shadow-2xl lg:px-4">
132+
{data && (
133+
<SimplePaginationBar
134+
itemNamePlural="sessions"
135+
startIndex={(data.meta.thisPage - 1) * data.meta.pageSize + 1}
136+
numItems={data.items.length}
137+
totalItems={data.meta.totalItems}
138+
onClickNextPage={
139+
data.meta.thisPage < data.meta.lastPage
140+
? () => setPage(page + 1)
141+
: undefined
142+
}
143+
onClickPreviousPage={
144+
data.meta.thisPage > 1 ? () => setPage(page - 1) : undefined
145+
}
146+
/>
147+
)}
148+
</div>
149+
<SessionNameModal
150+
open={showModal}
151+
importPrelude={importPrelude}
152+
onClose={() => setShowModal(false)}
153+
onCancel={() => setShowModal(false)}
154+
onSubmit={(name: string, _importPrelude: boolean) => {
155+
// Remember the student's choice of whether or not to import the Prelude.
156+
setImportPrelude(_importPrelude);
157+
newSession.mutate({
158+
data: { name, importPrelude: _importPrelude },
159+
});
121160
}}
122161
/>
123-
);
124-
} else if (isError) {
125-
return <div>Error: {error.message}</div>;
126-
} else {
127-
return <div>Loading...</div>;
128-
}
162+
</div>
163+
);
129164
};
130165

131166
export default ChooseSession;

src/components/SessionsPage/SessionsPage.stories.tsx

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/components/SessionsPage/index.tsx

Lines changed: 0 additions & 134 deletions
This file was deleted.

src/components/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export { default as SessionList } from "./SessionList";
2222
export { default as SessionNameModal } from "./SessionNameModal";
2323
export { default as SessionPreview } from "./SessionPreview";
2424
export { default as SessionsNavBar } from "./SessionsNavBar";
25-
export { default as SessionsPage } from "./SessionsPage";
2625
export { default as SimplePaginationBar } from "./SimplePaginationBar";
2726
export { default as Toolbar } from "./Toolbar";
2827
export { default as TreeReactFlow, TreeReactFlowOne } from "./TreeReactFlow";

0 commit comments

Comments
 (0)