Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add load older/newer note button (add pagination to timeline) #96

Merged
merged 7 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions app/components/loadMoreNote.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.loadMoreNote {
text-align: center;

div {
border: solid #000;
border-width: 0.5px 0;
}

div:hover {
background-color: #f4f4f6;
}
}
31 changes: 31 additions & 0 deletions app/components/loadMoreNote.tsx
laminne marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { TimelineButton } from "~/components/timelineButton";
import style from "./loadMoreNote.module.css";

export interface LoadMoreNoteButtonProps {
type: "newer" | "older";
noteID: string;
}

export const LoadMoreNoteButton = (props: LoadMoreNoteButtonProps) => {
if (props.type === "newer") {
return (
<div className={style.loadMoreNote}>
<TimelineButton
link={`?after_id=${props.noteID}#`}
linkText="Load newer notes"
/>
</div>
);
} else if (props.type === "older") {
return (
<div className={style.loadMoreNote}>
<TimelineButton
link={`?before_id=${props.noteID}#`}
linkText="Load older notes"
/>
</div>
);
} else {
throw new Error("Invalid props");
}
};
16 changes: 16 additions & 0 deletions app/components/timelineButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Link } from "@remix-run/react";

export interface TimelineButtonProps {
link: string;
linkText: string;
}

export const TimelineButton = ({ link, linkText }: TimelineButtonProps) => {
return (
<Link to={link}>
<div>
<p>{linkText}</p>
</div>
</Link>
);
};
10 changes: 8 additions & 2 deletions app/lib/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ export const account = async (

export const accountTimeline = async (
id: string,
token: string | undefined
token: string | undefined,
beforeID?: string
): Promise<TimelineResponse[] | { error: string }> => {
try {
const res = await fetch(`http://localhost:3000/timeline/accounts/${id}`, {
const url = new URL(`http://localhost:3000/timeline/accounts/${id}`);
if (beforeID) {
url.searchParams.append("before_id", beforeID);
}

const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
},
Expand Down
10 changes: 8 additions & 2 deletions app/lib/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ export interface TimelineResponse {
}

export const fetchHomeTimeline = async (
token: string
token: string,
beforeID?: string
): Promise<{ notes: TimelineResponse[] } | { error: string }> => {
try {
const timelineRes = await fetch("http://localhost:3000/timeline/home", {
const url = new URL("http://localhost:3000/timeline/home");
if (beforeID) {
url.searchParams.append("before_id", beforeID);
}

const timelineRes = await fetch(url, {
headers: {
authorization: `Bearer ${token}`,
},
Expand Down
22 changes: 21 additions & 1 deletion app/routes/accounts.$id.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LoaderFunctionArgs } from "@remix-run/cloudflare";
import { MetaFunction, useLoaderData } from "@remix-run/react";
import { LoadMoreNoteButton } from "~/components/loadMoreNote";
import { Note, NoteProps } from "~/components/note";
import { account, AccountResponse, accountTimeline } from "~/lib/account";
import { accountCookie } from "~/lib/login";
Expand All @@ -23,7 +24,14 @@ export const loader = async ({
return { error: accountRes.error };
}

const timelineRes = await accountTimeline(accountRes.id, token);
const query = new URL(request.url).searchParams;
const beforeID = query.get("before_id") ?? undefined;
const afterID = query.get("after_id") ?? undefined;
if (beforeID && afterID) {
throw new Error("before_id and after_id cannot be used together");
}

const timelineRes = await accountTimeline(accountRes.id, token, beforeID);
if ("error" in timelineRes) {
return { error: timelineRes.error };
}
Expand Down Expand Up @@ -104,9 +112,21 @@ interface AccountTimelineProps {
const AccountTimeline = ({ notes }: AccountTimelineProps) => {
return (
<div>
<div>
<LoadMoreNoteButton type="newer" noteID={notes[0].id} />
</div>

{notes.map((note) => {
return <Note key={note.id} {...note} />;
})}

<div>
{notes.length < 20 ? (
<></>
) : (
<LoadMoreNoteButton type="older" noteID={notes.at(-1)!.id} />
)}
</div>
</div>
);
};
33 changes: 23 additions & 10 deletions app/routes/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
TypedResponse,
} from "@remix-run/cloudflare";
import { MetaFunction, useLoaderData } from "@remix-run/react";
import { LoadMoreNoteButton } from "~/components/loadMoreNote";
import { Note } from "~/components/note";
import { PostForm } from "~/components/postForm";
import { accountCookie } from "~/lib/login";
Expand Down Expand Up @@ -34,7 +35,9 @@ export const loader = async ({
return { error: parsedToken.message };
}

const res = await fetchHomeTimeline(cookie);
const query = new URL(request.url).searchParams;
const beforeID = query.get("before_id") ?? undefined;
const res = await fetchHomeTimeline(cookie, beforeID);
if ("error" in res) {
return res;
}
Expand All @@ -57,6 +60,9 @@ export default function Timeline() {
return (
<div className={styles.noteContainer}>
<PostForm />

<LoadMoreNoteButton type="newer" noteID={loaderData.notes[0].id} />

{loaderData ? (
loaderData.notes.map((note) => {
const author = {
Expand All @@ -69,20 +75,27 @@ export default function Timeline() {
reactedBy: reaction.reacted_by,
}));
return (
<Note
key={note.id}
id={note.id}
author={author}
content={note.content}
contentsWarningComment={note.contents_warning_comment}
reactions={reactions}
loggedInAccountID={loaderData.loggedInAccount.id}
/>
<div key={note.id}>
<Note
id={note.id}
author={author}
content={note.content}
contentsWarningComment={note.contents_warning_comment}
reactions={reactions}
loggedInAccountID={loaderData.loggedInAccount.id}
/>
</div>
);
})
) : (
<div></div>
)}

{loaderData.notes.length < 20 ? (
<></>
) : (
<LoadMoreNoteButton type="older" noteID={loaderData.notes.at(-1)!.id} />
)}
</div>
);
}
Loading