feat: update frontend to work with v2 backend — camelCase, new endpoints, nested author
Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 9m38s
test / unit (pull_request) Successful in 16m2s
test / integration (pull_request) Failing after 17m2s

This commit is contained in:
2026-05-14 17:14:27 +02:00
parent 7110f30e16
commit 44385adb6b
17 changed files with 203 additions and 286 deletions

View File

@@ -33,7 +33,7 @@ export default function LoginPage() {
const form = useForm<z.infer<typeof LoginSchema>>({
resolver: zodResolver(LoginSchema),
defaultValues: { username: "", password: "" },
defaultValues: { email: "", password: "" },
});
async function onSubmit(values: z.infer<typeof LoginSchema>) {
@@ -43,7 +43,7 @@ export default function LoginPage() {
setToken(token);
router.push("/"); // Redirect to homepage on successful login
} catch {
setError("Invalid username or password.");
setError("Invalid email or password.");
}
}
@@ -61,12 +61,12 @@ export default function LoginPage() {
{/* ... Form fields for username and password ... */}
<FormField
control={form.control}
name="username"
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="frutiger" {...field} />
<Input type="email" placeholder="you@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>

View File

@@ -3,6 +3,7 @@ import {
getFeed,
getFriends,
getMe,
getTopFriends,
getUserProfile,
Me,
User,
@@ -60,7 +61,7 @@ async function FeedPage({
const { items: allThoughts, totalPages } = feedData!;
const thoughtThreads = buildThoughtThreads(allThoughts);
const authors = [...new Set(allThoughts.map((t) => t.authorUsername))];
const authors = [...new Set(allThoughts.map((t) => t.author.username))];
const userProfiles = await Promise.all(
authors.map((username) => getUserProfile(username, token).catch(() => null))
);
@@ -72,10 +73,10 @@ async function FeedPage({
);
const friends = (await getFriends(token)).users.map((user) => user.username);
const shouldDisplayTopFriends =
token && me?.topFriends && me.topFriends.length > 8;
console.log("Should display top friends:", shouldDisplayTopFriends);
const topFriendsData = me
? await getTopFriends(me.username, token).catch(() => ({ topFriends: [] }))
: { topFriends: [] };
const shouldDisplayTopFriends = topFriendsData.topFriends.length > 0;
return (
<div className="container mx-auto max-w-6xl p-4 sm:p-6">
@@ -96,7 +97,7 @@ async function FeedPage({
<div className="block lg:hidden space-y-6">
<PopularTags />
{shouldDisplayTopFriends && (
<TopFriends mode="top-friends" usernames={me.topFriends} />
<TopFriends mode="top-friends" usernames={topFriendsData.topFriends} />
)}
{!shouldDisplayTopFriends && token && friends.length > 0 && (
<TopFriends mode="friends" usernames={friends || []} />
@@ -141,7 +142,7 @@ async function FeedPage({
<div className="sticky top-20 space-y-6">
<PopularTags />
{shouldDisplayTopFriends && (
<TopFriends mode="top-friends" usernames={me.topFriends} />
<TopFriends mode="top-friends" usernames={topFriendsData.topFriends} />
)}
{!shouldDisplayTopFriends && token && friends.length > 0 && (
<TopFriends mode="friends" usernames={friends || []} />

View File

@@ -30,7 +30,7 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
const authorDetails = new Map<string, { avatarUrl?: string | null }>();
if (results) {
results.users.users.forEach((user: User) => {
results.users.forEach((user: User) => {
authorDetails.set(user.username, { avatarUrl: user.avatarUrl });
});
}
@@ -48,21 +48,21 @@ export default async function SearchPage({ searchParams }: SearchPageProps) {
<Tabs defaultValue="thoughts" className="w-full">
<TabsList>
<TabsTrigger value="thoughts">
Thoughts ({results.thoughts.thoughts.length})
Thoughts ({results.thoughts.length})
</TabsTrigger>
<TabsTrigger value="users">
Users ({results.users.users.length})
Users ({results.users.length})
</TabsTrigger>
</TabsList>
<TabsContent value="thoughts">
<ThoughtList
thoughts={results.thoughts.thoughts}
thoughts={results.thoughts}
authorDetails={authorDetails}
currentUser={me}
/>
</TabsContent>
<TabsContent value="users">
<UserListCard users={results.users.users} />
<UserListCard users={results.users} />
</TabsContent>
</Tabs>
) : (

View File

@@ -10,7 +10,7 @@ export default async function ApiKeysPage() {
}
const initialApiKeys = await getApiKeys(token).catch(() => ({
apiKeys: [],
keys: [],
}));
return (
@@ -21,7 +21,7 @@ export default async function ApiKeysPage() {
Manage API keys for third-party applications.
</p>
</div>
<ApiKeyList initialApiKeys={initialApiKeys.apiKeys} />
<ApiKeyList initialApiKeys={initialApiKeys.keys} />
</div>
);
}

View File

@@ -23,11 +23,11 @@ export default async function TagPage({ params }: TagPageProps) {
notFound();
}
const allThoughts = thoughtsResult.value.thoughts;
const allThoughts = thoughtsResult.value.items;
const thoughtThreads = buildThoughtThreads(allThoughts);
const me = meResult.status === "fulfilled" ? (meResult.value as Me) : null;
const authors = [...new Set(allThoughts.map((t) => t.authorUsername))];
const authors = [...new Set(allThoughts.map((t) => t.author.username))];
const userProfiles = await Promise.all(
authors.map((username) => getUserProfile(username, token).catch(() => null))
);

View File

@@ -15,7 +15,7 @@ interface ThoughtPageProps {
}
function collectAuthors(thread: ThoughtThreadType): string[] {
const authors = new Set<string>([thread.authorUsername]);
const authors = new Set<string>([thread.author.username]);
for (const reply of thread.replies) {
collectAuthors(reply).forEach((author) => authors.add(author));
}

View File

@@ -26,7 +26,7 @@ export default async function FollowersPage({ params }: FollowersPageProps) {
<p className="text-muted-foreground">Users following @{username}.</p>
</header>
<main>
<UserListCard users={followersData.users} />
<UserListCard users={followersData.items} />
</main>
</div>
);

View File

@@ -26,7 +26,7 @@ export default async function FollowingPage({ params }: FollowingPageProps) {
<p className="text-muted-foreground">Users that @{username} follows.</p>
</header>
<main>
<UserListCard users={followingData.users} />
<UserListCard users={followingData.items} />
</main>
</div>
);

View File

@@ -3,6 +3,7 @@ import {
getFollowingList,
getFriends,
getMe,
getTopFriends,
getUserProfile,
getUserThoughts,
Me,
@@ -55,33 +56,31 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
const me = meResult.status === "fulfilled" ? (meResult.value as Me) : null;
const thoughts =
thoughtsResult.status === "fulfilled" ? thoughtsResult.value.thoughts : [];
thoughtsResult.status === "fulfilled" ? thoughtsResult.value.items : [];
const thoughtThreads = buildThoughtThreads(thoughts);
const followersCount =
followersResult.status === "fulfilled"
? followersResult.value.users.length
? followersResult.value.total
: 0;
const followingCount =
followingResult.status === "fulfilled"
? followingResult.value.users.length
? followingResult.value.total
: 0;
const isOwnProfile = me?.username === user.username;
const isFollowing =
me?.following?.some(
(followedUser) => followedUser.username === user.username
) || false;
const isFollowing = user.isFollowedByViewer;
const authorDetails = new Map<string, { avatarUrl?: string | null }>();
authorDetails.set(user.username, { avatarUrl: user.avatarUrl });
const friends =
typeof token === "string"
? (await getFriends(token)).users.map((user) => user.username)
? (await getFriends(token)).users.map((u) => u.username)
: [];
const shouldDisplayTopFriends = token && friends.length > 8;
const topFriendsData = await getTopFriends(username, token).catch(() => ({ topFriends: [] }));
const shouldDisplayTopFriends = topFriendsData.topFriends.length > 0;
return (
<div id={`profile-page-${user.username}`}>
@@ -195,7 +194,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
</Card>
{shouldDisplayTopFriends && (
<TopFriends mode="top-friends" usernames={user.topFriends} />
<TopFriends mode="top-friends" usernames={topFriendsData.topFriends} />
)}
{token && <TopFriends mode="friends" usernames={friends || []} />}
</div>