diff --git a/thoughts-frontend/app/search/page.tsx b/thoughts-frontend/app/search/page.tsx
new file mode 100644
index 0000000..99d9296
--- /dev/null
+++ b/thoughts-frontend/app/search/page.tsx
@@ -0,0 +1,76 @@
+import { cookies } from "next/headers";
+import { getMe, search, User } from "@/lib/api";
+import { UserListCard } from "@/components/user-list-card";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { ThoughtList } from "@/components/thought-list";
+
+interface SearchPageProps {
+ searchParams: { q?: string };
+}
+
+export default async function SearchPage({ searchParams }: SearchPageProps) {
+ const query = searchParams.q || "";
+ const token = (await cookies()).get("auth_token")?.value ?? null;
+
+ if (!query) {
+ return (
+
+
Search Thoughts
+
+ Find users and thoughts across the platform.
+
+
+ );
+ }
+
+ const [results, me] = await Promise.all([
+ search(query, token).catch(() => null),
+ token ? getMe(token).catch(() => null) : null,
+ ]);
+
+ const authorDetails = new Map();
+ if (results) {
+ results.users.users.forEach((user: User) => {
+ authorDetails.set(user.username, { avatarUrl: user.avatarUrl });
+ });
+ }
+
+ return (
+
+
+
+ {results ? (
+
+
+
+ Thoughts ({results.thoughts.thoughts.length})
+
+
+ Users ({results.users.users.length})
+
+
+
+
+
+
+
+
+
+ ) : (
+
+ No results found or an error occurred.
+
+ )}
+
+
+ );
+}
diff --git a/thoughts-frontend/components/main-nav.tsx b/thoughts-frontend/components/main-nav.tsx
index a29058f..22bbbf7 100644
--- a/thoughts-frontend/components/main-nav.tsx
+++ b/thoughts-frontend/components/main-nav.tsx
@@ -3,6 +3,7 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
+import { SearchInput } from "./search-input";
export function MainNav() {
const pathname = usePathname();
@@ -17,6 +18,7 @@ export function MainNav() {
>
Feed
+
);
}
diff --git a/thoughts-frontend/components/post-thought-form.tsx b/thoughts-frontend/components/post-thought-form.tsx
index 97ce98c..b103faf 100644
--- a/thoughts-frontend/components/post-thought-form.tsx
+++ b/thoughts-frontend/components/post-thought-form.tsx
@@ -46,7 +46,7 @@ export function PostThoughtForm() {
toast.success("Your thought has been posted!");
form.reset();
router.refresh(); // This is the key to updating the feed
- } catch (err) {
+ } catch {
toast.error("Failed to post thought. Please try again.");
}
}
diff --git a/thoughts-frontend/components/search-input.tsx b/thoughts-frontend/components/search-input.tsx
new file mode 100644
index 0000000..c3bdda7
--- /dev/null
+++ b/thoughts-frontend/components/search-input.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { Input } from "./ui/input";
+import { Search as SearchIcon } from "lucide-react";
+
+export function SearchInput() {
+ const router = useRouter();
+
+ const handleSearch = (e: React.FormEvent) => {
+ e.preventDefault();
+ const formData = new FormData(e.currentTarget);
+ const query = formData.get("q") as string;
+ if (query) {
+ router.push(`/search?q=${encodeURIComponent(query)}`);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/thoughts-frontend/components/thought-list.tsx b/thoughts-frontend/components/thought-list.tsx
new file mode 100644
index 0000000..d927ce1
--- /dev/null
+++ b/thoughts-frontend/components/thought-list.tsx
@@ -0,0 +1,47 @@
+import { Me, Thought } from "@/lib/api";
+import { ThoughtCard } from "./thought-card";
+import { Card, CardContent } from "./ui/card";
+
+interface ThoughtListProps {
+ thoughts: Thought[];
+ authorDetails: Map;
+ currentUser: Me | null;
+}
+
+export function ThoughtList({
+ thoughts,
+ authorDetails,
+ currentUser,
+}: ThoughtListProps) {
+ if (thoughts.length === 0) {
+ return (
+
+ No thoughts to display.
+
+ );
+ }
+
+ return (
+
+
+
+ {thoughts.map((thought) => {
+ const author = {
+ username: thought.authorUsername,
+ avatarUrl: null,
+ ...authorDetails.get(thought.authorUsername),
+ };
+ return (
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/thoughts-frontend/components/ui/input.tsx b/thoughts-frontend/components/ui/input.tsx
index 60c258e..705ba64 100644
--- a/thoughts-frontend/components/ui/input.tsx
+++ b/thoughts-frontend/components/ui/input.tsx
@@ -10,7 +10,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
- "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-fa-inner transition-shadow",
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive shadow-fa-inner transition-shadow glass-effect glossy-effect bottom",
className
)}
{...props}
diff --git a/thoughts-frontend/lib/api.ts b/thoughts-frontend/lib/api.ts
index 94d0b7c..f981347 100644
--- a/thoughts-frontend/lib/api.ts
+++ b/thoughts-frontend/lib/api.ts
@@ -60,6 +60,11 @@ export const UpdateProfileSchema = z.object({
topFriends: z.array(z.string()).max(8).optional(),
});
+export const SearchResultsSchema = z.object({
+ users: z.object({ users: z.array(UserSchema) }),
+ thoughts: z.object({ thoughts: z.array(ThoughtSchema) }),
+});
+
export type User = z.infer;
export type Me = z.infer;
export type Thought = z.infer;
@@ -232,4 +237,14 @@ export const getFollowersList = (username: string, token: string | null) =>
{},
z.object({ users: z.array(UserSchema) }),
token
+ );
+
+
+
+export const search = (query: string, token: string | null) =>
+ apiFetch(
+ `/search?q=${encodeURIComponent(query)}`,
+ {},
+ SearchResultsSchema,
+ token
);
\ No newline at end of file