style: convert remaining ad-hoc inline cards to shared SurfaceCard

Introduce components/surface-card.tsx (Card + CardBody) mirroring the
.content-card / SectionCard token set, and use it on the (site) pages that
still hand-rolled card markup: me, search, and verify. This puts every
public page on one of the shared card components (ContentCard, SectionCard,
or SurfaceCard) for consistent radius/shadow/border.
This commit is contained in:
openhands committed 2026-08-27 15:59:15 +02:00
1 parent a3e3356ea7
commit ed6eaf33a0
4 files changed
+73 -117

No files matched your search

+16 -62
View File
@@ -7,6 +7,7 @@ import { claimReferral } from "@/actions/referral";
import { Reveal } from "@/components/motion-reveal";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import { SurfaceCard } from "@/components/surface-card";
import { auth } from "@/lib/auth";
import {
db,
@@ -382,14 +383,9 @@ export default async function MePage({
{ value: friendCount.toLocaleString(), label: "Friends" },
{ value: lastLoginDate, label: "Last login" },
].map((s) => (
<div
<SurfaceCard
key={s.label}
className="relative rounded-2xl border p-5 text-center transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
}}
className="relative p-5 text-center transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg"
>
<div
className="text-xl md:text-2xl font-black tracking-tight mb-0.5"
@@ -403,20 +399,13 @@ export default async function MePage({
>
{s.label}
</div>
</div>
</SurfaceCard>
))}
</div>
</Reveal>
<Reveal delay={0.15}>
<div
className="rounded-2xl border p-5"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
}}
>
<SurfaceCard className="p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h2
@@ -497,18 +486,11 @@ export default async function MePage({
))}
</div>
)}
</div>
</SurfaceCard>
</Reveal>
<Reveal delay={0.2}>
<div
className="rounded-2xl border p-5"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
}}
>
<SurfaceCard className="p-5">
<div className="mb-4">
<h2
className="text-lg font-black tracking-tight"
@@ -592,19 +574,12 @@ export default async function MePage({
</button>
)}
</div>
</div>
</SurfaceCard>
</Reveal>
{recentRooms.length > 0 && (
<Reveal delay={0.25}>
<div
className="rounded-2xl border p-5"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
}}
>
<SurfaceCard className="p-5">
<div className="flex items-center justify-between mb-4">
<div>
<h2
@@ -656,20 +631,13 @@ export default async function MePage({
</Link>
))}
</div>
</div>
</SurfaceCard>
</Reveal>
)}
{badges.length > 0 && (
<Reveal delay={0.3}>
<div
className="rounded-2xl border p-5"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
}}
>
<SurfaceCard className="p-5">
<div className="mb-4">
<h2
className="text-lg font-black tracking-tight"
@@ -697,19 +665,12 @@ export default async function MePage({
/>
))}
</div>
</div>
</SurfaceCard>
</Reveal>
)}
<Reveal delay={0.35}>
<div
className="rounded-2xl border p-5"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-primary) 12%, transparent)",
}}
>
<SurfaceCard className="p-5">
<h2
className="text-lg font-black tracking-tight mb-4"
style={{ color: "var(--color-text-readable)" }}
@@ -750,20 +711,13 @@ export default async function MePage({
</Link>
))}
</div>
</div>
</SurfaceCard>
</Reveal>
</div>
);
} catch {
content = (
<div
className="rounded-2xl border p-6 text-center"
style={{
background: "var(--color-surface)",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
<SurfaceCard className="p-6 text-center">
<p
className="text-sm font-bold"
style={{ color: "var(--color-text-muted)" }}
@@ -771,7 +725,7 @@ export default async function MePage({
We couldn&apos;t load your dashboard right now. Please try again
shortly.
</p>
</div>
</SurfaceCard>
);
}
+11 -45
View File
@@ -2,6 +2,7 @@ import { and, eq, like } from "drizzle-orm";
import type { Metadata } from "next";
import Link from "next/link";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import { SurfaceCard } from "@/components/surface-card";
import { db, Rooms, User } from "@/lib/db";
export const metadata: Metadata = { title: "Search" };
@@ -18,21 +19,14 @@ export default async function SearchPage({
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)",
}}
>
<SurfaceCard className="p-10 text-center">
<p
className="text-sm font-bold"
style={{ color: "var(--color-text-muted)" }}
>
Enter a search term to find users.
</p>
</div>
</SurfaceCard>
);
}
@@ -66,14 +60,7 @@ export default async function SearchPage({
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)",
}}
>
<SurfaceCard className="p-4">
<form action="/search" method="GET" className="flex gap-2">
<input
type="text"
@@ -99,7 +86,7 @@ export default async function SearchPage({
Search
</button>
</form>
</div>
</SurfaceCard>
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
Results for &quot;{query}&quot; — {users.length} user
@@ -109,14 +96,7 @@ export default async function SearchPage({
</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)",
}}
>
<SurfaceCard className="p-5">
<h2
className="text-lg font-black tracking-tight mb-4"
style={{ color: "var(--color-text-readable)" }}
@@ -155,18 +135,11 @@ export default async function SearchPage({
</Link>
))}
</div>
</div>
</SurfaceCard>
)}
{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)",
}}
>
<SurfaceCard className="p-5">
<h2
className="text-lg font-black tracking-tight mb-4"
style={{ color: "var(--color-text-readable)" }}
@@ -201,25 +174,18 @@ export default async function SearchPage({
</Link>
))}
</div>
</div>
</SurfaceCard>
)}
{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)",
}}
>
<SurfaceCard className="p-10 text-center">
<p
className="text-sm font-bold"
style={{ color: "var(--color-text-muted)" }}
>
No users or rooms found for &quot;{query}&quot;.
</p>
</div>
</SurfaceCard>
)}
</div>
);
+3 -10
View File
@@ -3,6 +3,7 @@ import { CheckCircle2, Clock, MailX } from "lucide-react";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
import { isValidVerificationToken } from "@/actions/email-verify";
import { SurfaceCard } from "@/components/surface-card";
import { db, User } from "@/lib/db";
type Status = "verified" | "already" | "invalid" | "unavailable";
@@ -30,15 +31,7 @@ function StatusCard({
return (
<main className="flex min-h-[60vh] items-center justify-center px-4">
<div
className="w-full max-w-md overflow-hidden rounded-2xl border"
style={{
borderColor:
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)",
backgroundColor: "var(--color-surface)",
boxShadow: "var(--shadow-card)",
}}
>
<SurfaceCard className="w-full max-w-md">
<div
className="flex items-center gap-2.5 border-b px-5 py-4"
style={{
@@ -81,7 +74,7 @@ function StatusCard({
</p>
{children}
</div>
</div>
</SurfaceCard>
</main>
);
}
+43
View File
@@ -0,0 +1,43 @@
import type { CSSProperties, ReactNode } from "react";
const CARD_BORDER =
"color-mix(in srgb, var(--color-text-muted) 12%, transparent)";
/**
* Public-site surface card. Mirrors the CSS .content-card / SectionCard visual
* language (--radius-lg corners, --shadow-card, hairline border) so every page
* that opts out of ContentCard still shares one consistent component.
*/
export function SurfaceCard({
className = "",
style,
children,
}: {
className?: string;
style?: CSSProperties;
children: ReactNode;
}) {
return (
<div
className={`overflow-hidden rounded-2xl border bg-[var(--color-surface)] ${className}`}
style={{
borderColor: CARD_BORDER,
boxShadow: "var(--shadow-card)",
...style,
}}
>
{children}
</div>
);
}
/** Padded body for a SurfaceCard. Override padding via className. */
export function SurfaceCardBody({
className = "p-5",
children,
}: {
className?: string;
children: ReactNode;
}) {
return <div className={className}>{children}</div>;
}