Files
EpicNext-Cms/src/app/(site)/search/page.tsx
T
openhands 7a2c0fd4d4
CI / check (push) Successful in 26s
CI / release (push) Skipped
CI / deploy (push) Successful in 58s
fix: resolve TypeScript and lint issues
- Fix proxy routes to use resolveImagerBase instead of removed resolveUpstreamBase
- Fix avatarImageUrl type signature to use AvatarOptions
- Run biome formatter
2026-08-09 19:42:02 +02:00

232 lines
5.9 KiB
TypeScript

import { and, eq, like } from "drizzle-orm";
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { db, Rooms, User } from "@/lib/db";
import { avatarImageUrl } from "@/lib/format";
export const metadata: Metadata = { title: "Search" };
type SearchParams = Promise<{ q?: string }>;
export default async function SearchPage({
searchParams,
}: {
searchParams: SearchParams;
}) {
const { q } = await searchParams;
const query = q?.trim();
if (!query) {
return (
<div
className="rounded-2xl border p-10 text-center"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<p
className="text-sm font-bold"
style={{ color: "var(--color-text-muted)" }}
>
Enter a search term to find users.
</p>
</div>
);
}
const users = await db
.select({
id: User.id,
username: User.username,
look: User.look,
motto: User.motto,
online: User.online,
rank: User.rank,
})
.from(User)
.where(like(User.username, `%${query}%`))
.limit(30)
.catch(() => []);
const rooms = await db
.select({
id: Rooms.id,
name: Rooms.name,
ownerName: Rooms.ownerName,
users: Rooms.users,
usersMax: Rooms.usersMax,
})
.from(Rooms)
.where(and(like(Rooms.name, `%${query}%`), eq(Rooms.state, "open")))
.limit(12)
.catch(() => []);
return (
<div className="flex flex-col gap-6">
<div
className="rounded-2xl border p-4"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<form action="/search" method="GET" className="flex gap-2">
<input
type="text"
name="q"
defaultValue={query}
placeholder="Search users or rooms..."
className="flex-1 rounded-xl border-2 px-4 py-2.5 text-sm font-medium"
style={{
backgroundColor: "var(--color-background)",
color: "var(--color-text-readable)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 25%, transparent)",
}}
/>
<button
type="submit"
className="rounded-xl px-6 py-2.5 font-extrabold text-sm transition-all duration-200 hover:scale-[1.02] active:scale-95"
style={{
background: "var(--color-primary)",
color: "var(--color-primary-foreground)",
}}
>
Search
</button>
</form>
</div>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
Results for &quot;{query}&quot; — {users.length} user
{users.length !== 1 ? "s" : ""}
{rooms.length > 0 &&
`, ${rooms.length} room${rooms.length !== 1 ? "s" : ""}`}
</p>
{users.length > 0 && (
<div
className="rounded-2xl border p-5"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<h2
className="text-lg font-black tracking-tight mb-4"
style={{ color: "var(--color-text-readable)" }}
>
Users
</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
{users.map((u) => (
<Link
key={u.id}
href={`/u/${u.username}`}
className="flex flex-col items-center gap-1.5 rounded-xl p-3 transition-all duration-200 hover:-translate-y-0.5"
style={{
background:
"color-mix(in srgb, var(--color-primary) 4%, transparent)",
}}
>
<Image
src={avatarImageUrl(u.look, {
headOnly: true,
direction: 2,
})}
alt={u.username}
width={40}
height={48}
unoptimized
className="rounded-lg"
/>
<span
className="text-xs font-extrabold truncate max-w-full text-center"
style={{ color: "var(--color-text-readable)" }}
>
{u.username}
</span>
<span
className="text-[9px] font-semibold"
style={{ color: "var(--color-text-muted)" }}
>
{u.online === "1" ? "🟢 Online" : "Offline"}
</span>
</Link>
))}
</div>
</div>
)}
{rooms.length > 0 && (
<div
className="rounded-2xl border p-5"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<h2
className="text-lg font-black tracking-tight mb-4"
style={{ color: "var(--color-text-readable)" }}
>
Rooms
</h2>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
{rooms.map((room) => (
<Link
key={room.id}
href={`/room/${room.id}`}
className="rounded-xl border p-3 transition-all duration-200 hover:-translate-y-0.5"
style={{
background:
"color-mix(in srgb, var(--color-primary) 4%, transparent)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<p
className="text-sm font-extrabold truncate"
style={{ color: "var(--color-text-readable)" }}
>
{room.name}
</p>
<p
className="text-[10px] font-semibold mt-0.5"
style={{ color: "var(--color-text-muted)" }}
>
{room.ownerName} · {room.users}/{room.usersMax}
</p>
</Link>
))}
</div>
</div>
)}
{users.length === 0 && rooms.length === 0 && (
<div
className="rounded-2xl border p-10 text-center"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<p
className="text-sm font-bold"
style={{ color: "var(--color-text-muted)" }}
>
No users or rooms found for &quot;{query}&quot;.
</p>
</div>
)}
</div>
);
}