Skip to content

Commit a081a06

Browse files
committed
fix: improve error handling and page title management in Bookmarks, Event, Person, and Schedule components
1 parent 1072ecd commit a081a06

4 files changed

Lines changed: 139 additions & 90 deletions

File tree

src/features/bookmarks/Bookmarks.tsx

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,44 +21,63 @@ export function Bookmarks() {
2121
const [loading, setLoading] = useState(false);
2222
const [error, setError] = useState<string | null>(null);
2323

24-
// Page title management
2524
useEffect(() => {
26-
if (loading) {
25+
if (error) {
26+
document.title = "Error · Bookmarks | Hacker Tracker";
27+
} else if (loading) {
2728
document.title = "Loading bookmarks… | Hacker Tracker";
2829
} else if (conference) {
2930
document.title = `Bookmarks · ${conference.name} | Hacker Tracker`;
3031
} else {
3132
document.title = "Bookmarks | Hacker Tracker";
3233
}
33-
}, [loading, conference]);
34+
}, [loading, conference, error]);
3435

3536
useEffect(() => {
36-
if (!confCode) return;
37-
3837
let cancelled = false;
3938

40-
(async () => {
39+
async function run() {
40+
if (!confCode) {
41+
setError("Missing required URL parameters.");
42+
return;
43+
}
4144
setLoading(true);
4245
setError(null);
46+
setGrouped(null);
47+
setConference(null);
48+
4349
try {
44-
const [conf, evs, tags] = await Promise.all([
45-
getConferenceByCode(confCode),
50+
const conf = await getConferenceByCode(confCode);
51+
if (cancelled) return;
52+
if (!conf) {
53+
setError(`Conference not found.`);
54+
return;
55+
}
56+
setConference(conf);
57+
58+
const bookmarks = loadConfBookmarks(confCode); // Set<number>
59+
if (bookmarks.size === 0) {
60+
setGrouped({});
61+
return;
62+
}
63+
64+
const [evs, tags] = await Promise.all([
4665
getEvents(confCode),
4766
getTags(confCode),
4867
]);
4968
if (cancelled) return;
5069

51-
const tz = conf?.timezone || "UTC";
52-
const bookmarks = loadConfBookmarks(confCode);
53-
const bookmarkedEvents = evs.filter((e) => bookmarks.has(e.id));
70+
const tz = conf.timezone || "UTC";
71+
const bookmarkedEvents = (evs as HTEvent[]).filter((e) =>
72+
bookmarks.has(e.id)
73+
);
74+
const groupedSchedule = buildScheduleBucketsByDay(
75+
bookmarkedEvents,
76+
tags as HTTagGroup[],
77+
tz
78+
);
5479

5580
startTransition(() => {
56-
setConference(conf);
57-
const groupedSchedule = buildScheduleBucketsByDay(
58-
bookmarkedEvents as HTEvent[],
59-
tags as HTTagGroup[],
60-
tz
61-
);
6281
setGrouped(groupedSchedule);
6382
});
6483
} catch (e) {
@@ -67,18 +86,17 @@ export function Bookmarks() {
6786
} finally {
6887
if (!cancelled) setLoading(false);
6988
}
70-
})();
89+
}
7190

91+
run();
7292
return () => {
7393
cancelled = true;
7494
};
7595
}, [confCode]);
7696

7797
if (loading) return <LoadingPage message="Loading bookmarks..." />;
7898

79-
if (!conference && error) {
80-
return <ErrorPage msg="Conference not found." />;
81-
}
99+
if (error) return <ErrorPage msg={error} />;
82100

83101
return (
84102
<div className="min-h-dvh flex flex-col">
@@ -106,7 +124,6 @@ export function Bookmarks() {
106124
</div>
107125
)}
108126
</main>
109-
110127
<HTFooter />
111128
</div>
112129
);

src/features/event/Event.tsx

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,20 @@ export function Event() {
3030
const [loading, setLoading] = useState(false);
3131
const [error, setError] = useState<string | null>(null);
3232

33-
// Early validation of required params
33+
// Title reflects state
34+
useEffect(() => {
35+
if (error && !conference) {
36+
document.title = "Error · Event | Hacker Tracker";
37+
} else if (loading) {
38+
document.title = "Loading event… | Hacker Tracker";
39+
} else if (conference && event) {
40+
document.title = `${event.title} · ${conference.name} | Hacker Tracker`;
41+
} else {
42+
document.title = "Event | Hacker Tracker";
43+
}
44+
}, [loading, conference, event, error]);
45+
46+
// Validate required params early
3447
useEffect(() => {
3548
if (!confCode || !eventId) {
3649
setError("Missing required URL parameters.");
@@ -44,49 +57,49 @@ export function Event() {
4457

4558
async function run() {
4659
if (!confCode || !eventId) return;
60+
4761
setLoading(true);
4862
setError(null);
63+
setEvent(null);
64+
setPeople([]);
65+
setConference(null);
4966

5067
try {
51-
const [conf, evt, tags] = await Promise.all([
52-
getConferenceByCode(confCode),
53-
getEventById(confCode, Number(eventId)),
54-
getTags(confCode),
55-
]);
68+
const conf = await getConferenceByCode(confCode);
5669
if (cancelled) return;
57-
5870
if (!conf) {
59-
setConference(null);
60-
setEvent(null);
6171
setError("Conference not found");
6272
return;
6373
}
74+
setConference(conf);
75+
76+
const evt = await getEventById(confCode, Number(eventId));
77+
if (cancelled) return;
6478
if (!evt) {
65-
setConference(conf);
66-
setEvent(null);
6779
setError("Event not found");
6880
return;
6981
}
7082

71-
// Safe extraction of speaker ids
72-
const speakerIds = (evt as HTEvent).speakers?.map((s) => s.id) ?? [];
73-
const speakers = speakerIds.length
74-
? await getSpeakersByIds(confCode, speakerIds)
75-
: [];
83+
const [tags, speakers] = await Promise.all([
84+
getTags(confCode),
85+
(evt as HTEvent).speakers?.length
86+
? getSpeakersByIds(
87+
confCode,
88+
(evt as HTEvent).speakers!.map((s) => s.id)
89+
)
90+
: Promise.resolve([] as HTPerson[]),
91+
]);
92+
if (cancelled) return;
7693

7794
const tagMap = new Map<number, HTTag>();
7895
(tags as HTTagGroup[]).forEach((group) => {
79-
group.tags?.forEach((tag) => tagMap.set(tag.id, tag));
96+
group.tags?.forEach((t) => tagMap.set(t.id, t));
8097
});
8198

8299
const processed = toProcessedEvent(evt as HTEvent, tagMap);
83100

84101
setPeople(speakers);
85-
setConference(conf);
86102
setEvent(processed);
87-
88-
// Set title without creating a fetch re-run dependency loop
89-
document.title = `${processed.title} - ${conf.name}`;
90103
} catch (e) {
91104
if (!cancelled) {
92105
const msg = e instanceof Error ? e.message : "Failed to load event";
@@ -103,7 +116,6 @@ export function Event() {
103116
};
104117
}, [confCode, eventId]);
105118

106-
// Loading
107119
if (loading) {
108120
return (
109121
<div className="min-h-screen flex flex-col bg-gray-950">
@@ -115,7 +127,6 @@ export function Event() {
115127
);
116128
}
117129

118-
// Fatal errors (no conference context)
119130
if (error && !conference) {
120131
return (
121132
<div className="min-h-screen flex flex-col bg-gray-950">
@@ -127,7 +138,6 @@ export function Event() {
127138
);
128139
}
129140

130-
// Success (or event-specific error with conference present)
131141
return (
132142
<div className="min-h-screen flex flex-col bg-gray-950">
133143
<main className="flex-1">

src/features/person/Person.tsx

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,68 +17,79 @@ export function Person() {
1717
const [loading, setLoading] = useState(false);
1818
const [error, setError] = useState<string | null>(null);
1919

20-
// Page title management
2120
useEffect(() => {
22-
if (loading) {
23-
document.title = "Loading person | Hacker Tracker";
21+
if (error) {
22+
document.title = "Error · Person | Hacker Tracker";
23+
} else if (loading) {
24+
document.title = "Loading person… | Hacker Tracker";
2425
} else if (conference && person) {
2526
document.title = `${person.name} · ${conference.name} | Hacker Tracker`;
2627
} else {
2728
document.title = "Person | Hacker Tracker";
2829
}
29-
}, [loading, conference, person]);
30+
}, [loading, conference, person, error]);
3031

3132
useEffect(() => {
32-
if (!confCode) return;
33-
3433
let cancelled = false;
35-
36-
(async () => {
34+
async function run() {
35+
if (!confCode || !personId) {
36+
setError("Missing required URL parameters.");
37+
return;
38+
}
3739
setLoading(true);
3840
setError(null);
41+
setPerson(null);
42+
setConference(null);
43+
setEvents([]);
44+
3945
try {
40-
const [conf, p] = await Promise.all([
41-
getConferenceByCode(confCode),
42-
getSpeakerById(confCode, Number(personId)),
43-
]);
46+
const conf = await getConferenceByCode(confCode);
4447
if (cancelled) return;
48+
if (!conf) {
49+
setError(`Conference not found.`);
50+
return;
51+
}
52+
setConference(conf);
4553

46-
let personEvents: HTEvent[] = [];
54+
const p = await getSpeakerById(confCode, Number(personId));
55+
if (cancelled) return;
56+
if (!p) {
57+
setError("Person not found.");
58+
return;
59+
}
4760

48-
if (p?.event_ids?.length) {
49-
personEvents = await getEventsByIds(confCode, p?.event_ids);
61+
let personEvents: HTEvent[] = [];
62+
if (p.event_ids?.length) {
63+
personEvents = await getEventsByIds(confCode, p.event_ids);
64+
if (cancelled) return;
5065
}
5166

5267
startTransition(() => {
53-
setConference(conf);
5468
setPerson(p);
5569
setEvents(personEvents);
5670
});
5771
} catch (e) {
58-
const msg = e instanceof Error ? e.message : "Failed to load people";
72+
const msg = e instanceof Error ? e.message : "Failed to load person";
5973
if (!cancelled) setError(msg);
6074
} finally {
6175
if (!cancelled) setLoading(false);
6276
}
63-
})();
64-
77+
}
78+
run();
6579
return () => {
6680
cancelled = true;
6781
};
6882
}, [confCode, personId]);
6983

70-
if (loading) return <LoadingPage message="Loading people..." />;
71-
72-
if (!conference && error) {
73-
return <ErrorPage msg="People not found." />;
74-
}
84+
if (loading) return <LoadingPage message="Loading person..." />;
85+
if (error) return <ErrorPage msg={error} />;
7586

7687
return (
7788
<div className="min-h-dvh flex flex-col">
7889
{conference && <ConferenceHeader conference={conference} />}
7990
<main className="flex-1">
8091
{person && conference ? (
81-
<Suspense fallback={<LoadingPage message="Loading people..." />}>
92+
<Suspense fallback={<LoadingPage message="Loading person..." />}>
8293
<PersonDetails
8394
conference={conference}
8495
person={person}

0 commit comments

Comments
 (0)