From 6840c4a989a4e16a93acb521fcf2124672560dc0 Mon Sep 17 00:00:00 2001 From: choiboa Date: Fri, 16 Jan 2026 00:20:00 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20feat:=20=20=EC=B6=94=EC=B2=9C?= =?UTF-8?q?=20=ED=8F=AC=EC=8A=A4=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../[slug]/_components/RecommendedPosts.tsx | 32 +++++++------------ src/app/(layout)/posts/[slug]/page.tsx | 10 +++--- src/lib/posts/index.ts | 1 + src/lib/posts/recommend.ts | 20 ++++++++++++ 4 files changed, 37 insertions(+), 26 deletions(-) create mode 100644 src/lib/posts/recommend.ts diff --git a/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx b/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx index 348d2cc..6a2c159 100644 --- a/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx +++ b/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx @@ -1,28 +1,18 @@ import RecommendedPostCard, { RecommendedPost, } from "@/app/(layout)/posts/[slug]/_components/post-recommended/RecommendedPostCard"; +import { getRecommendedPostsForPost, VelitePost } from "@/lib/posts"; -export default function RecommendedPosts() { - const items: RecommendedPost[] = [ - { - title: "React 상태 관리, 왜 어려울까?", - overview: - "state, props, context가 섞일 때 복잡도가 급증하는 이유와 단순화 전략을 정리합니다.", - href: "#", - }, - { - title: "useEffect를 덜 쓰는 패턴", - overview: - "불필요한 effect를 줄이고 서버/쿼리/파생 상태로 대체하는 실전 패턴을 소개합니다.", - href: "#", - }, - { - title: "컴포넌트 분해 기준 5가지", - overview: - "재사용성과 응집도를 동시에 챙기는 컴포넌트 분해 체크리스트를 제공합니다.", - href: "#", - }, - ]; +export default function RecommendedPosts({ post }: { post: VelitePost }) { + const recommended = getRecommendedPostsForPost(post, 3); + if (!recommended.length) return null; + + const items: RecommendedPost[] = recommended.map((p) => ({ + title: p.title, + overview: p.summary ?? "", + href: `/posts/${p.slug}`, + thumbnail: p.thumbnail, + })); return (
diff --git a/src/app/(layout)/posts/[slug]/page.tsx b/src/app/(layout)/posts/[slug]/page.tsx index b9c26c5..17ad6f1 100644 --- a/src/app/(layout)/posts/[slug]/page.tsx +++ b/src/app/(layout)/posts/[slug]/page.tsx @@ -8,7 +8,7 @@ import RecommendedPosts from "@/app/(layout)/posts/[slug]/_components/Recommende import { adaptVeliteToc } from "@/lib/mdx/toc"; import { Metadata } from "next"; import { notFound } from "next/navigation"; -import { posts } from "../../../../../.velite"; +import { velitePosts } from "@/lib/posts"; type PageProps = { params: Promise<{ slug: string }>; @@ -18,7 +18,7 @@ export async function generateMetadata({ params, }: PageProps): Promise { const { slug } = await params; - const post = posts.find((p) => p.slug === slug); + const post = velitePosts.find((p) => p.slug === slug); if (!post) return {}; const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "https://b0o0a.com"; @@ -56,7 +56,7 @@ export async function generateMetadata({ export default async function PostPage({ params }: PageProps) { const { slug } = await params; - const post = posts.find((p) => p.slug === slug); + const post = velitePosts.find((p) => p.slug === slug); if (!post) return notFound(); return ( @@ -90,13 +90,13 @@ export default async function PostPage({ params }: PageProps) {
- +
- +
); diff --git a/src/lib/posts/index.ts b/src/lib/posts/index.ts index fbd916c..85d0569 100644 --- a/src/lib/posts/index.ts +++ b/src/lib/posts/index.ts @@ -6,3 +6,4 @@ export * from "./utils"; export * from "./viewModels"; export * from "./stats"; export * from "./tags"; +export * from "./recommend"; diff --git a/src/lib/posts/recommend.ts b/src/lib/posts/recommend.ts new file mode 100644 index 0000000..f0faaa2 --- /dev/null +++ b/src/lib/posts/recommend.ts @@ -0,0 +1,20 @@ +import { queryPosts } from "@/lib/posts/query"; +import { VelitePost } from "@/lib/posts/source"; + +export function getRecommendedPostsForPost( + post: VelitePost, + limit = 3 +): VelitePost[] { + if (!post.series) return []; + + const { posts } = queryPosts({ + series: post.series, + sort: "popular", + page: 1, + perPage: limit + 1, + }); + + const filtered = posts.filter((p) => p.slug !== post.slug); + + return filtered.slice(0, limit); +} From eb0050caab36f2a264d789d47062fad5b62ee67c Mon Sep 17 00:00:00 2001 From: choiboa Date: Fri, 16 Jan 2026 01:07:44 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=A8=20feat:=20=20supabse=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99=20=EB=B0=8F=20=EC=9D=B8=EA=B8=B0=EC=88=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_components/series/SeriesPostList.tsx | 4 +- .../(shell)/_components/posts/PostCard.tsx | 2 +- src/app/(layout)/(shell)/page.tsx | 2 +- .../[slug]/_components/RecommendedPosts.tsx | 4 +- .../post-recommended/RecommendedPostCard.tsx | 28 ++--- src/hooks/usePostLike.ts | 85 ++++---------- src/lib/posts/query.ts | 15 ++- src/lib/posts/recommend.ts | 8 +- src/lib/posts/source.ts | 2 +- src/lib/posts/utils.ts | 20 +++- src/lib/posts/viewModels.ts | 4 +- src/lib/supabase/postLikes.ts | 106 ++++++++++++++++++ velite.config.ts | 8 +- 13 files changed, 188 insertions(+), 100 deletions(-) create mode 100644 src/lib/supabase/postLikes.ts diff --git a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx index 195dfe3..c8faa7b 100644 --- a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx +++ b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx @@ -10,7 +10,7 @@ interface Props { pageSize: number; } -export default function SeriesPostList({ +export default async function SeriesPostList({ category, series, sort, @@ -19,7 +19,7 @@ export default function SeriesPostList({ }: Props) { const appliedSort: PostSort = sort === "popular" ? "popular" : "latest"; - const result = queryPosts({ + const result = await queryPosts({ category, series, sort: appliedSort, diff --git a/src/app/(layout)/(shell)/_components/posts/PostCard.tsx b/src/app/(layout)/(shell)/_components/posts/PostCard.tsx index cd4b8e8..6758787 100644 --- a/src/app/(layout)/(shell)/_components/posts/PostCard.tsx +++ b/src/app/(layout)/(shell)/_components/posts/PostCard.tsx @@ -9,7 +9,7 @@ export interface PostCardProps { category: string; date: string; title: string; - excerpt: string; + excerpt?: string; className?: string; size?: "md" | "sm"; } diff --git a/src/app/(layout)/(shell)/page.tsx b/src/app/(layout)/(shell)/page.tsx index ca7a3ad..291d1d5 100644 --- a/src/app/(layout)/(shell)/page.tsx +++ b/src/app/(layout)/(shell)/page.tsx @@ -19,7 +19,7 @@ export default async function HomePage({ const tags = getAllTags(false); - const { posts, pagination, pageRange, applied } = queryPosts(params); + const { posts, pagination, pageRange, applied } = await queryPosts(params); return (
diff --git a/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx b/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx index 6a2c159..16a093e 100644 --- a/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx +++ b/src/app/(layout)/posts/[slug]/_components/RecommendedPosts.tsx @@ -3,8 +3,8 @@ import RecommendedPostCard, { } from "@/app/(layout)/posts/[slug]/_components/post-recommended/RecommendedPostCard"; import { getRecommendedPostsForPost, VelitePost } from "@/lib/posts"; -export default function RecommendedPosts({ post }: { post: VelitePost }) { - const recommended = getRecommendedPostsForPost(post, 3); +export default async function RecommendedPosts({ post }: { post: VelitePost }) { + const recommended = await getRecommendedPostsForPost(post, 3); if (!recommended.length) return null; const items: RecommendedPost[] = recommended.map((p) => ({ diff --git a/src/app/(layout)/posts/[slug]/_components/post-recommended/RecommendedPostCard.tsx b/src/app/(layout)/posts/[slug]/_components/post-recommended/RecommendedPostCard.tsx index ccf9b7a..d188ee3 100644 --- a/src/app/(layout)/posts/[slug]/_components/post-recommended/RecommendedPostCard.tsx +++ b/src/app/(layout)/posts/[slug]/_components/post-recommended/RecommendedPostCard.tsx @@ -16,23 +16,22 @@ export default function RecommendedPostCard({ -
-
- {post.thumbnail ? ( - - ) : ( -
- )} +
+
+ {post.title}
@@ -40,7 +39,7 @@ export default function RecommendedPostCard({ className={clsx( "text-sm font-semibold text-neutral-900 transition-colors", "group-hover:text-neutral-950", - "dark:text-foreground/70", + "dark:text-foreground/90", "line-clamp-1" )} > @@ -52,9 +51,6 @@ export default function RecommendedPostCard({

- -
-
); } diff --git a/src/hooks/usePostLike.ts b/src/hooks/usePostLike.ts index 7fae34b..a17b5c7 100644 --- a/src/hooks/usePostLike.ts +++ b/src/hooks/usePostLike.ts @@ -1,9 +1,14 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import type { PostgrestError } from "@supabase/supabase-js"; -import { supabase } from "@/lib/supabase/client"; import { getOrCreateViewerId } from "@/lib/supabase/viewerId"; +import { + addPostLike, + fetchPostLikeCount, + fetchPostLikeState, + isDuplicateKeyError, + removePostLike, +} from "@/lib/supabase/postLikes"; type UsePostLikeResult = { count: number; @@ -12,10 +17,6 @@ type UsePostLikeResult = { toggleLike: () => Promise; }; -function isDuplicateKeyError(error: PostgrestError | null): boolean { - return !!error && error.code === "23505"; -} - export function usePostLike(postId: string): UsePostLikeResult { const [count, setCount] = useState(0); const [liked, setLiked] = useState(false); @@ -30,65 +31,24 @@ export function usePostLike(postId: string): UsePostLikeResult { useEffect(() => { if (!postId) return; - const fetchLikeState = async () => { + const run = async () => { const viewerId = viewerIdRef.current ?? getOrCreateViewerId(); - if (!viewerId) { - setLoading(false); - return; - } - - const { - data, - count: total, - error, - } = await supabase - .from("post_likes") - .select("viewer_id", { count: "exact" }) - .eq("post_id", postId); + const { count, liked } = await fetchPostLikeState(postId, viewerId); - if (error) { - console.error("좋아요 조회 실패:", error); - setLoading(false); - return; - } - - const rows = data ?? []; - - setCount(total ?? rows.length); - setLiked(rows.some((row) => row.viewer_id === viewerId)); + setCount(count); + setLiked(liked); setLoading(false); }; - void fetchLikeState(); + void run(); }, [postId]); - const refreshCount = async () => { - const { count: total, error } = await supabase - .from("post_likes") - .select("*", { count: "exact", head: true }) - .eq("post_id", postId); - - if (error) { - console.error("좋아요 카운트 재조회 실패:", error); - return; - } - - if (typeof total === "number") { - setCount(total); - } - }; - const toggleLike = async () => { const viewerId = viewerIdRef.current ?? getOrCreateViewerId(); - if (!viewerId) return; + if (!viewerId || !postId) return; if (liked) { - const { error } = await supabase - .from("post_likes") - .delete() - .eq("post_id", postId) - .eq("viewer_id", viewerId); - + const { error } = await removePostLike(postId, viewerId); if (error) { console.error("좋아요 취소 실패:", error); return; @@ -99,15 +59,13 @@ export function usePostLike(postId: string): UsePostLikeResult { return; } - const { error } = await supabase.from("post_likes").insert({ - post_id: postId, - viewer_id: viewerId, - }); + const { error } = await addPostLike(postId, viewerId); if (error) { if (isDuplicateKeyError(error)) { setLiked(true); - await refreshCount(); + const refreshed = await fetchPostLikeCount(postId); + setCount(refreshed); } else { console.error("좋아요 추가 실패:", error); } @@ -118,10 +76,5 @@ export function usePostLike(postId: string): UsePostLikeResult { setCount((prev) => prev + 1); }; - return { - count, - liked, - loading, - toggleLike, - }; -} \ No newline at end of file + return { count, liked, loading, toggleLike }; +} diff --git a/src/lib/posts/query.ts b/src/lib/posts/query.ts index 055ab37..05f9309 100644 --- a/src/lib/posts/query.ts +++ b/src/lib/posts/query.ts @@ -1,6 +1,7 @@ import type { VelitePost } from "./source"; import { getAllPosts } from "./queries"; import { sortPosts, type PostSort, paginate, type PaginatedResult, getPageRange } from "./utils"; +import { getLikeCountsForPosts } from "@/lib/supabase/postLikes"; export interface QueryPostsParams { category?: string; @@ -32,7 +33,7 @@ export interface QueryPostsResult { }; } -export function queryPosts(params: QueryPostsParams = {}): QueryPostsResult { +export async function queryPosts(params: QueryPostsParams = {}): Promise { const { category, series, @@ -56,7 +57,17 @@ export function queryPosts(params: QueryPostsParams = {}): QueryPostsResult { pool = pool.filter((p) => p.tags?.includes(tag)); } - const sorted = sortPosts(pool, sort); + let sorted: VelitePost[]; + + if (sort === "popular") { + const ids = pool.map((p) => p.slug); + + const likeCounts = await getLikeCountsForPosts(ids); + + sorted = sortPosts(pool, "popular", likeCounts); + } else { + sorted = sortPosts(pool, sort); + } const pagination = paginate(sorted, { page, perPage }); diff --git a/src/lib/posts/recommend.ts b/src/lib/posts/recommend.ts index f0faaa2..99efadb 100644 --- a/src/lib/posts/recommend.ts +++ b/src/lib/posts/recommend.ts @@ -1,19 +1,21 @@ import { queryPosts } from "@/lib/posts/query"; import { VelitePost } from "@/lib/posts/source"; -export function getRecommendedPostsForPost( +export async function getRecommendedPostsForPost( post: VelitePost, limit = 3 -): VelitePost[] { +): Promise { if (!post.series) return []; - const { posts } = queryPosts({ + const { posts } = await queryPosts({ series: post.series, sort: "popular", page: 1, perPage: limit + 1, }); + if (!posts || !posts.length) return []; + const filtered = posts.filter((p) => p.slug !== post.slug); return filtered.slice(0, limit); diff --git a/src/lib/posts/source.ts b/src/lib/posts/source.ts index 7130a41..8ac7877 100644 --- a/src/lib/posts/source.ts +++ b/src/lib/posts/source.ts @@ -1,4 +1,4 @@ -import { posts } from "#site"; +import { posts } from "../../../.velite"; export const velitePosts = posts; export type VelitePost = (typeof velitePosts)[number]; \ No newline at end of file diff --git a/src/lib/posts/utils.ts b/src/lib/posts/utils.ts index ca39aa5..cdb0934 100644 --- a/src/lib/posts/utils.ts +++ b/src/lib/posts/utils.ts @@ -1,8 +1,13 @@ +import { LikeCountMap } from "@/lib/supabase/postLikes"; import type { VelitePost } from "./source"; export type PostSort = "latest" | "popular"; -export function sortPosts(posts: VelitePost[], sort: PostSort): VelitePost[] { +export function sortPosts( + posts: VelitePost[], + sort: PostSort, + likeCounts?: LikeCountMap, +): VelitePost[] { const copied = [...posts]; if (sort === "latest") { @@ -11,8 +16,17 @@ export function sortPosts(posts: VelitePost[], sort: PostSort): VelitePost[] { } if (sort === "popular") { - // TODO: supabase 연동 이후 그 기준으로 정렬 - copied.sort((a, b) => b.date.localeCompare(a.date)); + copied.sort((a, b) => { + const aLikes = likeCounts?.[a.slug] ?? 0; + const bLikes = likeCounts?.[b.slug] ?? 0; + + if (aLikes !== bLikes) { + return bLikes - aLikes; + } + + return b.date.localeCompare(a.date); + }); + return copied; } diff --git a/src/lib/posts/viewModels.ts b/src/lib/posts/viewModels.ts index 2c6b51a..4f4b8d4 100644 --- a/src/lib/posts/viewModels.ts +++ b/src/lib/posts/viewModels.ts @@ -7,13 +7,13 @@ export interface PostCardModel { category: string; date: string; title: string; - excerpt: string; + excerpt?: string; } export function toPostCardModel(post: VelitePost): PostCardModel { return { href: `/posts/${post.slug}`, - thumbnailSrc: post.thumbnail ?? "/images/thumbnails/default.png", + thumbnailSrc: post.thumbnail ?? "/post-fallback.png", thumbnailAlt: post.title, category: post.category, date: post.date, diff --git a/src/lib/supabase/postLikes.ts b/src/lib/supabase/postLikes.ts new file mode 100644 index 0000000..2be7167 --- /dev/null +++ b/src/lib/supabase/postLikes.ts @@ -0,0 +1,106 @@ +import { supabase } from "@/lib/supabase/client"; +import { PostgrestError } from "@supabase/supabase-js"; + +export type LikeCountMap = Record; + +export type LikeState = { + count: number; + liked: boolean; +}; + +export function isDuplicateKeyError(error: PostgrestError | null): boolean { + return !!error && error.code === "23505"; +} + +/* 단일 포스트 좋아요 상태 + 카운트 조회 */ +export async function fetchPostLikeState( + postId: string, + viewerId: string | null, +): Promise { + if (!postId || !viewerId) { + return { count: 0, liked: false }; + } + + const { + data, + count: total, + error, + } = await supabase + .from("post_likes") + .select("viewer_id", { count: "exact" }) + .eq("post_id", postId); + + if (error) { + console.error("좋아요 조회 실패:", error); + return { count: 0, liked: false }; + } + + const rows = data ?? []; + const count = total ?? rows.length; + const liked = rows.some((row) => row.viewer_id === viewerId); + + return { count, liked }; +} + +/* 카운트만 새로 조회 */ +export async function fetchPostLikeCount(postId: string): Promise { + if (!postId) return 0; + + const { count: total, error } = await supabase + .from("post_likes") + .select("*", { count: "exact", head: true }) + .eq("post_id", postId); + + if (error) { + console.error("좋아요 카운트 재조회 실패:", error); + return 0; + } + + return typeof total === "number" ? total : 0; +} + +/* 좋아요 추가 */ +export async function addPostLike(postId: string, viewerId: string) { + const { error } = await supabase.from("post_likes").insert({ + post_id: postId, + viewer_id: viewerId, + }); + + return { error }; +} + +/* 좋아요 취소 */ +export async function removePostLike(postId: string, viewerId: string) { + const { error } = await supabase + .from("post_likes") + .delete() + .eq("post_id", postId) + .eq("viewer_id", viewerId); + + return { error }; +} + +/* 인기 정렬용 */ +export async function getLikeCountsForPosts( + postIds: string[], +): Promise { + if (!postIds.length) return {}; + + const { data, error } = await supabase + .from("post_like_counts") + .select("post_id, like_count") + .in("post_id", postIds); + + if (error) { + console.error("🚨 post_like_counts 조회 실패:", error); + return {}; + } + + const map: LikeCountMap = {}; + + for (const row of data ?? []) { + map[row.post_id] = row.like_count ?? 0; + } + + return map; +} diff --git a/velite.config.ts b/velite.config.ts index 51dc52a..72940dd 100644 --- a/velite.config.ts +++ b/velite.config.ts @@ -42,7 +42,13 @@ export default defineConfig({ tags: s.array(s.string()).default([]).transform(normalizeTags), summary: s.string().optional(), - thumbnail: s.string().min(1), + thumbnail: s + .string() + .optional() + .transform((v) => { + if (!v || v.trim().length === 0) return undefined; + return v; + }), draft: s.boolean().default(false), code: s.mdx(), From f80d58ee4345ca663a26028930c0c2a0a66c4d51 Mon Sep 17 00:00:00 2001 From: choiboa Date: Fri, 16 Jan 2026 01:17:30 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=94=A8=20Refactor=20:=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=20=EB=B0=98=EC=9D=91=ED=98=95=20=EA=B3=A0?= =?UTF-8?q?=EB=A0=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(shell)/_components/layout/SiteHeader.tsx | 43 ++++++++++--------- src/components/common/CommandPalette.tsx | 18 +++++--- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx b/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx index e1c8474..7336127 100644 --- a/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx +++ b/src/app/(layout)/(shell)/_components/layout/SiteHeader.tsx @@ -83,28 +83,29 @@ export default function SiteHeader() {
- - - - - - {isMobileMenuOpen ? : } -
+
+ + {isMobileMenuOpen ? : } + + + + +
diff --git a/src/components/common/CommandPalette.tsx b/src/components/common/CommandPalette.tsx index 35ab97a..6f6b238 100644 --- a/src/components/common/CommandPalette.tsx +++ b/src/components/common/CommandPalette.tsx @@ -60,12 +60,20 @@ export function CommandPaletteProvider({ - + + 사이트 검색 - +
- + - + ESC
- + 검색 결과가 없습니다. From 35890ea6a47aeaaefa1b0cfd9cc4911d4abf92fa Mon Sep 17 00:00:00 2001 From: choiboa Date: Fri, 16 Jan 2026 01:21:54 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=9A=A8=20Fix=20:=20=EC=9D=B8=EC=9A=A9?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B8=80=EC=9E=90=20?= =?UTF-8?q?=EA=B2=B9=EC=B9=A8=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/mdx/Quote.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/mdx/Quote.tsx b/src/components/mdx/Quote.tsx index 692d26e..556467b 100644 --- a/src/components/mdx/Quote.tsx +++ b/src/components/mdx/Quote.tsx @@ -66,7 +66,7 @@ export default function Quote({ )} /> -
+
{children}
From ee736f3648997f7d2537e6c98e5c14b33ab0c0b0 Mon Sep 17 00:00:00 2001 From: choiboa Date: Fri, 16 Jan 2026 01:26:48 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=92=84=20Style=20:=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(category)/[category]/_components/series/SeriesPostList.tsx | 2 +- src/app/(layout)/posts/[slug]/_components/PostToc.tsx | 2 +- src/components/common/fallback/BrowseFallback.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx index c8faa7b..766c5d5 100644 --- a/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx +++ b/src/app/(layout)/(shell)/(category)/[category]/_components/series/SeriesPostList.tsx @@ -32,7 +32,7 @@ export default async function SeriesPostList({ const seriesMeta = series ? getSeriesMeta(series) : null; return ( -
+

{seriesMeta?.name ?? "모아보기"}

diff --git a/src/app/(layout)/posts/[slug]/_components/PostToc.tsx b/src/app/(layout)/posts/[slug]/_components/PostToc.tsx index e4b35bf..40082be 100644 --- a/src/app/(layout)/posts/[slug]/_components/PostToc.tsx +++ b/src/app/(layout)/posts/[slug]/_components/PostToc.tsx @@ -28,7 +28,7 @@ export default function PostToc({ items }: { items: TocItem[] }) { "group relative block rounded-md py-1.5 pr-2 text-sm transition-colors", "pl-4", isActive - ? "text-gray-900 dark:text-blue-300" + ? "text-gray-900 dark:text-blue-300 font-semibold" : "text-neutral-400 hover:text-gray-800 dark:text-neutral-400 dark:hover:text-blue-300" )} > diff --git a/src/components/common/fallback/BrowseFallback.tsx b/src/components/common/fallback/BrowseFallback.tsx index b247c6f..a4625a4 100644 --- a/src/components/common/fallback/BrowseFallback.tsx +++ b/src/components/common/fallback/BrowseFallback.tsx @@ -10,7 +10,7 @@ export default function BrowseFallback({ description = "조금만 기다리면 더 맛있게 정리될 거예요.", }: BrowseFallbackProps) { return ( -
+
{/* 텍스트 */} From c54854d0653fad9ad3b083bd09cac165013cdb16 Mon Sep 17 00:00:00 2001 From: choiboa Date: Fri, 16 Jan 2026 01:37:36 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=93=9D=20Chore=20:=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=20=ED=82=A4=20ci=EC=97=90=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef5564c..730994b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,21 +9,24 @@ on: jobs: ci: runs-on: ubuntu-latest + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }} + steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v3 - with: {version: 9} + with: { version: 9 } - uses: actions/setup-node@v4 - with: + with: node-version: 20 cache: pnpm - run: pnpm install --frozen-lockfile - + - run: pnpm build:content - + - run: pnpm typecheck - run: pnpm lint - - - run: pnpm build + - run: pnpm build