style(frontend): refactor UI components with sky color palette, improved spacing, and design polish

This commit is contained in:
MythEclipse
2026-06-03 03:18:04 +07:00
parent 20681ef308
commit e8deba93cc
14 changed files with 240 additions and 161 deletions
@@ -1,5 +1,6 @@
import type { MessageRecord } from "../../../shared/api/client";
import { parseMetadata } from "../../../entities/message/types";
import { EmptyStateMascot } from "../../../widgets/mascot/ChibiMascot";
interface ImageItem {
url: string;
@@ -8,6 +9,17 @@ interface ImageItem {
message: MessageRecord;
}
function kindBadge(kind: ImageItem["kind"]): string {
switch (kind) {
case "sticker":
return "bg-pink-100 text-pink-700 border-pink-200";
case "attachment":
return "bg-primary-soft text-primary border-primary/30";
case "embed":
return "bg-purple-100 text-purple-700 border-purple-200";
}
}
export function ImageGrid({ messages }: { messages: MessageRecord[] }) {
const images: ImageItem[] = [];
@@ -57,9 +69,7 @@ export function ImageGrid({ messages }: { messages: MessageRecord[] }) {
if (images.length === 0) {
return (
<div className="rounded-2xl border border-dashed border-border p-10 text-center text-sm text-muted-foreground">
No images found.
</div>
<EmptyStateMascot variant="thinking" message="No images yet~" />
);
}
@@ -74,7 +84,7 @@ export function ImageGrid({ messages }: { messages: MessageRecord[] }) {
href={image.url}
target="_blank"
rel="noreferrer"
className="group overflow-hidden rounded-2xl border border-border bg-card shadow-sm transition-all hover:border-primary/30 hover:shadow-md"
className="group overflow-hidden rounded-2xl border border-primary/20 bg-white shadow-sm transition-all hover:border-primary/40 hover:shadow-md"
>
<div className="relative aspect-video overflow-hidden">
{image.kind === "sticker" ? (
@@ -92,14 +102,16 @@ export function ImageGrid({ messages }: { messages: MessageRecord[] }) {
loading="lazy"
/>
)}
<div className="absolute right-2 top-2 rounded-md bg-black/60 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider text-white backdrop-blur">
<span
className={`absolute right-2 top-2 rounded-full border px-2 py-0.5 text-[10px] font-medium uppercase tracking-wider shadow-sm backdrop-blur ${kindBadge(image.kind)}`}
>
{image.kind}
</div>
</span>
</div>
<div className="p-3">
<div className="truncate text-sm font-medium">{image.title}</div>
<div className="flex items-center gap-2">
<div className="h-4 w-4 overflow-hidden rounded-full">
<div className="h-4 w-4 overflow-hidden rounded-full ring-1 ring-primary/30">
<img
src={
image.message.avatar_url ??
@@ -94,13 +94,13 @@ function aiVariant(status: string) {
function severityColor(severity: string) {
switch (severity) {
case "critical":
return "bg-red-500/20 text-red-300 border-red-500/30";
return "bg-red-100 text-red-700 border-red-200";
case "high":
return "bg-orange-500/20 text-orange-300 border-orange-500/30";
return "bg-orange-100 text-orange-700 border-orange-200";
case "medium":
return "bg-yellow-500/20 text-yellow-300 border-yellow-500/30";
return "bg-yellow-100 text-yellow-700 border-yellow-200";
case "low":
return "bg-blue-500/20 text-blue-300 border-blue-500/30";
return "bg-blue-100 text-blue-700 border-blue-200";
default:
return "bg-muted text-muted-foreground border-border";
}
@@ -175,7 +175,13 @@ export function MessageCard({
return (
<article
className={`group rounded-2xl border border-border bg-card ${compact ? "px-4 py-1.5" : "p-4"} shadow-sm transition-all hover:border-primary/30 hover:shadow-md ${message.deleted_at ? "opacity-60" : ""}`}
className={`group rounded-2xl border bg-white shadow-sm transition-all hover:border-primary/30 hover:shadow-md ${
compact ? "px-4 py-1.5" : "p-4"
} ${
message.deleted_at
? "border-red-200 opacity-60"
: "border-primary/20"
}`}
>
<div className={`flex ${compact ? "gap-2" : "gap-3"}`}>
{!compact && (
@@ -185,7 +191,7 @@ export function MessageCard({
"https://cdn.discordapp.com/embed/avatars/0.png"
}
alt=""
className="h-10 w-10 shrink-0 rounded-full object-cover ring-1 ring-border"
className="h-10 w-10 shrink-0 rounded-full object-cover ring-2 ring-primary/30"
/>
)}
<div
@@ -319,10 +325,10 @@ export function MessageCard({
{message.ai_analysis ? (
<div
className={`rounded-xl border-l-2 p-3 ${
className={`rounded-xl border-l-4 p-3 ${
aiStatus === "flagged"
? "border-l-red-500 bg-red-500/5"
: "border-l-blue-500 bg-blue-500/5"
? "border-l-pink-400 bg-pink-50/50"
: "border-l-emerald-400 bg-emerald-50/50"
}`}
>
<button
@@ -331,9 +337,7 @@ export function MessageCard({
className="flex w-full items-center justify-between gap-2 text-left text-xs"
>
<span className="font-medium text-foreground/80">
{aiStatus === "flagged"
? "🚨"
: "️"}{" "}
{aiStatus === "flagged" ? "🚨 " : "️ "}
{analysisSummary}
</span>
{showAnalysis ? (
@@ -351,7 +355,7 @@ export function MessageCard({
) : null}
{message.ai_error ? (
<div className="rounded-xl bg-destructive/10 p-3 text-sm text-destructive">
<div className="rounded-xl bg-pink-50/50 p-3 text-sm text-pink-700">
AI error: {message.ai_error}
</div>
) : null}
@@ -370,7 +374,7 @@ export function MessageCard({
{isReanalyzing ? "Reanalyzing..." : "Re-analyze"}
</Button>
{aiStatus === "error" && (
<span className="text-xs text-destructive/80">
<span className="text-xs text-pink-600/80">
Click to retry analysis
</span>
)}
@@ -383,7 +387,7 @@ export function MessageCard({
export function MessageCardSkeleton() {
return (
<article className="rounded-2xl border border-border bg-card p-4 shadow-sm">
<article className="rounded-2xl border border-primary/20 bg-white p-4 shadow-sm">
<div className="flex gap-3">
<Skeleton className="h-10 w-10 shrink-0 rounded-full" />
<div className="min-w-0 flex-1 space-y-3">
@@ -1,7 +1,13 @@
import { motion } from "framer-motion";
import { useEffect, useMemo, useRef } from "react";
import type { MessageRecord } from "../../../shared/api/client";
import {
cardStagger,
cardItem,
} from "../../../shared/hooks/useFramerStagger";
import { ScrollArea } from "../../../shared/ui";
import { MessageCard, MessageCardSkeleton } from "./MessageCard";
import { EmptyStateMascot } from "../../../widgets/mascot/ChibiMascot";
export interface MessageFeedProps {
messages: MessageRecord[];
@@ -81,27 +87,29 @@ export function MessageFeed({
}
if (messages.length === 0) {
return (
<div className="rounded-2xl border border-dashed border-border p-10 text-center text-sm text-muted-foreground">
{emptyText}
</div>
);
return <EmptyStateMascot variant="waving" message="No messages yet~" />;
}
return (
<ScrollArea className="h-[calc(100vh-260px)] pr-3">
<div className="space-y-3">
<motion.div
className="space-y-3"
variants={cardStagger}
initial="initial"
animate="animate"
>
{groupedMessages.map((group) =>
group.messages.map((message, idx) => {
const isFirstInGroup = idx === 0;
const isCompact = !isFirstInGroup;
return (
<MessageCard
key={message.id}
message={message}
onReanalyze={onReanalyze}
compact={isCompact}
/>
<motion.div key={message.id} variants={cardItem}>
<MessageCard
message={message}
onReanalyze={onReanalyze}
compact={isCompact}
/>
</motion.div>
);
}),
)}
@@ -115,11 +123,11 @@ export function MessageFeed({
{loadingMore ? (
<MessageCardSkeleton />
) : (
<div className="h-2 w-2 rounded-full bg-muted-foreground/40" />
<div className="h-2 w-2 rounded-full bg-primary/40" />
)}
</div>
)}
</div>
</motion.div>
</ScrollArea>
);
}
@@ -13,6 +13,22 @@ interface AlertDetail {
brief: string;
}
function severityToToastType(
severity: string,
): "error" | "warning" | "info" | "success" {
switch (severity) {
case "critical":
case "high":
return "error";
case "medium":
return "warning";
case "low":
return "info";
default:
return "warning";
}
}
export function ModerationAlertListener() {
const { addToast } = useToast();
@@ -29,7 +45,7 @@ export function ModerationAlertListener() {
addToast(
`🚨 ${username} ${sevLabel}${catLabel}: ${brief}`,
"error",
severityToToastType(severity),
);
};