Skip to content

Commit 1a8d70b

Browse files
lwinmoepaingclaude
andcommitted
💄 style(blog): modernize UI across blog pages, auth components, and content renderer
Refine SignInButton and UserAvatar with prismatic outline gradient and richer dropdown. Modernize MyPostsClient with left accent bars, micro icons, and status badges. Add author avatars to BlogCard with fallback initials. Redesign BlogPostClient with header card, share button, and author section. Polish ContentRenderer with article container and refined Lexical theme typography. Redirect blog edit to my-posts on save/publish. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5d08a26 commit 1a8d70b

8 files changed

Lines changed: 595 additions & 261 deletions

File tree

src/app/blog/BlogPageClient.tsx

Lines changed: 106 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {
1717
Calendar,
1818
PenLine,
1919
Sparkles,
20+
Clock3,
21+
Database,
22+
FileCode2,
2023
} from "lucide-react";
2124
import { useTranslations } from "next-intl";
2225
import { useLanguage } from "@/hooks/useLanguage";
@@ -32,6 +35,8 @@ type BlogItem = {
3235
date: string;
3336
slug: string;
3437
source?: "static" | "firestore";
38+
authorName?: string;
39+
authorPhotoURL?: string;
3540
};
3641

3742
/* ── Floating ambient orbs ── */
@@ -181,6 +186,11 @@ const BlogCard = ({
181186
year: "numeric",
182187
});
183188

189+
// Reading time estimate (rough: ~1min per 200 chars of description)
190+
const readTime = Math.max(1, Math.ceil((blog.description?.length ?? 80) / 200));
191+
192+
const SourceIcon = blog.source === "firestore" ? Database : FileCode2;
193+
184194
return (
185195
<motion.div
186196
className={cn(isFeatured && "md:col-span-2 lg:col-span-2")}
@@ -207,9 +217,16 @@ const BlogCard = ({
207217
"transition-all duration-500 ease-out",
208218
"hover:-translate-y-1.5 hover:border-white/[0.12]",
209219
"hover:shadow-[0_20px_60px_-15px_rgba(167,139,250,0.12)]",
210-
isFeatured ? "p-7 md:p-8" : "p-6"
211220
)}
212221
>
222+
{/* Top accent bar — always visible, colored by accent */}
223+
<div
224+
className="h-[2px] w-full"
225+
style={{
226+
background: `linear-gradient(90deg, ${accentColor}50, ${accentColor}20, transparent 80%)`,
227+
}}
228+
/>
229+
213230
{/* Cursor-following spotlight */}
214231
<motion.div
215232
className="absolute pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-500"
@@ -224,15 +241,7 @@ const BlogCard = ({
224241
}}
225242
/>
226243

227-
{/* Top accent line on hover */}
228-
<motion.div
229-
className="absolute top-0 left-0 right-0 h-[2px] opacity-0 group-hover:opacity-100 transition-opacity duration-500"
230-
style={{
231-
background: `linear-gradient(90deg, transparent, ${accentColor}, transparent)`,
232-
}}
233-
/>
234-
235-
{/* Corner glow */}
244+
{/* Corner glow on hover */}
236245
<div
237246
className="absolute -top-12 -right-12 w-32 h-32 opacity-0 group-hover:opacity-[0.06] transition-opacity duration-700 rounded-full"
238247
style={{
@@ -241,42 +250,54 @@ const BlogCard = ({
241250
/>
242251

243252
{/* Content */}
244-
<div className="relative z-10 flex flex-col h-full">
245-
{/* Header: date + featured badge */}
246-
<div className="flex items-center gap-3 mb-4">
247-
<div className="flex items-center gap-1.5">
248-
<Calendar
249-
className="w-3 h-3"
250-
style={{ color: accentColor }}
251-
/>
252-
<span className="font-mono text-[11px] text-zinc-500">
253-
{formattedDate}
254-
</span>
255-
</div>
253+
<div className={cn(
254+
"relative z-10 flex flex-col h-full",
255+
isFeatured ? "p-7 md:p-8" : "p-5 md:p-6"
256+
)}>
257+
{/* Header row: meta badges */}
258+
<div className="flex items-center flex-wrap gap-2.5 mb-4">
259+
{/* Date badge */}
260+
<span className="inline-flex items-center gap-1.5 text-[11px] font-mono text-zinc-500">
261+
<Calendar className="w-3 h-3" style={{ color: accentColor }} />
262+
{formattedDate}
263+
</span>
264+
265+
<span className="w-0.5 h-0.5 rounded-full bg-zinc-700" />
266+
267+
{/* Read time */}
268+
<span className="inline-flex items-center gap-1 text-[11px] font-mono text-zinc-600">
269+
<Clock3 className="w-2.5 h-2.5" />
270+
{readTime} min
271+
</span>
272+
273+
<span className="w-0.5 h-0.5 rounded-full bg-zinc-700" />
274+
275+
{/* Source micro badge */}
276+
<span
277+
className="inline-flex items-center gap-1 text-[9px] font-mono uppercase tracking-wider px-1.5 py-0.5 rounded-md border"
278+
style={{
279+
color: `${accentColor}90`,
280+
background: `${accentColor}06`,
281+
borderColor: `${accentColor}15`,
282+
}}
283+
>
284+
<SourceIcon className="w-2.5 h-2.5" />
285+
{blog.source === "firestore" ? "Community" : "MDX"}
286+
</span>
287+
288+
{/* Featured badge */}
256289
{isFeatured && (
257-
<motion.span
258-
className="flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-mono uppercase tracking-wider"
290+
<span
291+
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-mono uppercase tracking-wider"
259292
style={{
260293
background: `${accentColor}10`,
261294
border: `1px solid ${accentColor}25`,
262295
color: accentColor,
263296
}}
264-
animate={{
265-
boxShadow: [
266-
`0 0 0px ${accentColor}00`,
267-
`0 0 12px ${accentColor}20`,
268-
`0 0 0px ${accentColor}00`,
269-
],
270-
}}
271-
transition={{
272-
duration: 3,
273-
repeat: Infinity,
274-
ease: "easeInOut",
275-
}}
276297
>
277298
<Sparkles className="w-2.5 h-2.5" />
278299
Latest
279-
</motion.span>
300+
</span>
280301
)}
281302
</div>
282303

@@ -305,32 +326,56 @@ const BlogCard = ({
305326
</p>
306327
)}
307328

308-
{/* Read more (pushed to bottom) */}
309-
<div className="mt-auto flex items-center gap-2 text-sm">
310-
<span
311-
className="font-mono text-xs transition-colors duration-300"
312-
style={{ color: `${accentColor}90` }}
313-
>
314-
<span className={mmFont}>{t("readArticle")}</span>
315-
</span>
316-
<motion.span
317-
className="inline-block"
318-
initial={{ x: 0 }}
319-
whileHover={{ x: 3 }}
320-
>
329+
{/* Separator */}
330+
<div
331+
className="h-px w-full mb-4 mt-auto"
332+
style={{
333+
background: `linear-gradient(90deg, ${accentColor}10, white/[0.03], transparent)`,
334+
}}
335+
/>
336+
337+
{/* Footer: author + read more */}
338+
<div className="flex items-center justify-between">
339+
{/* Author */}
340+
<div className="flex items-center gap-2">
341+
{blog.authorPhotoURL ? (
342+
<img
343+
src={blog.authorPhotoURL}
344+
alt={blog.authorName ?? ""}
345+
className="w-5 h-5 rounded-full object-cover ring-1 ring-white/[0.08]"
346+
referrerPolicy="no-referrer"
347+
/>
348+
) : (
349+
<span
350+
className="w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold uppercase ring-1 ring-white/[0.08]"
351+
style={{
352+
background: `${accentColor}15`,
353+
color: `${accentColor}cc`,
354+
}}
355+
>
356+
{(blog.authorName ?? blog.source ?? "M").charAt(0)}
357+
</span>
358+
)}
359+
{blog.authorName && (
360+
<span className="text-[11px] font-mono text-zinc-500 truncate max-w-[100px]">
361+
{blog.authorName}
362+
</span>
363+
)}
364+
</div>
365+
366+
{/* Read more + index */}
367+
<div className="flex items-center gap-2 text-sm">
368+
<span
369+
className="font-mono text-xs transition-colors duration-300"
370+
style={{ color: `${accentColor}90` }}
371+
>
372+
<span className={mmFont}>{t("readArticle")}</span>
373+
</span>
321374
<ArrowUpRight
322375
className="w-3.5 h-3.5 transition-all duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
323376
style={{ color: accentColor }}
324377
/>
325-
</motion.span>
326-
327-
{/* Expanding line */}
328-
<motion.div
329-
className="h-[1px] flex-1 origin-left scale-x-0 group-hover:scale-x-100 transition-transform duration-500"
330-
style={{
331-
background: `linear-gradient(90deg, ${accentColor}40, transparent)`,
332-
}}
333-
/>
378+
</div>
334379
</div>
335380
</div>
336381
</div>
@@ -442,6 +487,8 @@ const BlogPageClient = ({ blogs: staticBlogs }: { blogs: BlogItem[] }) => {
442487
date: (p.publishedAt ?? p.createdAt).toISOString(),
443488
slug: `/blog/post?slug=${p.slug}`,
444489
source: "firestore" as const,
490+
authorName: p.authorName,
491+
authorPhotoURL: p.authorPhotoURL,
445492
}));
446493
const blogs = [...staticBlogs.map((b) => ({ ...b, source: "static" as const })), ...firestoreItems]
447494
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());

src/app/blog/edit/BlogEditClient.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import { useState, useCallback, Suspense } from "react";
4-
import { useSearchParams } from "next/navigation";
4+
import { useSearchParams, useRouter } from "next/navigation";
55
import { cn } from "@/utils";
66
import { motion } from "motion/react";
77
import { PenLine, Save, Tag, X, Image as ImageIcon, CheckCircle } from "lucide-react";
@@ -75,6 +75,7 @@ function TagInput({
7575
}
7676

7777
function BlogEditForm({ postId }: { postId: string }) {
78+
const router = useRouter();
7879
const { user, isAdmin } = useAuth();
7980
const {
8081
title,
@@ -106,6 +107,7 @@ function BlogEditForm({ postId }: { postId: string }) {
106107

107108
const handleSave = async () => {
108109
await save();
110+
router.push("/blog/my-posts");
109111
};
110112

111113
const handlePublishToggle = async () => {
@@ -118,7 +120,7 @@ function BlogEditForm({ postId }: { postId: string }) {
118120
} else {
119121
await publishBlogPost(postId, user.uid, isAdmin);
120122
}
121-
window.location.reload();
123+
router.push("/blog/my-posts");
122124
} catch (err) {
123125
if (err instanceof Error) {
124126
setPublishError(err.message);

0 commit comments

Comments
 (0)