Skip to content

Commit

Permalink
fix(frontend): crash on empty planned events (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
GabenGar authored Nov 25, 2024
1 parent 71410d1 commit 92c0093
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 41 deletions.
35 changes: 24 additions & 11 deletions apps/frontend/src/app/(main)/[lang]/planned-events/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { ILocalizableProps, ITranslatableProps } from "#components/types";
import {
PlannedEventCreateForm,
PlannedEventPreview,
countPlannedEvents,
createPlannedEvent,
getPlannedEvents,
type IPlannedEventInit,
Expand All @@ -25,23 +26,35 @@ export function Client({ language, commonTranslation, translation }: IProps) {
const searchParams = useSearchParams();
const [plannedEvents, changePlannedEvents] =
useState<Awaited<ReturnType<typeof getPlannedEvents>>>();
const [isLoading, switchLoading] = useState(true);
const inputPage = searchParams.get("page")?.trim();
const page = !inputPage ? undefined : parseInt(inputPage, 10);

useEffect(() => {
(async () => {
const newPlannedEvents = await getPlannedEvents(page);
try {
switchLoading(true);
const count = await countPlannedEvents();

if (!page) {
const url = createPlannedEventsPageURL(language, {
page: newPlannedEvents.pagination.totalPages,
});
router.replace(url);
if (count === 0) {
return;
}

return;
}
const newPlannedEvents = await getPlannedEvents(page);

if (!page) {
const url = createPlannedEventsPageURL(language, {
page: newPlannedEvents.pagination.totalPages,
});
router.replace(url);

changePlannedEvents(newPlannedEvents);
return;
}

changePlannedEvents(newPlannedEvents);
} finally {
switchLoading(false);
}
})();
}, [page]);

Expand Down Expand Up @@ -70,9 +83,9 @@ export function Client({ language, commonTranslation, translation }: IProps) {
)}
</Overview>

{!plannedEvents ? (
{isLoading ? (
<Loading />
) : plannedEvents.pagination.totalCount === 0 ? (
) : !plannedEvents ? (
<Overview headingLevel={2}>
{() => (
<OverviewHeader>
Expand Down
25 changes: 25 additions & 0 deletions apps/frontend/src/browser/store/indexed/count.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getTransaction } from "./get-transaction";
import type { IStorageName } from "./types";

export async function countIndexedDBItems(
storeName: IStorageName,
): Promise<number> {
const resultCount = await new Promise<number>((resolve, reject) => {
getTransaction([storeName], "readonly").then((transaction) => {
transaction.onerror = (event) => {
reject(event);
};

const objectStore = transaction.objectStore(storeName);
const countRequest = objectStore.count();

countRequest.onsuccess = (event) => {
const count = (event.target as typeof countRequest).result;

resolve(count);
}
});
});

return resultCount
}
2 changes: 1 addition & 1 deletion apps/frontend/src/browser/store/indexed/create.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getOneIndexedDBItem } from "./get";
import { getOneIndexedDBItem } from "./one";
import { getTransaction } from "./get-transaction";
import type { IStorageName } from "./types";

Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/browser/store/indexed/delete.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getOneIndexedDBItem } from "./get";
import { getOneIndexedDBItem } from "./one";
import { getTransaction } from "./get-transaction";
import type { IStorageName } from "./types";

Expand Down
30 changes: 5 additions & 25 deletions apps/frontend/src/browser/store/indexed/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,7 @@ import {
import { getTransaction } from "./get-transaction";
import type { IStorageName } from "./types";

export async function getOneIndexedDBItem<Type>(
storeName: IStorageName,
query: IDBValidKey,
validate: (input: unknown) => asserts input is Type,
): Promise<Type> {
const result = await new Promise((resolve, reject) => {
getTransaction([storeName], "readonly").then((transaction) => {
const objectStore = transaction.objectStore(storeName);
const request = objectStore.get(query);

request.onerror = (event) => {
reject(event);
};

request.onsuccess = (event) => {
const result: unknown = (event.target as typeof request).result;

resolve(result);
};
});
});

validate(result);

return result;
}

export async function getManyIndexedDBItems<Type>(
storeName: IStorageName,
Expand All @@ -51,6 +26,11 @@ export async function getManyIndexedDBItems<Type>(

countRequest.onsuccess = (event) => {
const count = (event.target as typeof countRequest).result;

if (count === 0) {
return reject(new Error(`No results found for store "${storeName}".`))
}

const pagination = createClientPagination(count, limit, page);
// collecting all keys is kinda cringe
// but better than getting all values
Expand Down
4 changes: 3 additions & 1 deletion apps/frontend/src/browser/store/indexed/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export { getOneIndexedDBItem, getManyIndexedDBItems } from "./get";
export { countIndexedDBItems } from "./count";
export { getManyIndexedDBItems } from "./get";
export { getOneIndexedDBItem } from "./one";
export { createOneIndexedDBItem } from "./create";
export { updateOneIndexedDBItem } from "./update";
export { deleteOneIndexedDBItem } from "./delete";
Expand Down
29 changes: 29 additions & 0 deletions apps/frontend/src/browser/store/indexed/one.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { getTransaction } from "./get-transaction";
import type { IStorageName } from "./types";

export async function getOneIndexedDBItem<Type>(
storeName: IStorageName,
query: IDBValidKey,
validate: (input: unknown) => asserts input is Type,
): Promise<Type> {
const result = await new Promise((resolve, reject) => {
getTransaction([storeName], "readonly").then((transaction) => {
const objectStore = transaction.objectStore(storeName);
const request = objectStore.get(query);

request.onerror = (event) => {
reject(event);
};

request.onsuccess = (event) => {
const result: unknown = (event.target as typeof request).result;

resolve(result);
};
});
});

validate(result);

return result;
}
2 changes: 1 addition & 1 deletion apps/frontend/src/browser/store/indexed/update.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getOneIndexedDBItem } from "./get";
import { getOneIndexedDBItem } from "./one";
import { getTransaction } from "./get-transaction";
import type { IStorageName } from "./types";

Expand Down
6 changes: 5 additions & 1 deletion apps/frontend/src/entities/planned-event/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export { createPlannedEvent } from "./lib/create";
export { getPlannedEvents, getPlannedEvent } from "./lib/get";
export {
countPlannedEvents,
getPlannedEvents,
getPlannedEvent,
} from "./lib/get";
export { editPlannedEvent } from "./lib/edit";
export { deletePlannedEvent } from "./lib/delete";
export { PlannedEventCreateForm } from "./forms/create";
Expand Down
7 changes: 7 additions & 0 deletions apps/frontend/src/entities/planned-event/lib/get.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import {
getManyIndexedDBItems,
getOneIndexedDBItem,
countIndexedDBItems,
} from "#browser/store/indexed";
import type { IPaginatedCollection } from "#lib/pagination";
import type { IPlannedEvent } from "../types";
import { validatePlannedEvent } from "./validate";

export async function countPlannedEvents(): Promise<number> {
const count = await countIndexedDBItems("planned_events");

return count;
}

export async function getPlannedEvents(
page?: number,
): Promise<IPaginatedCollection<IPlannedEvent>> {
Expand Down

0 comments on commit 92c0093

Please sign in to comment.