Skip to content

Commit bf29bd7

Browse files
authored
Merge pull request #195 from techulus/feat/posts
feat: Posts
2 parents 6a22e9c + 4d44fb8 commit bf29bd7

22 files changed

Lines changed: 2324 additions & 71 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"use client";
2+
3+
import { Title } from "@radix-ui/react-dialog";
4+
import { useQuery } from "@tanstack/react-query";
5+
import Link from "next/link";
6+
import { useParams } from "next/navigation";
7+
import { parseAsBoolean, useQueryState } from "nuqs";
8+
import { useState } from "react";
9+
import EmptyState from "@/components/core/empty-state";
10+
import { Panel } from "@/components/core/panel";
11+
import PageSection from "@/components/core/section";
12+
import PostForm from "@/components/form/post";
13+
import PageTitle from "@/components/layout/page-title";
14+
import PostsList from "@/components/project/posts/posts-list";
15+
import { buttonVariants } from "@/components/ui/button";
16+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
17+
import { useTRPC } from "@/trpc/client";
18+
19+
export default function Posts() {
20+
const { projectId, tenant } = useParams();
21+
const [create, setCreate] = useQueryState(
22+
"create",
23+
parseAsBoolean.withDefault(false),
24+
);
25+
const [activeTab, setActiveTab] = useState("published");
26+
27+
const trpc = useTRPC();
28+
29+
const { data: project } = useQuery(
30+
trpc.projects.getProjectById.queryOptions({
31+
id: +projectId!,
32+
}),
33+
);
34+
35+
const { data: publishedPosts = [] } = useQuery({
36+
...trpc.posts.list.queryOptions({
37+
projectId: +projectId!,
38+
}),
39+
enabled: activeTab === "published",
40+
});
41+
42+
const { data: myDrafts = [] } = useQuery({
43+
...trpc.posts.myDrafts.queryOptions({
44+
projectId: +projectId!,
45+
}),
46+
enabled: activeTab === "drafts",
47+
});
48+
49+
return (
50+
<>
51+
<PageTitle
52+
title="Posts"
53+
actions={
54+
project?.canEdit ? (
55+
<Link
56+
href={`/${tenant}/projects/${projectId}/posts?create=true`}
57+
className={buttonVariants()}
58+
>
59+
New
60+
</Link>
61+
) : undefined
62+
}
63+
/>
64+
65+
<PageSection transparent>
66+
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
67+
<TabsList className="mb-4">
68+
<TabsTrigger value="published">Published</TabsTrigger>
69+
<TabsTrigger value="drafts">My Drafts</TabsTrigger>
70+
</TabsList>
71+
72+
<TabsContent value="published">
73+
{publishedPosts.length ? (
74+
<PostsList posts={publishedPosts} projectId={+projectId!} />
75+
) : (
76+
<EmptyState
77+
show={!publishedPosts.length}
78+
label="post"
79+
createLink={`/${tenant}/projects/${projectId}/posts?create=true`}
80+
/>
81+
)}
82+
</TabsContent>
83+
84+
<TabsContent value="drafts">
85+
{myDrafts.length ? (
86+
<PostsList posts={myDrafts} projectId={+projectId!} />
87+
) : (
88+
<div className="text-center text-muted-foreground py-8">
89+
No draft posts
90+
</div>
91+
)}
92+
</TabsContent>
93+
</Tabs>
94+
</PageSection>
95+
96+
{project?.canEdit && (
97+
<Panel open={create} setOpen={setCreate}>
98+
<Title>
99+
<PageTitle title="New Post" compact />
100+
</Title>
101+
<PostForm />
102+
</Panel>
103+
)}
104+
</>
105+
);
106+
}

bun.lock

Lines changed: 65 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/core/search-panel.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
FileText,
1111
Filter,
1212
FolderOpen,
13+
MessagesSquare,
1314
RefreshCw,
1415
Search,
1516
X,
@@ -36,7 +37,7 @@ interface SearchResult {
3637
id: string;
3738
title: string;
3839
description?: string;
39-
type: "project" | "task" | "tasklist" | "event";
40+
type: "project" | "task" | "tasklist" | "event" | "post";
4041
status?: string;
4142
projectName?: string;
4243
url: string;
@@ -56,6 +57,8 @@ const getTypeIcon = (type: string) => {
5657
return <FileText className="h-4 w-4" />;
5758
case "event":
5859
return <Calendar className="h-4 w-4" />;
60+
case "post":
61+
return <MessagesSquare className="h-4 w-4" />;
5962
default:
6063
return <Search className="h-4 w-4" />;
6164
}
@@ -71,6 +74,8 @@ const getTypeLabel = (type: string) => {
7174
return "Task List";
7275
case "event":
7376
return "Event";
77+
case "post":
78+
return "Post";
7479
default:
7580
return type;
7681
}
@@ -105,7 +110,7 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
105110

106111
const [query, setQuery] = useState("");
107112
const [typeFilter, setTypeFilter] = useState<
108-
"project" | "task" | "tasklist" | "event" | undefined
113+
"project" | "task" | "tasklist" | "event" | "post" | undefined
109114
>();
110115
const [projectFilter, setProjectFilter] = useState<number | undefined>();
111116
const [statusFilter, setStatusFilter] = useState<string | undefined>();
@@ -146,7 +151,7 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
146151
try {
147152
const result = await indexAllMutation.mutateAsync();
148153
toast.success("Content reindexed successfully!", {
149-
description: `Indexed ${result.indexed.projects} projects, ${result.indexed.taskLists} task lists, ${result.indexed.tasks} tasks, and ${result.indexed.events} events.`,
154+
description: `Indexed ${result.indexed.projects} projects, ${result.indexed.taskLists} task lists, ${result.indexed.tasks} tasks, ${result.indexed.events} events, and ${result.indexed.posts} posts.`,
150155
});
151156
} catch (err) {
152157
console.error("Error reindexing content:", err);
@@ -185,7 +190,7 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
185190

186191
const groupedResults = groupBy<
187192
SearchResult,
188-
"project" | "task" | "tasklist" | "event"
193+
"project" | "task" | "tasklist" | "event" | "post"
189194
>(searchResults, (item) => item.type);
190195

191196
// Reset search when sheet closes
@@ -203,7 +208,7 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
203208
<div>
204209
<h2 className="text-lg font-semibold">Search</h2>
205210
<p className="text-sm text-muted-foreground">
206-
Search across all your projects, tasks, events, and more
211+
Search across all your projects, tasks, events, posts, and more
207212
</p>
208213
</div>
209214
<Button
@@ -222,7 +227,7 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
222227
<div className="relative">
223228
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
224229
<Input
225-
placeholder="Search projects, tasks, events..."
230+
placeholder="Search projects, tasks, events, posts..."
226231
value={query}
227232
onChange={(e) => setQuery(e.target.value)}
228233
className="pl-10 pr-4 h-10"
@@ -312,6 +317,10 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
312317
<Calendar className="mr-2 h-4 w-4" />
313318
Events
314319
</DropdownMenuItem>
320+
<DropdownMenuItem onClick={() => setTypeFilter("post")}>
321+
<MessagesSquare className="mr-2 h-4 w-4" />
322+
Posts
323+
</DropdownMenuItem>
315324
</DropdownMenuContent>
316325
</DropdownMenu>
317326

@@ -402,7 +411,7 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
402411
</h3>
403412
<p className="text-muted-foreground max-w-md">
404413
Type in the search box above to find projects, tasks,
405-
events, and more across your workspace.
414+
events, posts, and more across your workspace.
406415
</p>
407416
</div>
408417
</div>
@@ -444,7 +453,7 @@ export function SearchSheet({ open, onOpenChange }: SearchSheetProps) {
444453
{searchResults.length !== 1 ? "s" : ""} for "{debouncedQuery}"
445454
</div>
446455

447-
{["project", "tasklist", "task", "event"].map((type) => {
456+
{["project", "tasklist", "task", "event", "post"].map((type) => {
448457
const results =
449458
groupedResults[type as keyof typeof groupedResults];
450459
if (!results || results.length === 0) return null;

0 commit comments

Comments
 (0)