Skip to content
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
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ node_modules
.turbo
.next
.docusaurus
packages/shared-db/migrations
packages/shared-db/migrations
apps/app/src/routeTree.gen.ts
39 changes: 39 additions & 0 deletions apps/api/src/routes/api/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { FeedService } from "@curatedotfun/core-services";
import {
ApiErrorResponseSchema,
CreateUserRequestSchema,
FeedsWrappedResponseSchema,
UpdateUserRequestSchema,
UserDeletedWrappedResponseSchema,
UserNearAccountIdParamSchema,
Expand Down Expand Up @@ -333,4 +335,41 @@ usersRoutes.get(
},
);

usersRoutes.get(
"/:nearAccountId/feeds",
zValidator("param", UserNearAccountIdParamSchema),
async (c) => {
const { nearAccountId } = c.req.valid("param");
const sp = c.var.sp;

try {
const feedService: FeedService = sp.getFeedService();
const feeds = await feedService.getFeedsByCreator(nearAccountId);

return c.json(
FeedsWrappedResponseSchema.parse({
statusCode: 200,
success: true,
data: feeds.map((feed) => ({
...feed,
config: feed.config,
})),
}),
);
} catch (error) {
c.var.sp
.getLogger()
.error({ error }, `Error fetching feeds for ${nearAccountId}`);
return c.json(
ApiErrorResponseSchema.parse({
statusCode: 500,
success: false,
error: { message: "Failed to fetch feeds" },
}),
500,
);
}
},
);

export { usersRoutes };
2 changes: 1 addition & 1 deletion apps/app/src/components/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default function UserMenu({ className }: UserMenuProps) {
) : (
<ProfileImage size="small" />
)}
<p className="text-sm font-medium leading-6 hidden sm:block">
<p className="text-sm font-medium leading-6">
{getUserDisplayName()}
</p>
<ChevronDown
Expand Down
16 changes: 8 additions & 8 deletions apps/app/src/components/profile/ProfileTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import {
Activity,
ScanSearch,
// Award,
// Newspaper,
Newspaper,
// NotepadTextDashed
} from "lucide-react";
import { ProfileOverview } from "./overview";
// import { ProfileContent } from "./content";
// import { MyFeeds } from "./my-feeds";
import { MyFeeds } from "./my-feeds";
// import { ProfilePoints } from "./points";
import { ProfileActivity } from "./activity";

Expand All @@ -26,12 +26,12 @@ const TABS = [
// icon: NotepadTextDashed,
// component: ProfileContent,
// },
// {
// value: "my-feeds",
// label: "My Feeds",
// icon: Newspaper,
// component: MyFeeds,
// },
{
value: "my-feeds",
label: "My Feeds",
icon: Newspaper,
component: MyFeeds,
},
// {
// value: "points",
// label: "Points",
Expand Down
36 changes: 36 additions & 0 deletions apps/app/src/components/profile/my-feeds/FeedCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { FeedResponse } from "@curatedotfun/types";
import { useFeedStats } from "../../../lib/api/feeds";
import { Card } from "./card";

interface FeedCardProps {
feed: FeedResponse;
}

export function FeedCard({ feed }: FeedCardProps) {
const { contentCount, curatorCount } = useFeedStats(feed.id);

const isComplete = !!(
feed.config &&
feed.config.name &&
feed.config.description &&
feed.config.sources &&
feed.config.sources.length > 0
);

const tags: string[] = []; // TODO: Extract tags from feed sources or create a tags system
const imageSrc = feed.config?.image || "/images/feed-image.png";

return (
<Card
id={feed.id}
image={imageSrc}
title={feed.name}
tags={tags}
description={feed.description || "No description available"}
createdAt={new Date(feed.createdAt)}
curators={curatorCount}
contents={contentCount}
isCompleted={isComplete}
/>
);
}
24 changes: 24 additions & 0 deletions apps/app/src/components/profile/my-feeds/SearchForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Search } from "lucide-react";
import { Input } from "../../ui/input";

interface SearchFormProps {
searchTerm: string;
onSearchChange: (value: string) => void;
}

export function SearchForm({ searchTerm, onSearchChange }: SearchFormProps) {
return (
<form className="relative w-full md:w-fit">
<Input
placeholder="Search feeds..."
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
className="ps-9 sm:min-w-[300px] w-full"
/>
<Search
className="absolute left-2 top-1/2 -translate-y-1/2 text-black/50 size-5"
strokeWidth={1.5}
/>
</form>
);
}
100 changes: 54 additions & 46 deletions apps/app/src/components/profile/my-feeds/card.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Info, Newspaper, Users } from "lucide-react";
import { Badge } from "../../ui/badge";
import { cn } from "../../../lib/utils";
import { Link } from "@tanstack/react-router";

interface CardProps {
id: string;
image: string;
title: string;
tags: string[];
Expand All @@ -14,6 +16,7 @@ interface CardProps {
}

export function Card({
id,
image,
title,
tags,
Expand All @@ -24,58 +27,63 @@ export function Card({
isCompleted,
}: CardProps) {
return (
<div className="self-stretch flex flex-col">
{!isCompleted && (
<div className="flex items-center text-yellow-700 gap-4 px-4 py-2 rounded-t-lg border border-yellow-700 bg-yellow-50">
<Info strokeWidth={1.5} size={24} />
Setup incomplete
</div>
)}
<div
className={cn(
"flex p-4 gap-[32px] flex-col justify-between flex-1 self-stretch rounded-lg border border-neutral-200 bg-white",
!isCompleted && "border-yellow-700 rounded-t-none border-t-0",
<Link to={"/feed/$feedId"} params={{ feedId: id }} className="no-underline">
<div className="self-stretch flex flex-col">
{!isCompleted && (
<div className="flex items-center text-yellow-700 gap-4 px-4 py-2 rounded-t-lg border border-yellow-700 bg-yellow-50">
<Info strokeWidth={1.5} size={24} />
Setup incomplete
</div>
)}
>
<div className="space-y-3">
<div className="flex gap-[14px] p-2.5 rounded-md bg-neutral-50">
<img
src={image}
alt="Near Week"
className="object-cover size-[68px] shrink-0"
/>
<div>
<p className="font-londrina font-[900] text-black text-[24px]">
{title}
</p>
<div className="flex items-center gap-2 flex-wrap">
{tags.length > 0 &&
tags.map((tag, index) => (
<Badge key={index} className="text-xs">
#{tag}
</Badge>
))}
<div
className={cn(
"flex p-4 gap-[32px] flex-col justify-between flex-1 self-stretch rounded-lg border border-neutral-200 bg-white",
!isCompleted && "border-yellow-700 rounded-t-none border-t-0",
)}
>
<div className="space-y-3">
<div className="flex gap-[14px] p-2.5 rounded-md bg-neutral-50">
<img
src={image}
alt={title}
className="object-cover size-[68px] shrink-0"
onError={(e) => {
e.currentTarget.src = "/images/default-feed.png";
}}
/>
<div>
<p className="font-londrina font-[900] text-black text-[24px]">
{title}
</p>
<div className="flex items-center gap-2 flex-wrap">
{tags.length > 0 &&
tags.map((tag, index) => (
<Badge key={index} className="text-xs">
#{tag}
</Badge>
))}
</div>
</div>
</div>
<p>{description}</p>
<p className="mt-0.5">Created {createdAt.toLocaleDateString()}</p>
</div>
<p>{description}</p>
<p className="mt-0.5">Created {createdAt.toLocaleDateString()}</p>
</div>
<div className="flex gap-[14px] items-center justify-between p-2.5 rounded-md bg-neutral-50">
<div className="flex items-center gap-2">
<Users strokeWidth={1.5} size={24} />
<p className="text-black text-base">
<span className="font-bold">{curators}</span> Curators
</p>
</div>
<div className="flex items-center gap-2">
<Newspaper strokeWidth={1.5} size={24} />
<p className="text-black text-base">
<span className="font-bold">{contents}</span> Contents
</p>
<div className="flex gap-[14px] items-center justify-between p-2.5 rounded-md bg-neutral-50">
<div className="flex items-center gap-2">
<Users strokeWidth={1.5} size={24} />
<p className="text-black text-base">
<span className="font-bold">{curators}</span> Curators
</p>
</div>
<div className="flex items-center gap-2">
<Newspaper strokeWidth={1.5} size={24} />
<p className="text-black text-base">
<span className="font-bold">{contents}</span> Contents
</p>
</div>
</div>
</div>
</div>
</div>
</Link>
);
}
16 changes: 16 additions & 0 deletions apps/app/src/components/profile/my-feeds/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const filterOptions = [
{
value: "all",
label: "All",
},
{
value: "completed",
label: "Completed",
},
{
value: "incomplete",
label: "Setup incomplete",
},
] as const;

export type FilterValue = (typeof filterOptions)[number]["value"];
Loading